blob: 24f07c7c3d8554795bf81c652b3f2c8d89e1690f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.geode.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 com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.apache.geode.InternalGemFireException;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.distributed.internal.DMStats;
import org.apache.geode.internal.ClassPathLoader;
import org.apache.geode.internal.InternalDataSerializer;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.serialization.DSCODE;
import org.apache.geode.internal.tcp.ByteBufferInputStream;
import org.apache.geode.internal.tcp.ByteBufferInputStream.ByteSource;
import org.apache.geode.internal.tcp.ByteBufferInputStream.ByteSourceFactory;
import org.apache.geode.internal.util.Hex;
import org.apache.geode.pdx.JSONFormatter;
import org.apache.geode.pdx.PdxSerializationException;
import org.apache.geode.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.
* <p>
* We do not use this normal java io serialization when serializing this class in GemFire because
* Sendable takes precedence over Serializable.
*/
public class PdxInstanceImpl extends PdxReaderImpl implements InternalPdxInstance {
private static final long serialVersionUID = -1669268527103938431L;
private static final boolean USE_STATIC_MAPPER =
Boolean.getBoolean("PdxInstance.use-static-mapper");
@Immutable
private static final ObjectMapper mapper = USE_STATIC_MAPPER ? createObjectMapper() : null;
private static ObjectMapper createObjectMapper() {
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);
return mapper;
}
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 PdxInputStream((ByteBufferInputStream) 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 PdxInputStream(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();
}
}
@Override
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());
InternalCache cache = GemFireCacheImpl
.getForPdx("PDX registry is unavailable because the Cache has been closed.");
TypeRegistry tr = cache.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;
}
@Override
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.toByte());
out.writeInt(ur.basicSize());
out.writeInt(ur.getPdxType().getTypeId());
ur.basicSendTo(out);
}
}
@Override
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.toByte());
bb.putInt(ur.basicSize());
bb.putInt(ur.getPdxType().getTypeId());
ur.basicSendTo(bb);
return result;
}
}
@Override
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(JSONFormatter.JSON_CLASSNAME)) {
// introspect the JSON, does the @type meta-data exist.
String className = extractTypeMetaData();
if (StringUtils.isNotBlank(className)) {
try {
String JSON = JSONFormatter.toJSON(this);
ObjectMapper objMapper = USE_STATIC_MAPPER ? mapper : createObjectMapper();
Object classInstance =
objMapper.readValue(JSON, ClassPathLoader.getLatest().forName(className));
return classInstance;
} catch (Exception e) {
throw new PdxSerializationException(
"Could not deserialize as java class '" + className + "' could not be 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: {
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) {
return false;
}
if (!(obj instanceof PdxInstanceImpl)) {
return false;
}
final PdxInstanceImpl other = (PdxInstanceImpl) obj;
PdxReaderImpl ur2 = other.getUnmodifiableReader();
PdxReaderImpl ur1 = getUnmodifiableReader();
if (!ur1.getPdxType().getClassName().equals(ur2.getPdxType().getClassName())) {
return false;
}
SortedSet<PdxField> myFields = ur1.getPdxType().getSortedIdentityFields();
SortedSet<PdxField> otherFields = ur2.getPdxType().getSortedIdentityFields();
if (!myFields.equals(otherFields)) {
if (ur1.getPdxType().getClassName().isEmpty()) {
return false;
}
// 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)) {
return false;
}
}
break;
case OBJECT_ARRAY: {
Object[] myArray = ur1.readObjectArray(myType);
Object[] otherArray = ur2.readObjectArray(otherType);
if (!Arrays.deepEquals(myArray, otherArray)) {
return false;
}
}
break;
case OBJECT: {
Object myObject = ur1.readObject(myType);
Object otherObject = ur2.readObject(otherType);
if (myObject != otherObject) {
if (myObject == null) {
return false;
}
if (otherObject == null) {
return false;
}
if (myObject.getClass().isArray()) { // for bug 42976
Class<?> myComponentType = myObject.getClass().getComponentType();
Class<?> otherComponentType = otherObject.getClass().getComponentType();
if (!myComponentType.equals(otherComponentType)) {
return false;
}
if (myComponentType.isPrimitive()) {
ByteSource myBuffer = getRaw(myType);
ByteSource otherBuffer = other.getRaw(otherType);
if (!myBuffer.equals(otherBuffer)) {
return false;
}
} else {
if (!Arrays.deepEquals((Object[]) myObject, (Object[]) otherObject)) {
return false;
}
}
} else if (!myObject.equals(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("]{");
boolean firstElement = true;
for (PdxField fieldType : ur.getPdxType().getSortedIdentityFields()) {
if (firstElement) {
firstElement = false;
} else {
result.append(", ");
} ;
result.append(fieldType.getFieldName());
result.append("=");
try {
final Object value = ur.readField(fieldType.getFieldName());
if (value instanceof byte[]) {
result.append(Hex.toHex((byte[]) value));
} else if (value.getClass().isArray()) {
if (value instanceof short[]) {
result.append(Arrays.toString((short[]) value));
} else if (value instanceof int[]) {
result.append(Arrays.toString((int[]) value));
} else if (value instanceof long[]) {
result.append(Arrays.toString((long[]) value));
} else if (value instanceof char[]) {
result.append(Arrays.toString((char[]) value));
} else if (value instanceof float[]) {
result.append(Arrays.toString((float[]) value));
} else if (value instanceof double[]) {
result.append(Arrays.toString((double[]) value));
} else if (value instanceof boolean[]) {
result.append(Arrays.toString((boolean[]) value));
} else {
result.append(Arrays.deepToString((Object[]) value));
}
} else {
result.append(value);
}
} catch (RuntimeException e) {
result.append(e);
}
}
result.append("}");
return result.toString();
}
@Override
public List<String> getFieldNames() {
return getPdxType().getFieldNames();
}
@Override
PdxUnreadData getReadUnreadFieldsCalled() {
return null;
}
protected void clearCachedState() {
this.cachedHashCode = UNUSED_HASH_CODE;
this.cachedObjectForm = null;
}
@Override
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);
}
@Override
public String getClassName() {
return getPdxType().getClassName();
}
@Override
public boolean isEnum() {
return false;
}
@Override
public Object getRawField(String fieldName) {
return getUnmodifiableReader(fieldName).readRawField(fieldName);
}
@Override
public boolean isDeserializable() {
if (this.getClassName().equals(JSONFormatter.JSON_CLASSNAME)) {
return true;
}
return !getPdxType().getNoDomainClass();
}
}