| /* |
| * 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.DSCODE; |
| 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.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(); |
| } |
| } |