1 package com.vladmihalcea.hibernate.type.json.internal;
2
3 import com.vladmihalcea.hibernate.util.LogUtils;
4 import com.vladmihalcea.hibernate.type.util.ObjectMapperWrapper;
5 import com.vladmihalcea.hibernate.util.ReflectionUtils;
6 import org.hibernate.HibernateException;
7 import org.hibernate.annotations.common.reflection.XProperty;
8 import org.hibernate.annotations.common.reflection.java.JavaXMember;
9 import org.hibernate.engine.jdbc.BinaryStream;
10 import org.hibernate.engine.jdbc.internal.BinaryStreamImpl;
11 import org.hibernate.type.descriptor.WrapperOptions;
12 import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
13 import org.hibernate.type.descriptor.java.BlobTypeDescriptor;
14 import org.hibernate.type.descriptor.java.DataHelper;
15 import org.hibernate.type.descriptor.java.MutableMutabilityPlan;
16 import org.hibernate.usertype.DynamicParameterizedType;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.lang.reflect.Method;
22 import java.lang.reflect.ParameterizedType;
23 import java.lang.reflect.Type;
24 import java.lang.reflect.TypeVariable;
25 import java.sql.Blob;
26 import java.sql.SQLException;
27 import java.util.*;
28
29 /**
30  * @author Vlad Mihalcea
31  */

