Skip to content

Instantly share code, notes, and snippets.

@nhtzr
Created October 31, 2017 20:22
Show Gist options
  • Save nhtzr/5f7747729abf8af1c8472ba5e730ce00 to your computer and use it in GitHub Desktop.
Save nhtzr/5f7747729abf8af1c8472ba5e730ce00 to your computer and use it in GitHub Desktop.
package us.peopleconnect.microservicecore.util;
import java.io.IOException;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.SerializationException;
import org.hibernate.usertype.UserType;
import org.postgresql.util.PGobject;
import org.springframework.util.ObjectUtils;
/**
* Usertype to persist objects as JSONB. If the data needs to be cloned, use deepCopy and
* also set mutable flag to true. Example usage : metadata field in AuthorizationService
* Request Entity.
*/
public class AbstractJsonType<T> implements UserType {
private ObjectMapper mapper;
private Class<T> returnedClass;
public AbstractJsonType(Class<T> returnedClass) {
this.returnedClass = returnedClass;
this.mapper = new ObjectMapper();
}
public AbstractJsonType(Class<T> returnedClass, ObjectMapper mapper) {
this.returnedClass = returnedClass;
this.mapper = mapper;
}
/**
* Return the SQL type codes for the columns mapped by this type. The codes are defined
* on <tt>java.sql.Types</tt>.
*
* @return int[] the typecodes
* @see java.sql.Types
*/
@Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT };
}
/**
* The class returned by <tt>nullSafeGet()</tt>.
*
* @return Class
*/
@Override
public Class returnedClass() {
return returnedClass;
}
/**
* Compare two instances of the class mapped by this type for persistence "equality".
* Equality of the persistent state.
*
* @param x
* @param y
* @return boolean
*/
@Override
public boolean equals(Object x, Object y) throws HibernateException {
return ObjectUtils.nullSafeEquals(x, y);
}
/**
* Get a hashcode for the instance, consistent with persistence "equality"
*/
@Override
public int hashCode(Object x) throws HibernateException {
if (x == null) {
return 0;
}
return x.hashCode();
}
/**
* Retrieve an instance of the mapped class from a JDBC resultset. Implementors should
* handle possibility of null values.
*
* @param rs
* a JDBC result set
* @param names
* the column names
* @param session
* @param owner
* the containing entity @return Object
* @throws org.hibernate.HibernateException
* @throws java.sql.SQLException
*/
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session,
Object owner) throws HibernateException, SQLException {
PGobject o = (PGobject) rs.getObject(names[0]);
if (o.getValue() != null) {
try {
return mapper.readValue(o.getValue(), returnedClass);
} catch (IOException e) {
throw new HibernateException(e);
}
}
return new HashMap<String, String>();
}
/**
* Write an instance of the mapped class to a prepared statement. Implementors should
* handle possibility of null values. A multi-column type should be written to
* parameters starting from <tt>index</tt>.
*
* @param st
* a JDBC prepared statement
* @param value
* the object to write
* @param index
* statement parameter index
* @param session
* @throws org.hibernate.HibernateException
* @throws java.sql.SQLException
*/
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SharedSessionContractImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
}
else {
try {
String string = mapper.writeValueAsString(value);
st.setObject(index, string, Types.OTHER);
}
catch (IOException e) {
throw new HibernateException(e);
}
}
}
/**
* Return a deep copy of the persistent state, stopping at entities and at collections.
* It is not necessary to copy immutable objects, or null values, in which case it is
* safe to simply return the argument.
*
* @param originalValue
* the object to be cloned, which may be null
* @return Object a copy
*/
@Override
public Object deepCopy(Object originalValue) throws HibernateException {
if (originalValue == null) {
return null;
}
if (!(returnedClass.isInstance(originalValue))) {
return null;
}
try {
return mapper.treeToValue(mapper.valueToTree(originalValue), returnedClass);
} catch (IOException e) {
throw new HibernateException(e);
}
}
/**
* Are objects of this type mutable?
*
* @return boolean
*/
@Override
public boolean isMutable() {
return true;
}
/**
* Transform the object into its cacheable representation. At the very least this method
* should perform a deep copy if the type is mutable. That may not be enough for some
* implementations, however; for example, associations must be cached as identifier
* values. (optional operation)
*
* @param value
* the object to be cached
* @return a cachable representation of the object
* @throws org.hibernate.HibernateException
*/
@Override
public Serializable disassemble(Object value) throws HibernateException {
Object copy = deepCopy(value);
if (copy instanceof Serializable) {
return (Serializable) copy;
}
throw new SerializationException(
String.format("Cannot serialize '%s', %s is not Serializable.", value, value.getClass()),
null);
}
/**
* Reconstruct an object from the cacheable representation. At the very least this
* method should perform a deep copy if the type is mutable. (optional operation)
*
* @param cached
* the object to be cached
* @param owner
* the owner of the cached object
* @return a reconstructed object from the cachable representation
* @throws org.hibernate.HibernateException
*/
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return deepCopy(cached);
}
/**
* During merge, replace the existing (target) value in the entity we are merging to
* with a new (original) value from the detached entity we are merging. For immutable
* objects, or null values, it is safe to simply return the first parameter. For mutable
* objects, it is safe to return a copy of the first parameter. For objects with
* component values, it might make sense to recursively replace component values.
*
* @param original
* the value from the detached entity being merged
* @param target
* the value in the managed entity
* @return the value to be merged
*/
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy(original);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment