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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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