blob: f0f07933fdc335d061f602bae85b746a7ac6c5c6 [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* one or more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package com.gemstone.gemfire.pdx.internal;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gemstone.gemfire.InternalGemFireException;
import com.gemstone.gemfire.distributed.internal.DMStats;
import com.gemstone.gemfire.internal.ClassPathLoader;
import com.gemstone.gemfire.internal.DSCODE;
import com.gemstone.gemfire.internal.InternalDataSerializer;
import com.gemstone.gemfire.internal.Sendable;
import com.gemstone.gemfire.internal.cache.GemFireCacheImpl;
import com.gemstone.gemfire.internal.tcp.ByteBufferInputStream.ByteSource;
import com.gemstone.gemfire.internal.tcp.ByteBufferInputStream.ByteSourceFactory;
import com.gemstone.gemfire.pdx.JSONFormatter;
import com.gemstone.gemfire.pdx.PdxInstance;
import com.gemstone.gemfire.pdx.PdxSerializationException;
import com.gemstone.gemfire.pdx.WritablePdxInstance;
/**
* Implementation code in this class must be careful to not directly call
* super class state. Instead it must call {@link #getUnmodifiableReader()} and
* access the super class state using it.
* This class could be changed to not extend PdxReaderImpl but to instead have
* an instance variable that is a PdxReaderImpl but that would cause this class
* to use more memory.
*
* We do not use this normal java io serialization
* when serializing this class in GemFire because Sendable takes precedence over Serializable.
*
* @author darrel
*
*/
public class PdxInstanceImpl extends PdxReaderImpl implements PdxInstance, Sendable, ConvertableToBytes {
private static final long serialVersionUID = -1669268527103938431L;
private transient volatile Object cachedObjectForm;
/**
* Computes the hash code once and stores it. This is added to address the
* issue of identity value getting changed for each hash code call (as new objects
* instances are created in each call for the same object).
* The value 0 means the hash code is not yet computed (this avoids using
* extra variable for this), if the computation returns 0, it will be set to 1. This
* doesn't break the equality rule, where hash code can be same for non-equal
* objects.
*/
private static final int UNUSED_HASH_CODE = 0;
private transient volatile int cachedHashCode = UNUSED_HASH_CODE;
private static final ThreadLocal<Boolean> pdxGetObjectInProgress = new ThreadLocal<Boolean>();
public PdxInstanceImpl(PdxType pdxType, DataInput in, int len) {
super(pdxType, createDis(in, len));
}
protected PdxInstanceImpl(PdxReaderImpl original) {
super(original);
}
private static PdxInputStream createDis(DataInput in, int len) {
PdxInputStream dis;
if (in instanceof PdxInputStream) {
dis = new PdxInstanceInputStream((PdxInputStream) in, len);
try {
int bytesSkipped = in.skipBytes(len);
int bytesRemaining = len - bytesSkipped;
while (bytesRemaining > 0) {
in.readByte();
bytesRemaining--;
}
} catch (IOException ex) {
throw new PdxSerializationException("Could not deserialize PDX", ex);
}
} else {
byte[] bytes = new byte[len];
try {
in.readFully(bytes);
} catch (IOException ex) {
throw new PdxSerializationException("Could not deserialize PDX", ex);
}
dis = new PdxInstanceInputStream(bytes);
}
return dis;
}
public static boolean getPdxReadSerialized() {
return pdxGetObjectInProgress.get() == null;
}
public static void setPdxReadSerialized(boolean readSerialized) {
if (!readSerialized) {
pdxGetObjectInProgress.set(true);
} else {
pdxGetObjectInProgress.remove();
}
}
public Object getField(String fieldName) {
return getUnmodifiableReader(fieldName).readField(fieldName);
}
private PdxWriterImpl convertToTypeWithNoDeletedFields(PdxReaderImpl ur) {
PdxOutputStream os = new PdxOutputStream();
PdxType pt = new PdxType(ur.getPdxType().getClassName(), !ur.getPdxType().getNoDomainClass());
GemFireCacheImpl gfc = GemFireCacheImpl.getForPdx("PDX registry is unavailable because the Cache has been closed.");
TypeRegistry tr = gfc.getPdxRegistry();
PdxWriterImpl writer = new PdxWriterImpl(pt, tr, os);
for (PdxField field: pt.getFields()) {
if (!field.isDeleted()) {
writer.writeRawField(field, ur.getRaw(field));
}
}
writer.completeByteStreamGeneration();
return writer;
}
// Sendable implementation
public void sendTo(DataOutput out) throws IOException {
PdxReaderImpl ur = getUnmodifiableReader();
if (ur.getPdxType().getHasDeletedField()) {
PdxWriterImpl writer = convertToTypeWithNoDeletedFields(ur);
writer.sendTo(out);
} else {
out.write(DSCODE.PDX);
out.writeInt(ur.basicSize());
out.writeInt(ur.getPdxType().getTypeId());
ur.basicSendTo(out);
}
}
public byte[] toBytes() {
PdxReaderImpl ur = getUnmodifiableReader();
if (ur.getPdxType().getHasDeletedField()) {
PdxWriterImpl writer = convertToTypeWithNoDeletedFields(ur);
return writer.toByteArray();
} else {
byte[] result = new byte[PdxWriterImpl.HEADER_SIZE + ur.basicSize()];
ByteBuffer bb = ByteBuffer.wrap(result);
bb.put(DSCODE.PDX);
bb.putInt(ur.basicSize());
bb.putInt(ur.getPdxType().getTypeId());
ur.basicSendTo(bb);
return result;
}
}
// this is for internal use of the query engine.
public Object getCachedObject() {
Object result = this.cachedObjectForm;
if (result == null) {
result = getObject();
this.cachedObjectForm = result;
}
return result;
}
private String extractTypeMetaData(){
Object type = this.getField("@type");
if (type != null) {
if (type instanceof String) {
return (String)type;
}else {
throw new PdxSerializationException("Could not deserialize as invalid className found");
}
}else {
return null;
}
}
@Override
public Object getObject() {
if (getPdxType().getNoDomainClass()) {
//In case of Developer Rest APIs, All PdxInstances converted from Json will have a className =__GEMFIRE_JSON.
//Following code added to convert Json/PdxInstance into the Java object.
if(this.getClassName().equals("__GEMFIRE_JSON")){
//introspect the JSON, does the @type meta-data exist.
String className = extractTypeMetaData();
if(StringUtils.hasText(className)) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("MM/dd/yyyy"));
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
String JSON = JSONFormatter.toJSON(this);
Object classInstance = mapper.readValue(JSON, ClassPathLoader.getLatest().forName(className));
return classInstance;
}catch(Exception e){
throw new PdxSerializationException("Could not deserialize as java class type could not resolved", e);
}
}
}
return this;
}
boolean wouldReadSerialized = PdxInstanceImpl.getPdxReadSerialized();
if (!wouldReadSerialized) {
return getUnmodifiableReader().basicGetObject();
} else {
PdxInstanceImpl.setPdxReadSerialized(false);
try {
return getUnmodifiableReader().basicGetObject();
} finally {
PdxInstanceImpl.setPdxReadSerialized(true);
}
}
}
@Override
public int hashCode() {
if (this.cachedHashCode != UNUSED_HASH_CODE) {
// Already computed.
return this.cachedHashCode;
}
PdxReaderImpl ur = getUnmodifiableReader();
// Compute hash code.
Collection<PdxField> fields = ur.getPdxType().getSortedIdentityFields();
int hashCode = 1;
for(PdxField ft: fields) {
switch (ft.getFieldType()) {
case CHAR:
case BOOLEAN:
case BYTE:
case SHORT:
case INT:
case LONG:
case DATE:
case FLOAT:
case DOUBLE:
case STRING:
case BOOLEAN_ARRAY:
case CHAR_ARRAY:
case BYTE_ARRAY:
case SHORT_ARRAY:
case INT_ARRAY:
case LONG_ARRAY:
case FLOAT_ARRAY:
case DOUBLE_ARRAY:
case STRING_ARRAY:
case ARRAY_OF_BYTE_ARRAYS: {
ByteSource buffer = ur.getRaw(ft);
if (!buffer.equals(ByteSourceFactory.create(ft.getFieldType().getDefaultBytes()))) {
hashCode = hashCode *31 + buffer.hashCode();
}
break;
}
case OBJECT_ARRAY: {
Object[] oArray = ur.readObjectArray(ft);
if (oArray != null) {
// default value of null does not modify hashCode.
hashCode = hashCode *31 + Arrays.deepHashCode(oArray);
}
break;
}
case OBJECT: {
//TODO - we might able to optimize these to not deserialize the object
Object objectValue = ur.readObject(ft);
if (objectValue == null) {
// default value of null does not modify hashCode.
} else if (objectValue.getClass().isArray()) {
Class<?> myComponentType = objectValue.getClass().getComponentType();
if (myComponentType.isPrimitive()) {
ByteSource buffer = getRaw(ft);
hashCode = hashCode *31 + buffer.hashCode();
} else {
hashCode = hashCode *31 + Arrays.deepHashCode((Object[])objectValue);
}
} else {
hashCode = hashCode *31 + objectValue.hashCode();
}
break;
}
default:
throw new InternalGemFireException("Unhandled field type " + ft.getFieldType());
}
}
int result = (hashCode == UNUSED_HASH_CODE)?(hashCode + 1):hashCode;
this.cachedHashCode = result;
return result;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if(obj == null) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#0 o1=<" + this + "> o2=<" + obj + ">");
return false;
}
if (!(obj instanceof PdxInstanceImpl)) {
// if (!result) {
// GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#1 o1=<" + this + "> o2=<" + obj + ">");
// }
return false;
}
final PdxInstanceImpl other = (PdxInstanceImpl) obj;
PdxReaderImpl ur2 = other.getUnmodifiableReader();
PdxReaderImpl ur1 = getUnmodifiableReader();
if(!ur1.getPdxType().getClassName().equals(ur2.getPdxType().getClassName())) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#2 o1=<" + this + "> o2=<" + obj + ">");
return false;
}
SortedSet<PdxField> myFields = ur1.getPdxType().getSortedIdentityFields();
SortedSet<PdxField> otherFields = ur2.getPdxType().getSortedIdentityFields();
if (!myFields.equals(otherFields)) {
// It is not ok to modify myFields and otherFields in place so make copies
myFields = new TreeSet<PdxField>(myFields);
otherFields = new TreeSet<PdxField>(otherFields);
addDefaultFields(myFields, otherFields);
addDefaultFields(otherFields, myFields);
}
Iterator<PdxField> myFieldIterator = myFields.iterator();
Iterator<PdxField> otherFieldIterator = otherFields.iterator();
while(myFieldIterator.hasNext()) {
PdxField myType = myFieldIterator.next();
PdxField otherType = otherFieldIterator.next();
switch (myType.getFieldType()) {
case CHAR:
case BOOLEAN:
case BYTE:
case SHORT:
case INT:
case LONG:
case DATE:
case FLOAT:
case DOUBLE:
case STRING:
case BOOLEAN_ARRAY:
case CHAR_ARRAY:
case BYTE_ARRAY:
case SHORT_ARRAY:
case INT_ARRAY:
case LONG_ARRAY:
case FLOAT_ARRAY:
case DOUBLE_ARRAY:
case STRING_ARRAY:
case ARRAY_OF_BYTE_ARRAYS: {
ByteSource myBuffer = ur1.getRaw(myType);
ByteSource otherBuffer = ur2.getRaw(otherType);
if(!myBuffer.equals(otherBuffer)) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#4 o1=<" + this + "> o2=<" + obj + ">");
return false;
}}
break;
case OBJECT_ARRAY: {
Object[] myArray = ur1.readObjectArray(myType);
Object[] otherArray = ur2.readObjectArray(otherType);
if(!Arrays.deepEquals(myArray, otherArray)) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#5 o1=<" + this + "> o2=<" + obj + ">");
return false;
}}
break;
case OBJECT: {
Object myObject = ur1.readObject(myType);
Object otherObject = ur2.readObject(otherType);
if(myObject != otherObject) {
if(myObject == null) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#6 o1=<" + this + "> o2=<" + obj + ">");
return false;
}
if(otherObject == null) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#7 o1=<" + this + "> o2=<" + obj + ">");
return false;
}
if (myObject.getClass().isArray()) { // for bug 42976
Class<?> myComponentType = myObject.getClass().getComponentType();
Class<?> otherComponentType = otherObject.getClass().getComponentType();
if (!myComponentType.equals(otherComponentType)) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#8 o1=<" + this + "> o2=<" + obj + ">");
return false;
}
if (myComponentType.isPrimitive()) {
ByteSource myBuffer = getRaw(myType);
ByteSource otherBuffer = other.getRaw(otherType);
if(!myBuffer.equals(otherBuffer)) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#9 o1=<" + this + "> o2=<" + obj + ">");
return false;
}
} else {
if (!Arrays.deepEquals((Object[])myObject, (Object[])otherObject)) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#10 o1=<" + this + "> o2=<" + obj + ">");
return false;
}
}
} else if (!myObject.equals(otherObject)) {
//GemFireCacheImpl.getInstance().getLogger().info("DEBUG equals#11 fn=" + myType.getFieldName() + " myFieldClass=" + myObject.getClass() + " otherFieldCLass=" + otherObject.getClass() + " o1=<" + this + "> o2=<" + obj + ">" + "myObj=<" + myObject + "> otherObj=<" + otherObject + ">");
return false;
}
}}
break;
default:
throw new InternalGemFireException("Unhandled field type " + myType.getFieldType());
}
}
return true;
}
/**
* Any fields that are in otherFields but not in myFields
* are added to myFields as defaults. When adding fields they
* are inserted in the natural sort order.
* Note: myFields may be modified by this call.
*/
private static void addDefaultFields(SortedSet<PdxField> myFields, SortedSet<PdxField> otherFields) {
for (PdxField f: otherFields) {
if (!myFields.contains(f)) {
myFields.add(new DefaultPdxField(f));
}
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
PdxReaderImpl ur = getUnmodifiableReader();
result.append("PDX[").append(ur.getPdxType().getTypeId()).append(",").append(ur.getPdxType().getClassName())
//.append(",limit=").append(this.dis.size())
.append("]{");
boolean firstElement = true;
for(PdxField fieldType : ur.getPdxType().getSortedIdentityFields()) {
if(firstElement) {firstElement= false;} else {result.append(", ");};
result.append(fieldType.getFieldName());
//result.append(':').append(fieldType.getTypeIdString()); // DEBUG
//result.append(':').append(getAbsolutePosition(fieldType)); // DEBUG
result.append("=");
try {
// TODO check to see if getField returned an array and if it did use Arrays.deepToString
result.append(ur.readField(fieldType.getFieldName()));
} catch (RuntimeException e) {
result.append(e);
}
}
result.append("}");
return result.toString();
}
public List<String> getFieldNames() {
return getPdxType().getFieldNames();
}
@Override
PdxUnreadData getReadUnreadFieldsCalled() {
return null;
}
protected void clearCachedState() {
this.cachedHashCode = UNUSED_HASH_CODE;
this.cachedObjectForm = null;
}
public WritablePdxInstance createWriter() {
if (isEnum()) {
throw new IllegalStateException("PdxInstances that are an enum can not be modified.");
}
return new WritablePdxInstanceImpl(getUnmodifiableReader());
}
protected PdxReaderImpl getUnmodifiableReader() {
return this;
}
protected PdxReaderImpl getUnmodifiableReader(String fieldName) {
return this;
}
// All PdxReaderImpl methods that might change the ByteBuffer position
// need to be synchronized so that they are done atomically.
// This fixes bug 43178.
// primitive read methods all use absolute read methods so they do not set position
// so no sync is needed. Fixed width fields all use absolute read methods.
@Override
public synchronized String readString(String fieldName) {
return super.readString(fieldName);
}
@Override
public synchronized Object readObject(String fieldName) {
return super.readObject(fieldName);
}
@Override
public
synchronized Object readObject(PdxField ft) {
return super.readObject(ft);
}
@Override
public synchronized char[] readCharArray(String fieldName) {
return super.readCharArray(fieldName);
}
@Override
public synchronized boolean[] readBooleanArray(String fieldName) {
return super.readBooleanArray(fieldName);
}
@Override
public synchronized byte[] readByteArray(String fieldName) {
return super.readByteArray(fieldName);
}
@Override
public synchronized short[] readShortArray(String fieldName) {
return super.readShortArray(fieldName);
}
@Override
public synchronized int[] readIntArray(String fieldName) {
return super.readIntArray(fieldName);
}
@Override
public synchronized long[] readLongArray(String fieldName) {
return super.readLongArray(fieldName);
}
@Override
public synchronized float[] readFloatArray(String fieldName) {
return super.readFloatArray(fieldName);
}
@Override
public synchronized double[] readDoubleArray(String fieldName) {
return super.readDoubleArray(fieldName);
}
@Override
public synchronized String[] readStringArray(String fieldName) {
return super.readStringArray(fieldName);
}
@Override
public synchronized Object[] readObjectArray(String fieldName) {
return super.readObjectArray(fieldName);
}
@Override
public synchronized Object[] readObjectArray(PdxField ft) {
return super.readObjectArray(ft);
}
@Override
public synchronized byte[][] readArrayOfByteArrays(String fieldName) {
return super.readArrayOfByteArrays(fieldName);
}
@Override
public synchronized Object readField(String fieldName) {
return super.readField(fieldName);
}
@Override
protected synchronized Object basicGetObject() {
DMStats stats = InternalDataSerializer.getDMStats(null);
long start = stats.startPdxInstanceDeserialization();
try {
return super.basicGetObject();
} finally {
stats.endPdxInstanceDeserialization(start);
}
}
// override getRaw to fix bug 43569
@Override
protected synchronized ByteSource getRaw(PdxField ft) {
return super.getRaw(ft);
}
@Override
protected synchronized void basicSendTo(DataOutput out) throws IOException {
super.basicSendTo(out);
}
@Override
protected synchronized void basicSendTo(ByteBuffer bb) {
super.basicSendTo(bb);
}
public String getClassName() {
return getPdxType().getClassName();
}
public boolean isEnum() {
return false;
}
public Object getRawField(String fieldName){
return getUnmodifiableReader(fieldName).readRawField(fieldName);
}
public Object getDefaultValueIfFieldExistsInAnyPdxVersions(String fieldName,
String className) throws FieldNotFoundInPdxVersion {
PdxType pdxType = GemFireCacheImpl
.getForPdx(
"PDX registry is unavailable because the Cache has been closed.")
.getPdxRegistry().getPdxTypeForField(fieldName, className);
if (pdxType == null) {
throw new FieldNotFoundInPdxVersion("PdxType with field "
+ fieldName + " is not found for class " + className);
}
return pdxType.getPdxField(fieldName).getFieldType().getDefaultValue();
}
}