32 public class JsonTypeDescriptor
33     extends AbstractTypeDescriptor<Object> implements DynamicParameterizedType {
34
35     private Type propertyType;
36
37     private Class propertyClass;
38
39     private ObjectMapperWrapper objectMapperWrapper;
40
41     public JsonTypeDescriptor() {
42         this(ObjectMapperWrapper.INSTANCE);
43     }
44
45     public JsonTypeDescriptor(Type type) {
46         this();
47         setPropertyClass(type);
48     }
49
50     public JsonTypeDescriptor(final ObjectMapperWrapper objectMapperWrapper) {
51         super(Object.classnew MutableMutabilityPlan<Object>() {
52             @Override
53             protected Object deepCopyNotNull(Object value) {
54                 return objectMapperWrapper.clone(value);
55             }
56         });
57         this.objectMapperWrapper = objectMapperWrapper;
58     }
59
60     public JsonTypeDescriptor(final ObjectMapperWrapper objectMapperWrapper, Type type) {
61         this(objectMapperWrapper);
62         setPropertyClass(type);
63     }
64
65     @Override
66     public void setParameterValues(Properties parameters) {
67         final XProperty xProperty = (XProperty) parameters.get(DynamicParameterizedType.XPROPERTY);
68         Type type = (xProperty instanceof JavaXMember) ?
69             ((JavaXMember) xProperty).getJavaType() :
70             ((ParameterType) parameters.get(PARAMETER_TYPE)).getReturnedClass();
71         setPropertyClass(type);
72     }
73
74     @Override
75     public boolean areEqual(Object one, Object another) {
76         if (one == another) {
77             return true;
78         }
79         if (one == null || another == null) {
80             return false;
81         }
82         if (one instanceof String && another instanceof String) {
83             return one.equals(another);
84         }
85         if ((one instanceof Collection && another instanceof Collection) ||
86             (one instanceof Map && another instanceof Map)) {
87             return Objects.equals(one, another);
88         }
89         if (one.getClass().equals(another.getClass()) &&
90             ReflectionUtils.getDeclaredMethodOrNull(one.getClass(), "equals", Object.class) != null) {
91             return one.equals(another);
92         }
93         return objectMapperWrapper.toJsonNode(objectMapperWrapper.toString(one)).equals(
94             objectMapperWrapper.toJsonNode(objectMapperWrapper.toString(another))
95         );
96     }
97
98     @Override
99     public String toString(Object value) {
100         return objectMapperWrapper.toString(value);
101     }
102
103     @Override
104     public Object fromString(String string) {
105         if(propertyClass == null) {
106             throw new HibernateException(
107                 "The propertyClass in JsonTypeDescriptor is null, " +
108                     "hence it doesn't know to what Java Object type " +
109                     "to map the JSON column value that was read from the database!"
110             );
111         }
112         if (String.class.isAssignableFrom(propertyClass)) {
113             return string;
114         }
115         return objectMapperWrapper.fromString(string, propertyType);
116     }
117
118     @SuppressWarnings({"unchecked"})
119     @Override
120     public <X> X unwrap(Object value, Class<X> type, WrapperOptions options) {
121         if (value == null) {
122             return null;
123         }
124
125         if (String.class.isAssignableFrom(type)) {
126             return value instanceof String ? (X) value : (X) toString(value);
127         } else if (BinaryStream.class.isAssignableFrom(type) ||
128             byte[].class.isAssignableFrom(type)) {
129             String stringValue = (value instanceof String) ? (String) value : toString(value);
130
131             return (X) new BinaryStreamImpl(DataHelper.extractBytes(new ByteArrayInputStream(stringValue.getBytes())));
132         } else if (Blob.class.isAssignableFrom(type)) {
133             String stringValue = (value instanceof String) ? (String) value : toString(value);
134
135             final Blob blob = BlobTypeDescriptor.INSTANCE.fromString(stringValue);
136             return (X) blob;
137         } else if (Object.class.isAssignableFrom(type)) {
138             String stringValue = (value instanceof String) ? (String) value : toString(value);
139             return (X) objectMapperWrapper.toJsonNode(stringValue);
140         }
141
142         throw unknownUnwrap(type);
143     }
144
145     @Override
146     public <X> Object wrap(X value, WrapperOptions options) {
147         if (value == null) {
148             return null;
149         }
150
151         Blob blob = null;
152
153         if (Blob.class.isAssignableFrom(value.getClass())) {
154             blob = options.getLobCreator().wrap((Blob) value);
155         } else if (byte[].class.isAssignableFrom(value.getClass())) {
156             blob = options.getLobCreator().createBlob((byte[]) value);
157         } else if (InputStream.class.isAssignableFrom(value.getClass())) {
158             InputStream inputStream = (InputStream) value;
159             try {
160                 blob = options.getLobCreator().createBlob(inputStream, inputStream.available());
161             } catch (IOException e) {
162                 throw unknownWrap(value.getClass());
163             }
164         }
165
166         String stringValue;
167         try {
168             stringValue = (blob != null) ? new String(DataHelper.extractBytes(blob.getBinaryStream())) : value.toString();
169         } catch (SQLException e) {
170             throw new HibernateException("Unable to extract binary stream from Blob", e);
171         }
172
173         return fromString(stringValue);
174     }
175
176     private void setPropertyClass(Type type) {
177         this.propertyType = type;
178         if (type instanceof ParameterizedType) {
179             type = ((ParameterizedType) type).getRawType();
180         } else if (type instanceof TypeVariable) {
181             type = ((TypeVariable) type).getGenericDeclaration().getClass();
182         }
183         this.propertyClass = (Class) type;
184         validatePropertyType();
185     }
186
187     private void validatePropertyType() {
188         if(Collection.class.isAssignableFrom(propertyClass)) {
189             if (propertyType instanceof ParameterizedType) {
190                 ParameterizedType parameterizedType = (ParameterizedType) propertyType;
191
192                 for(Class genericType : ReflectionUtils.getGenericTypes(parameterizedType)) {
193                     if(validatedTypes.contains(genericType)) {
194                         continue;
195                     }
196                     validatedTypes.add(genericType);
197                     Method equalsMethod = ReflectionUtils.getMethodOrNull(genericType, "equals", Object.class);
198                     Method hashCodeMethod = ReflectionUtils.getMethodOrNull(genericType, "hashCode");
199
200                     if(equalsMethod == null ||
201                         hashCodeMethod == null ||
202                         Object.class.equals(equalsMethod.getDeclaringClass()) ||
203                         Object.class.equals(hashCodeMethod.getDeclaringClass())) {
204                         LogUtils.LOGGER.warn("The {} class should override both the equals and hashCode methods based on the JSON object value it represents!", genericType);
205                     }
206                 }
207             }
208         }
209     }
210
211     private static List<Class> validatedTypes = new ArrayList<>();
212 }
213