001/* 002 * Copyright 2023 the original author or authors. 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.cuioss.test.valueobjects.contract; 017 018import static java.util.Objects.requireNonNull; 019import static org.junit.jupiter.api.Assertions.assertEquals; 020import static org.junit.jupiter.api.Assertions.assertNotNull; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022 023import java.io.ByteArrayInputStream; 024import java.io.ByteArrayOutputStream; 025import java.io.ObjectInputStream; 026import java.io.ObjectOutputStream; 027import java.io.Serializable; 028import java.util.Arrays; 029import java.util.List; 030import java.util.SortedSet; 031import java.util.TreeSet; 032 033import de.cuioss.test.valueobjects.api.object.ObjectTestConfig; 034import de.cuioss.test.valueobjects.api.object.ObjectTestContract; 035import de.cuioss.test.valueobjects.objects.ParameterizedInstantiator; 036import de.cuioss.test.valueobjects.objects.impl.ExceptionHelper; 037import de.cuioss.test.valueobjects.property.PropertyMetadata; 038import de.cuioss.tools.logging.CuiLogger; 039import lombok.RequiredArgsConstructor; 040 041/** 042 * Tests whether the object in hand implements {@link Serializable} and than 043 * serializes / deserializes the object, and compares the newly created object 044 * with the original by using {@link Object#equals(Object)} 045 * 046 * @author Oliver Wolff 047 */ 048@RequiredArgsConstructor 049public class SerializableContractImpl implements ObjectTestContract { 050 051 private static final CuiLogger log = new CuiLogger(SerializableContractImpl.class); 052 053 @Override 054 public void assertContract(final ParameterizedInstantiator<?> instantiator, 055 final ObjectTestConfig objectTestConfig) { 056 requireNonNull(instantiator); 057 058 final var builder = new StringBuilder("Verifying "); 059 builder.append(getClass().getName()).append("\nWith configuration: ").append(instantiator.toString()); 060 log.info(builder.toString()); 061 062 var shouldUseEquals = checkForEqualsComparison(objectTestConfig); 063 064 Object template = instantiator.newInstanceMinimal(); 065 066 assertTrue(template instanceof Serializable, 067 template.getClass().getName() + " does not implement java.io.Serializable"); 068 069 final var serializationFailedMessage = template.getClass().getName() + " is not equal after serialization"; 070 var serializeAndDeserialize = serializeAndDeserialize(template); 071 if (shouldUseEquals) { 072 assertEquals(template, serializeAndDeserialize, serializationFailedMessage); 073 } 074 if (!checkTestBasicOnly(objectTestConfig) 075 && !instantiator.getRuntimeProperties().getWritableProperties().isEmpty()) { 076 var properties = filterProperties(instantiator.getRuntimeProperties().getWritableProperties(), 077 objectTestConfig); 078 template = instantiator.newInstance(properties); 079 serializeAndDeserialize = serializeAndDeserialize(template); 080 if (shouldUseEquals) { 081 assertEquals(template, serializeAndDeserialize, serializationFailedMessage); 082 } 083 } 084 } 085 086 static List<PropertyMetadata> filterProperties(final List<PropertyMetadata> allProperties, 087 final ObjectTestConfig objectTestConfig) { 088 if (null == objectTestConfig) { 089 return allProperties; 090 } 091 final SortedSet<String> consideredAttributes = new TreeSet<>(); 092 allProperties.forEach(p -> consideredAttributes.add(p.getName())); 093 // Whitelist takes precedence 094 if (objectTestConfig.serializableOf().length > 0) { 095 consideredAttributes.clear(); 096 consideredAttributes.addAll(Arrays.asList(objectTestConfig.serializableOf())); 097 } else { 098 consideredAttributes.removeAll(Arrays.asList(objectTestConfig.serializableExclude())); 099 } 100 return allProperties.stream().filter(p -> consideredAttributes.contains(p.getName())).toList(); 101 } 102 103 static boolean checkForEqualsComparison(final ObjectTestConfig objectTestConfig) { 104 return null == objectTestConfig || objectTestConfig.serializableCompareUsingEquals(); 105 } 106 107 static boolean checkTestBasicOnly(final ObjectTestConfig objectTestConfig) { 108 return null != objectTestConfig && objectTestConfig.serializableBasicOnly(); 109 } 110 111 /** 112 * Shorthand combining the calls {@link #serializeObject(Object)} 113 * {@link #deserializeObject(byte[])} 114 * 115 * @param object to be serialized, must not be null 116 * @return the deserialized object. 117 */ 118 public static final Object serializeAndDeserialize(final Object object) { 119 assertNotNull(object, "Given Object must not be null"); 120 final var serialized = serializeObject(object); 121 return deserializeObject(serialized); 122 } 123 124 /** 125 * Serializes an object into a newly created byteArray 126 * 127 * @param object to be serialized 128 * @return the resulting byte array 129 */ 130 public static final byte[] serializeObject(final Object object) { 131 assertNotNull(object, "Given Object must not be null"); 132 final var baos = new ByteArrayOutputStream(1024); 133 try (var oas = new ObjectOutputStream(baos)) { 134 oas.writeObject(object); 135 oas.flush(); 136 } catch (final Exception e) { 137 throw new AssertionError( 138 "Unable to serialize, due to " + ExceptionHelper.extractCauseMessageFromThrowable(e)); 139 } 140 return baos.toByteArray(); 141 } 142 143 /** 144 * Deserializes an object from a given byte-array 145 * 146 * @param bytes to be deserialized 147 * @return the deserialized object 148 */ 149 public static final Object deserializeObject(final byte[] bytes) { 150 assertNotNull(bytes, "Given byte-array must not be null"); 151 final var bais = new ByteArrayInputStream(bytes); 152 try (var ois = new ObjectInputStream(bais)) { 153 return ois.readObject(); 154 } catch (final Exception e) { 155 throw new AssertionError( 156 "Unable to deserialize, due to " + ExceptionHelper.extractCauseMessageFromThrowable(e)); 157 } 158 } 159 160}