From 703cc43282f4acf1ac95fc5efaf158ec2998e9a6 Mon Sep 17 00:00:00 2001 From: John Blum Date: Tue, 7 Jul 2020 11:26:13 -0700 Subject: [PATCH] Add ResourceUtils abstract utility class containing operations for working with Resource objects and byte arrays. Resolves gh-92. --- .../geode/core/io/support/ResourceUtils.java | 80 +++++++++ .../io/support/ResourceUtilsUnitTests.java | 159 ++++++++++++++++++ 2 files changed, 239 insertions(+) create mode 100644 spring-geode/src/main/java/org/springframework/geode/core/io/support/ResourceUtils.java create mode 100644 spring-geode/src/test/java/org/springframework/geode/core/io/support/ResourceUtilsUnitTests.java diff --git a/spring-geode/src/main/java/org/springframework/geode/core/io/support/ResourceUtils.java b/spring-geode/src/main/java/org/springframework/geode/core/io/support/ResourceUtils.java new file mode 100644 index 00000000..2bc576d2 --- /dev/null +++ b/spring-geode/src/main/java/org/springframework/geode/core/io/support/ResourceUtils.java @@ -0,0 +1,80 @@ +/* + * 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.support; + +import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException; + +import java.util.Optional; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.WritableResource; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + +/** + * Abstract utility class containing functionality to work with {@link Resource Resources}. + * + * @author John Blum + * @see org.springframework.core.io.Resource + * @see org.springframework.core.io.WritableResource + * @since 1.3.1 + */ +public abstract class ResourceUtils { + + /** + * Returns the {@link Resource} as a {@link WritableResource} if possible. + * + * This method makes a best effort to determine whether the target {@link Resource} is actually {@literal writable}. + * Even still, it may be possible that a write to the target {@link Resource} will fail. + * + * @param resource {@link Resource} to cast as a {@link WritableResource}. + * @return a {@link WritableResource} from the target {@link Resource} if possible; never {@literal null}. + * @throws IllegalStateException if the target {@link Resource} is not writable. + * @see org.springframework.core.io.Resource + * @see org.springframework.core.io.WritableResource + */ + public static @NonNull WritableResource getWritableResource(@Nullable Resource resource) { + + return Optional.ofNullable(resource) + .filter(WritableResource.class::isInstance) + .map(WritableResource.class::cast) + .filter(WritableResource::isWritable) + .orElseThrow(() -> newIllegalStateException("Resource [%s] is not writable", + ResourceUtils.nullSafeGetDescription(resource))); + } + + /** + * Determines whether the given byte array is {@literal null} or {@literal empty}. + * + * @param array byte array to evaluate. + * @return a boolean value indicating whether the given byte array is {@literal null} or {@literal empty}. + */ + public static boolean isNotEmpty(@Nullable byte[] array) { + return array != null && array.length > 0; + } + + /** + * Null-safe method to get the {@link Resource#getDescription() description} of the given {@link Resource}. + * + * @param resource {@link Resource} to describe. + * @return a {@link Resource#getDescription() description} of the {@link Resource}, or {@literal null} + * if the {@link Resource} handle is {@literal null}. + * @see org.springframework.core.io.Resource + */ + public static @Nullable String nullSafeGetDescription(@Nullable Resource resource) { + return resource != null ? resource.getDescription() : null; + } +} diff --git a/spring-geode/src/test/java/org/springframework/geode/core/io/support/ResourceUtilsUnitTests.java b/spring-geode/src/test/java/org/springframework/geode/core/io/support/ResourceUtilsUnitTests.java new file mode 100644 index 00000000..f5d98058 --- /dev/null +++ b/spring-geode/src/test/java/org/springframework/geode/core/io/support/ResourceUtilsUnitTests.java @@ -0,0 +1,159 @@ +/* + * 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.support; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import org.junit.Test; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.WritableResource; + +/** + * Unit Tests for {@link ResourceUtils}. + * + * @author John Blum + * @see org.junit.Test + * @see org.mockito.Mockito + * @see org.springframework.core.io.Resource + * @see org.springframework.core.io.WritableResource + * @see org.springframework.geode.core.io.support.ResourceUtils + * @since 1.3.1 + */ +public class ResourceUtilsUnitTests { + + @Test + public void getWritableResourceReturnsWritableResourceFromResource() { + + WritableResource mockResource = mock(WritableResource.class); + + doReturn(true).when(mockResource).isWritable(); + + assertThat(ResourceUtils.getWritableResource(mockResource)).isEqualTo(mockResource); + + verify(mockResource, times(1)).isWritable(); + verifyNoMoreInteractions(mockResource); + } + + @Test(expected = IllegalStateException.class) + public void getWritableResourceFromResourceThrowsIllegalStateException() { + + Resource mockResource = mock(Resource.class); + + doReturn("MOCK").when(mockResource).getDescription(); + + try { + ResourceUtils.getWritableResource(mockResource); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("Resource [MOCK] is not writable"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(mockResource, times(1)).getDescription(); + verifyNoMoreInteractions(mockResource); + } + } + + @Test(expected = IllegalStateException.class) + public void getWritableResourceFromNullThrowsIllegalStateException() { + + try { + ResourceUtils.getWritableResource(null); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("Resource [null] is not writable"); + assertThat(expected).hasNoCause(); + + throw expected; + } + } + + @Test(expected = IllegalStateException.class) + public void getWritableResourceFromNonWritableWritableResourceThrowsIllegalStateException() { + + WritableResource mockResource = mock(WritableResource.class); + + doReturn("TEST").when(mockResource).getDescription(); + doReturn(false).when(mockResource).isWritable(); + + try { + ResourceUtils.getWritableResource(mockResource); + } + catch (IllegalStateException expected) { + + assertThat(expected).hasMessage("Resource [TEST] is not writable"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + verify(mockResource, times(1)).getDescription(); + verify(mockResource, times(1)).isWritable(); + verifyNoMoreInteractions(mockResource); + } + } + + @Test + public void isNotEmptyWithNonEmptyByteArrayReturnsTrue() { + + byte[] array = { + (byte) 0xCA, + (byte) 0xFE, + (byte) 0xBA, + (byte) 0xBE + }; + + assertThat(ResourceUtils.isNotEmpty(array)).isTrue(); + } + + @Test + public void isNotEmptyWithEmptyByteArrayReturnsFalse() { + assertThat(ResourceUtils.isNotEmpty(new byte[0])).isFalse(); + } + + @Test + public void isNotEmptyWithNullByteArrayReturnsFalse() { + assertThat(ResourceUtils.isNotEmpty(null)).isFalse(); + } + + @Test + public void nullSafeDescriptionWithNonNullResource() { + + Resource mockResource = mock(Resource.class); + + doReturn("TEST").when(mockResource).getDescription(); + + assertThat(ResourceUtils.nullSafeGetDescription(mockResource)).isEqualTo("TEST"); + + verify(mockResource, times(1)).getDescription(); + verifyNoMoreInteractions(mockResource); + } + + @Test + public void nullSafeGetDescriptionWithNullResource() { + assertThat(ResourceUtils.nullSafeGetDescription(null)).isEqualTo(null); + } +}