From d2591f7f8ca8edc3d88e974ed35de3d75e4cbdf4 Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 7 Jul 2020 11:21:47 -0700 Subject: [PATCH] Define ResourceWriter interface for writing data to a resource identified by a Resource handle. Resolves gh-92. --- .../geode/core/io/ResourceWriter.java | 94 ++++++++++++ .../core/io/ResourceWriterUnitTests.java | 134 ++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 spring-geode/src/main/java/org/springframework/geode/core/io/ResourceWriter.java create mode 100644 spring-geode/src/test/java/org/springframework/geode/core/io/ResourceWriterUnitTests.java diff --git a/spring-geode/src/main/java/org/springframework/geode/core/io/ResourceWriter.java b/spring-geode/src/main/java/org/springframework/geode/core/io/ResourceWriter.java new file mode 100644 index 00000000..883eec3d --- /dev/null +++ b/spring-geode/src/main/java/org/springframework/geode/core/io/ResourceWriter.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 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 + * + * https://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.geode.core.io; + +import java.nio.ByteBuffer; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.WritableResource; +import org.springframework.lang.NonNull; + +/** + * Interface (contract) for writers to define the algorithm or strategy for writing data to a target {@link Resource}, + * such as by using the {@link WritableResource WritableResource's} + * {@link WritableResource#getOutputStream()} OutputStream}. + * + * @author John Blum + * @see org.springframework.core.io.Resource + * @see org.springframework.core.io.WritableResource + * @since 1.3.1 + */ +@FunctionalInterface +@SuppressWarnings("unused") +public interface ResourceWriter { + + /** + * Writes data to the target {@link Resource} as defined by the algorithm/strategy of this writer. + * + * This method should throw an {@link UnhandledResourceException} if the algorithm or strategy used by this writer + * is not able to or capable of writing to the {@link Resource} at its location. This allows subsequent writers + * in a composition to possibly handle the {@link Resource}. Any other {@link Exception} thrown by this + * {@code write} method will break the chain of write calls in the composition. + * + * @param resource {@link Resource} to write data to. + * @param data array of bytes containing the data to write to the target {@link Resource}. + * @see org.springframework.core.io.Resource + */ + void write(@NonNull Resource resource, byte[] data); + + /** + * Writes data contained in the {@link ByteBuffer} to the target {@link Resource} as defined by + * the algorithm/strategy of this writer. + * + * This method should throw an {@link UnhandledResourceException} if the algorithm or strategy used by this writer + * is not able to or capable of writing to the {@link Resource} at its location. This allows subsequent writers + * in a composition to possibly handle the {@link Resource}. Any other {@link Exception} thrown by this + * {@code write} method will break the chain of write calls in the composition. + * + * @param resource {@link Resource} to write data to. + * @param data {@link ByteBuffer} containing the data to write to the target {@link Resource}. + * @see org.springframework.core.io.Resource + * @see java.nio.ByteBuffer + * @see #write(Resource, byte[]) + */ + default void write (@NonNull Resource resource, ByteBuffer data) { + write(resource, data.array()); + } + + /** + * Composes this {@link ResourceWriter} with the given {@link ResourceWriter} + * using the {@literal Composite Software Design Pattern}. + * + * @param writer {@link ResourceWriter} to compose with this writer. + * @return a composite {@link ResourceWriter} composed of this {@link ResourceWriter} + * and the given {@link ResourceWriter}. If the given {@link ResourceWriter} is {@literal null}, + * then this {@link ResourceWriter} is returned. + * @see Compsite Software Design Pattern + * @see ResourceWriter + */ + default ResourceWriter thenWriteTo(ResourceWriter writer) { + + return writer == null ? this + : (resource, data) -> { + try { + this.write(resource, data); + } + catch (UnhandledResourceException ignore) { + writer.write(resource, data); + } + }; + } +} diff --git a/spring-geode/src/test/java/org/springframework/geode/core/io/ResourceWriterUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/core/io/ResourceWriterUnitTests.java new file mode 100644 index 00000000..ebe95cdf --- /dev/null +++ b/spring-geode/src/test/java/org/springframework/geode/core/io/ResourceWriterUnitTests.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 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 + * + * https://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.geode.core.io; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import java.nio.ByteBuffer; + +import org.junit.Test; + +import org.springframework.core.io.Resource; + +/** + * Unit Tests for {@link ResourceWriter}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.core.io.Resource + * @see org.springframework.geode.core.io.ResourceWriter + * @since 1.3.1 + */ +public class ResourceWriterUnitTests { + + @Test + public void writeWithByteBufferCallsWriteWithByteArray() { + + byte[] data = { (byte) 0x1 }; + + Resource mockResource = mock(Resource.class); + + ResourceWriter writer = mock(ResourceWriter.class); + + doCallRealMethod().when(writer).write(any(), any(ByteBuffer.class)); + + writer.write(mockResource, ByteBuffer.wrap(data)); + + verify(writer, times(1)).write(eq(mockResource), eq(data)); + } + + @Test + public void thenWriteToNullReturnsThis() { + + ResourceWriter mockResourceWriter = mock(ResourceWriter.class); + + doCallRealMethod().when(mockResourceWriter).thenWriteTo(any()); + + assertThat(mockResourceWriter.thenWriteTo(null)).isEqualTo(mockResourceWriter); + } + + @Test + public void thenWriteToResourceWriterReturnsComposite() { + + ResourceWriter thisMockResourceWriter = mock(ResourceWriter.class, "this"); + ResourceWriter thatMockResourceWriter = mock(ResourceWriter.class, "that"); + + doCallRealMethod().when(thisMockResourceWriter).thenWriteTo(any()); + + ResourceWriter composite = thisMockResourceWriter.thenWriteTo(thatMockResourceWriter); + + assertThat(composite).isNotNull(); + assertThat(composite).isNotEqualTo(thisMockResourceWriter); + assertThat(composite).isNotEqualTo(thatMockResourceWriter); + } + + @Test + public void thenWriteToResourceWriterWritesToThis() { + + byte[] array = { (byte) 0xCA, (byte) 0xFE }; + + Resource mockResource = mock(Resource.class); + + ResourceWriter thisMockResourceWriter = mock(ResourceWriter.class, "this"); + ResourceWriter thatMockResourceWriter = mock(ResourceWriter.class, "that"); + + doCallRealMethod().when(thisMockResourceWriter).thenWriteTo(any()); + + ResourceWriter composite = thisMockResourceWriter.thenWriteTo(thatMockResourceWriter); + + assertThat(composite).isNotNull(); + + composite.write(mockResource, array); + + verify(thisMockResourceWriter, times(1)).write(eq(mockResource), eq(array)); + verify(thatMockResourceWriter, never()).write(any(), any(byte[].class)); + verifyNoInteractions(mockResource); + } + + @Test + public void thenWriteToResourceWriterWritesToThat() { + + byte[] array = { (byte) 0xBA, (byte) 0xBE }; + + Resource mockResource = mock(Resource.class); + + ResourceWriter thisMockResourceWriter = mock(ResourceWriter.class, "this"); + ResourceWriter thatMockResourceWriter = mock(ResourceWriter.class, "that"); + + doCallRealMethod().when(thisMockResourceWriter).thenWriteTo(any()); + doThrow(new UnhandledResourceException("TEST")).when(thisMockResourceWriter).write(eq(mockResource), eq(array)); + + ResourceWriter composite = thisMockResourceWriter.thenWriteTo(thatMockResourceWriter); + + assertThat(composite).isNotNull(); + + composite.write(mockResource, array); + + verify(thisMockResourceWriter, times(1)).write(eq(mockResource), eq(array)); + verify(thatMockResourceWriter, times(1)).write(any(), any(byte[].class)); + verifyNoInteractions(mockResource); + } +}