Browse Source
This commit introduces the ResourceEncoder and ResourceDecoder, and uses these in ResourceHttpMessageConverter as a non-zero-copy fallback method.pull/1111/head
8 changed files with 431 additions and 132 deletions
@ -0,0 +1,77 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2016 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.core.codec.support; |
||||||
|
|
||||||
|
import java.io.InputStream; |
||||||
|
|
||||||
|
import org.reactivestreams.Publisher; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.core.io.ByteArrayResource; |
||||||
|
import org.springframework.core.io.InputStreamResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.core.io.buffer.DataBuffer; |
||||||
|
import org.springframework.core.io.buffer.support.DataBufferUtils; |
||||||
|
import org.springframework.util.MimeType; |
||||||
|
import org.springframework.util.MimeTypeUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* A decoder for {@link Resource}s. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public class ResourceDecoder extends AbstractDecoder<Resource> { |
||||||
|
|
||||||
|
public ResourceDecoder() { |
||||||
|
super(MimeTypeUtils.ALL); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean canDecode(ResolvableType type, MimeType mimeType, Object... hints) { |
||||||
|
Class<?> clazz = type.getRawClass(); |
||||||
|
return (InputStreamResource.class.equals(clazz) || |
||||||
|
clazz.isAssignableFrom(ByteArrayResource.class)) && |
||||||
|
super.canDecode(type, mimeType, hints); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Flux<Resource> decode(Publisher<DataBuffer> inputStream, ResolvableType type, |
||||||
|
MimeType mimeType, Object... hints) { |
||||||
|
Class<?> clazz = type.getRawClass(); |
||||||
|
|
||||||
|
Flux<DataBuffer> body = Flux.from(inputStream); |
||||||
|
|
||||||
|
if (InputStreamResource.class.equals(clazz)) { |
||||||
|
InputStream is = DataBufferUtils.toInputStream(body); |
||||||
|
return Flux.just(new InputStreamResource(is)); |
||||||
|
} |
||||||
|
else if (clazz.isAssignableFrom(ByteArrayResource.class)) { |
||||||
|
Mono<DataBuffer> singleBuffer = body.reduce(DataBuffer::write); |
||||||
|
return Flux.from(singleBuffer.map(buffer -> { |
||||||
|
byte[] bytes = new byte[buffer.readableByteCount()]; |
||||||
|
buffer.read(bytes); |
||||||
|
return new ByteArrayResource(bytes); |
||||||
|
})); |
||||||
|
} |
||||||
|
else { |
||||||
|
return Flux.error(new IllegalStateException( |
||||||
|
"Unsupported resource class: " + clazz)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,78 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2016 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.core.codec.support; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
|
||||||
|
import org.reactivestreams.Publisher; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.core.io.buffer.DataBuffer; |
||||||
|
import org.springframework.core.io.buffer.DataBufferAllocator; |
||||||
|
import org.springframework.core.io.buffer.support.DataBufferUtils; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.MimeType; |
||||||
|
import org.springframework.util.MimeTypeUtils; |
||||||
|
import org.springframework.util.StreamUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* An encoder for {@link Resource}s. |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public class ResourceEncoder extends AbstractEncoder<Resource> { |
||||||
|
|
||||||
|
public static final int DEFAULT_BUFFER_SIZE = StreamUtils.BUFFER_SIZE; |
||||||
|
|
||||||
|
private final int bufferSize; |
||||||
|
|
||||||
|
public ResourceEncoder() { |
||||||
|
this(DEFAULT_BUFFER_SIZE); |
||||||
|
} |
||||||
|
|
||||||
|
public ResourceEncoder(int bufferSize) { |
||||||
|
super(MimeTypeUtils.ALL); |
||||||
|
Assert.isTrue(bufferSize > 0, "'bufferSize' must be larger than 0"); |
||||||
|
this.bufferSize = bufferSize; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean canEncode(ResolvableType type, MimeType mimeType, Object... hints) { |
||||||
|
Class<?> clazz = type.getRawClass(); |
||||||
|
return (super.canEncode(type, mimeType, hints) && |
||||||
|
Resource.class.isAssignableFrom(clazz)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Flux<DataBuffer> encode(Publisher<? extends Resource> inputStream, |
||||||
|
DataBufferAllocator allocator, ResolvableType type, MimeType mimeType, |
||||||
|
Object... hints) { |
||||||
|
return Flux.from(inputStream). |
||||||
|
concatMap(resource -> { |
||||||
|
try { |
||||||
|
InputStream is = resource.getInputStream(); |
||||||
|
return DataBufferUtils.read(is, allocator, this.bufferSize); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
return Mono.error(ex); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,59 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2016 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.core.io.support; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.net.URI; |
||||||
|
|
||||||
|
import org.springframework.core.io.ByteArrayResource; |
||||||
|
import org.springframework.core.io.DescriptiveResource; |
||||||
|
import org.springframework.core.io.InputStreamResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.ResourceUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public abstract class ResourceUtils2 { |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicates whether the given resource has a file, so that {@link |
||||||
|
* Resource#getFile()} |
||||||
|
* can be called without an {@link java.io.IOException}. |
||||||
|
* @param resource the resource to check |
||||||
|
* @return {@code true} if the given resource has a file; {@code false} otherwise |
||||||
|
*/ |
||||||
|
// TODO: refactor into Resource.hasFile() method
|
||||||
|
public static boolean hasFile(Resource resource) { |
||||||
|
Assert.notNull(resource, "'resource' must not be null"); |
||||||
|
|
||||||
|
// the following Resource implementations do not support getURI/getFile
|
||||||
|
if (resource instanceof ByteArrayResource || |
||||||
|
resource instanceof DescriptiveResource || |
||||||
|
resource instanceof InputStreamResource) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
try { |
||||||
|
URI resourceUri = resource.getURI(); |
||||||
|
return ResourceUtils.URL_PROTOCOL_FILE.equals(resourceUri.getScheme()); |
||||||
|
} |
||||||
|
catch (IOException ignored) { |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,80 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2016 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.core.codec.support; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.test.TestSubscriber; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.core.io.ByteArrayResource; |
||||||
|
import org.springframework.core.io.InputStreamResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.core.io.buffer.DataBuffer; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.util.StreamUtils; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public class ResourceDecoderTests extends AbstractAllocatingTestCase { |
||||||
|
|
||||||
|
private final ResourceDecoder decoder = new ResourceDecoder(); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void canDecode() throws Exception { |
||||||
|
assertTrue(decoder.canDecode(ResolvableType.forClass(InputStreamResource.class), |
||||||
|
MediaType.TEXT_PLAIN)); |
||||||
|
assertTrue(decoder.canDecode(ResolvableType.forClass(ByteArrayResource.class), |
||||||
|
MediaType.TEXT_PLAIN)); |
||||||
|
assertTrue(decoder.canDecode(ResolvableType.forClass(Resource.class), |
||||||
|
MediaType.TEXT_PLAIN)); |
||||||
|
assertTrue(decoder.canDecode(ResolvableType.forClass(InputStreamResource.class), |
||||||
|
MediaType.APPLICATION_JSON)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void decode() throws Exception { |
||||||
|
DataBuffer fooBuffer = stringBuffer("foo"); |
||||||
|
DataBuffer barBuffer = stringBuffer("bar"); |
||||||
|
Flux<DataBuffer> source = Flux.just(fooBuffer, barBuffer); |
||||||
|
|
||||||
|
Flux<Resource> result = |
||||||
|
decoder.decode(source, ResolvableType.forClass(Resource.class), null); |
||||||
|
|
||||||
|
TestSubscriber<Resource> testSubscriber = new TestSubscriber<>(); |
||||||
|
testSubscriber.bindTo(result). |
||||||
|
assertNoError(). |
||||||
|
assertComplete(). |
||||||
|
assertValuesWith(resource -> { |
||||||
|
try { |
||||||
|
byte[] bytes = |
||||||
|
StreamUtils.copyToByteArray(resource.getInputStream()); |
||||||
|
assertEquals("foobar", new String(bytes)); |
||||||
|
} |
||||||
|
catch (IOException e) { |
||||||
|
fail(e.getMessage()); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2016 the original author or authors. |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package org.springframework.core.codec.support; |
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
import reactor.core.test.TestSubscriber; |
||||||
|
|
||||||
|
import org.springframework.core.ResolvableType; |
||||||
|
import org.springframework.core.io.ByteArrayResource; |
||||||
|
import org.springframework.core.io.InputStreamResource; |
||||||
|
import org.springframework.core.io.Resource; |
||||||
|
import org.springframework.core.io.buffer.DataBuffer; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Arjen Poutsma |
||||||
|
*/ |
||||||
|
public class ResourceEncoderTests extends AbstractAllocatingTestCase { |
||||||
|
|
||||||
|
private final ResourceEncoder encoder = new ResourceEncoder(); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void canEncode() throws Exception { |
||||||
|
assertTrue(encoder.canEncode(ResolvableType.forClass(InputStreamResource.class), |
||||||
|
MediaType.TEXT_PLAIN)); |
||||||
|
assertTrue(encoder.canEncode(ResolvableType.forClass(ByteArrayResource.class), |
||||||
|
MediaType.TEXT_PLAIN)); |
||||||
|
assertTrue(encoder.canEncode(ResolvableType.forClass(Resource.class), |
||||||
|
MediaType.TEXT_PLAIN)); |
||||||
|
assertTrue(encoder.canEncode(ResolvableType.forClass(InputStreamResource.class), |
||||||
|
MediaType.APPLICATION_JSON)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void encode() throws Exception { |
||||||
|
String s = "foo"; |
||||||
|
Resource resource = new ByteArrayResource(s.getBytes(StandardCharsets.UTF_8)); |
||||||
|
|
||||||
|
Mono<Resource> source = Mono.just(resource); |
||||||
|
|
||||||
|
Flux<DataBuffer> output = |
||||||
|
encoder.encode(source, allocator, ResolvableType.forClass(Resource.class), |
||||||
|
null); |
||||||
|
|
||||||
|
TestSubscriber<DataBuffer> testSubscriber = new TestSubscriber<>(); |
||||||
|
testSubscriber.bindTo(output).assertNoError().assertComplete() |
||||||
|
.assertValues(stringBuffer(s)); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue