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
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.class, new 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