blob: 2dda9eb4f012530612d93b1a6e91f15e77976fff [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.ignite.internal.portable;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.binary.BinaryIdMapper;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.binary.BinarySerializer;
import org.apache.ignite.binary.Binarylizable;
import org.apache.ignite.internal.processors.cache.CacheObjectImpl;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.marshaller.MarshallerExclusions;
import org.apache.ignite.marshaller.optimized.OptimizedMarshaller;
import org.jetbrains.annotations.Nullable;
import static java.lang.reflect.Modifier.isStatic;
import static java.lang.reflect.Modifier.isTransient;
/**
* Portable class descriptor.
*/
public class PortableClassDescriptor {
/** */
private final PortableContext ctx;
/** */
private final Class<?> cls;
/** */
private final BinarySerializer serializer;
/** ID mapper. */
private final BinaryIdMapper idMapper;
/** */
private final BinaryWriteMode mode;
/** */
private final boolean userType;
/** */
private final int typeId;
/** */
private final String typeName;
/** Affinity key field name. */
private final String affKeyFieldName;
/** */
private final Constructor<?> ctor;
/** */
private final BinaryFieldAccessor[] fields;
/** */
private final Method writeReplaceMtd;
/** */
private final Method readResolveMtd;
/** */
private final Map<String, Integer> stableFieldsMeta;
/** Object schemas. Initialized only for serializable classes and contains only 1 entry. */
private final PortableSchema stableSchema;
/** Schema registry. */
private final PortableSchemaRegistry schemaReg;
/** */
private final boolean registered;
/** */
private final boolean useOptMarshaller;
/** */
private final boolean excluded;
/**
* @param ctx Context.
* @param cls Class.
* @param userType User type flag.
* @param typeId Type ID.
* @param typeName Type name.
* @param affKeyFieldName Affinity key field name.
* @param idMapper ID mapper.
* @param serializer Serializer.
* @param metaDataEnabled Metadata enabled flag.
* @param registered Whether typeId has been successfully registered by MarshallerContext or not.
* @param predefined Whether the class is predefined or not.
* @throws BinaryObjectException In case of error.
*/
PortableClassDescriptor(
PortableContext ctx,
Class<?> cls,
boolean userType,
int typeId,
String typeName,
@Nullable String affKeyFieldName,
@Nullable BinaryIdMapper idMapper,
@Nullable BinarySerializer serializer,
boolean metaDataEnabled,
boolean registered,
boolean predefined
) throws BinaryObjectException {
assert ctx != null;
assert cls != null;
assert idMapper != null;
this.ctx = ctx;
this.cls = cls;
this.typeId = typeId;
this.userType = userType;
this.typeName = typeName;
this.affKeyFieldName = affKeyFieldName;
this.serializer = serializer;
this.idMapper = idMapper;
this.registered = registered;
schemaReg = ctx.schemaRegistry(typeId);
excluded = MarshallerExclusions.isExcluded(cls);
useOptMarshaller = !predefined && initUseOptimizedMarshallerFlag();
if (excluded)
mode = BinaryWriteMode.EXCLUSION;
else {
if (cls == BinaryEnumObjectImpl.class)
mode = BinaryWriteMode.PORTABLE_ENUM;
else
mode = serializer != null ? BinaryWriteMode.PORTABLE : PortableUtils.mode(cls);
}
switch (mode) {
case P_BYTE:
case P_BOOLEAN:
case P_SHORT:
case P_CHAR:
case P_INT:
case P_LONG:
case P_FLOAT:
case P_DOUBLE:
case BYTE:
case SHORT:
case INT:
case LONG:
case FLOAT:
case DOUBLE:
case CHAR:
case BOOLEAN:
case DECIMAL:
case STRING:
case UUID:
case DATE:
case TIMESTAMP:
case BYTE_ARR:
case SHORT_ARR:
case INT_ARR:
case LONG_ARR:
case FLOAT_ARR:
case DOUBLE_ARR:
case CHAR_ARR:
case BOOLEAN_ARR:
case DECIMAL_ARR:
case STRING_ARR:
case UUID_ARR:
case DATE_ARR:
case TIMESTAMP_ARR:
case OBJECT_ARR:
case COL:
case MAP:
case PORTABLE_OBJ:
case ENUM:
case PORTABLE_ENUM:
case ENUM_ARR:
case CLASS:
case EXCLUSION:
ctor = null;
fields = null;
stableFieldsMeta = null;
stableSchema = null;
break;
case PORTABLE:
case EXTERNALIZABLE:
ctor = constructor(cls);
fields = null;
stableFieldsMeta = null;
stableSchema = null;
break;
case OBJECT:
ctor = constructor(cls);
ArrayList<BinaryFieldAccessor> fields0 = new ArrayList<>();
stableFieldsMeta = metaDataEnabled ? new HashMap<String, Integer>() : null;
PortableSchema.Builder schemaBuilder = PortableSchema.Builder.newBuilder();
Collection<String> names = new HashSet<>();
Collection<Integer> ids = new HashSet<>();
for (Class<?> c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
for (Field f : c.getDeclaredFields()) {
int mod = f.getModifiers();
if (!isStatic(mod) && !isTransient(mod)) {
f.setAccessible(true);
String name = f.getName();
if (!names.add(name))
throw new BinaryObjectException("Duplicate field name [fieldName=" + name +
", cls=" + cls.getName() + ']');
int fieldId = idMapper.fieldId(typeId, name);
if (!ids.add(fieldId))
throw new BinaryObjectException("Duplicate field ID: " + name);
BinaryFieldAccessor fieldInfo = BinaryFieldAccessor.create(f, fieldId);
fields0.add(fieldInfo);
schemaBuilder.addField(fieldId);
if (metaDataEnabled)
stableFieldsMeta.put(name, fieldInfo.mode().typeId());
}
}
}
fields = fields0.toArray(new BinaryFieldAccessor[fields0.size()]);
stableSchema = schemaBuilder.build();
break;
default:
// Should never happen.
throw new BinaryObjectException("Invalid mode: " + mode);
}
if (mode == BinaryWriteMode.PORTABLE || mode == BinaryWriteMode.EXTERNALIZABLE ||
mode == BinaryWriteMode.OBJECT) {
readResolveMtd = U.findNonPublicMethod(cls, "readResolve");
writeReplaceMtd = U.findNonPublicMethod(cls, "writeReplace");
}
else {
readResolveMtd = null;
writeReplaceMtd = null;
}
}
/**
* @return {@code True} if enum.
*/
boolean isEnum() {
return mode == BinaryWriteMode.ENUM;
}
/**
* @return Described class.
*/
Class<?> describedClass() {
return cls;
}
/**
* @return Type ID.
*/
public int typeId() {
return typeId;
}
/**
* @return User type flag.
*/
public boolean userType() {
return userType;
}
/**
* @return Fields meta data.
*/
Map<String, Integer> fieldsMeta() {
return stableFieldsMeta;
}
/**
* @return Schema.
*/
PortableSchema schema() {
return stableSchema;
}
/**
* @return Whether typeId has been successfully registered by MarshallerContext or not.
*/
public boolean registered() {
return registered;
}
/**
* @return {@code true} if {@link OptimizedMarshaller} must be used instead of {@link BinaryMarshaller}
* for object serialization and deserialization.
*/
public boolean useOptimizedMarshaller() {
return useOptMarshaller;
}
/**
* Checks whether the class values are explicitly excluded from marshalling.
*
* @return {@code true} if excluded, {@code false} otherwise.
*/
public boolean excluded() {
return excluded;
}
/**
* @return portableWriteReplace() method
*/
@Nullable Method getWriteReplaceMethod() {
return writeReplaceMtd;
}
/**
* @return portableReadResolve() method
*/
@SuppressWarnings("UnusedDeclaration")
@Nullable Method getReadResolveMethod() {
return readResolveMtd;
}
/**
* @param obj Object.
* @param writer Writer.
* @throws BinaryObjectException In case of error.
*/
void write(Object obj, BinaryWriterExImpl writer) throws BinaryObjectException {
assert obj != null;
assert writer != null;
writer.typeId(typeId);
switch (mode) {
case P_BYTE:
case BYTE:
writer.writeByteFieldPrimitive((byte) obj);
break;
case P_SHORT:
case SHORT:
writer.writeShortFieldPrimitive((short)obj);
break;
case P_INT:
case INT:
writer.writeIntFieldPrimitive((int) obj);
break;
case P_LONG:
case LONG:
writer.writeLongFieldPrimitive((long) obj);
break;
case P_FLOAT:
case FLOAT:
writer.writeFloatFieldPrimitive((float) obj);
break;
case P_DOUBLE:
case DOUBLE:
writer.writeDoubleFieldPrimitive((double) obj);
break;
case P_CHAR:
case CHAR:
writer.writeCharFieldPrimitive((char) obj);
break;
case P_BOOLEAN:
case BOOLEAN:
writer.writeBooleanFieldPrimitive((boolean) obj);
break;
case DECIMAL:
writer.doWriteDecimal((BigDecimal)obj);
break;
case STRING:
writer.doWriteString((String)obj);
break;
case UUID:
writer.doWriteUuid((UUID)obj);
break;
case DATE:
writer.doWriteDate((Date)obj);
break;
case TIMESTAMP:
writer.doWriteTimestamp((Timestamp)obj);
break;
case BYTE_ARR:
writer.doWriteByteArray((byte[])obj);
break;
case SHORT_ARR:
writer.doWriteShortArray((short[]) obj);
break;
case INT_ARR:
writer.doWriteIntArray((int[]) obj);
break;
case LONG_ARR:
writer.doWriteLongArray((long[]) obj);
break;
case FLOAT_ARR:
writer.doWriteFloatArray((float[]) obj);
break;
case DOUBLE_ARR:
writer.doWriteDoubleArray((double[]) obj);
break;
case CHAR_ARR:
writer.doWriteCharArray((char[]) obj);
break;
case BOOLEAN_ARR:
writer.doWriteBooleanArray((boolean[]) obj);
break;
case DECIMAL_ARR:
writer.doWriteDecimalArray((BigDecimal[]) obj);
break;
case STRING_ARR:
writer.doWriteStringArray((String[]) obj);
break;
case UUID_ARR:
writer.doWriteUuidArray((UUID[]) obj);
break;
case DATE_ARR:
writer.doWriteDateArray((Date[]) obj);
break;
case TIMESTAMP_ARR:
writer.doWriteTimestampArray((Timestamp[]) obj);
break;
case OBJECT_ARR:
writer.doWriteObjectArray((Object[])obj);
break;
case COL:
writer.doWriteCollection((Collection<?>)obj);
break;
case MAP:
writer.doWriteMap((Map<?, ?>)obj);
break;
case ENUM:
writer.doWriteEnum((Enum<?>)obj);
break;
case PORTABLE_ENUM:
writer.doWritePortableEnum((BinaryEnumObjectImpl)obj);
break;
case ENUM_ARR:
writer.doWriteEnumArray((Object[])obj);
break;
case CLASS:
writer.doWriteClass((Class)obj);
break;
case PORTABLE_OBJ:
writer.doWritePortableObject((BinaryObjectImpl)obj);
break;
case PORTABLE:
if (preWrite(writer, obj)) {
try {
if (serializer != null)
serializer.writeBinary(obj, writer);
else
((Binarylizable)obj).writeBinary(writer);
postWrite(writer, obj);
// Check whether we need to update metadata.
if (obj.getClass() != BinaryMetadata.class) {
int schemaId = writer.schemaId();
if (schemaReg.schema(schemaId) == null) {
// This is new schema, let's update metadata.
BinaryMetadataCollector collector =
new BinaryMetadataCollector(typeId, typeName, idMapper);
if (serializer != null)
serializer.writeBinary(obj, collector);
else
((Binarylizable)obj).writeBinary(collector);
PortableSchema newSchema = collector.schema();
BinaryMetadata meta = new BinaryMetadata(typeId, typeName, collector.meta(),
affKeyFieldName, Collections.singleton(newSchema), false);
ctx.updateMetadata(typeId, meta);
schemaReg.addSchema(newSchema.schemaId(), newSchema);
}
}
}
finally {
writer.popSchema();
}
}
break;
case EXTERNALIZABLE:
if (preWrite(writer, obj)) {
writer.rawWriter();
try {
((Externalizable)obj).writeExternal(writer);
postWrite(writer, obj);
}
catch (IOException e) {
throw new BinaryObjectException("Failed to write Externalizable object: " + obj, e);
}
finally {
writer.popSchema();
}
}
break;
case OBJECT:
if (preWrite(writer, obj)) {
try {
for (BinaryFieldAccessor info : fields)
info.write(obj, writer);
writer.schemaId(stableSchema.schemaId());
postWrite(writer, obj);
}
finally {
writer.popSchema();
}
}
break;
default:
assert false : "Invalid mode: " + mode;
}
}
/**
* @param reader Reader.
* @return Object.
* @throws BinaryObjectException If failed.
*/
Object read(BinaryReaderExImpl reader) throws BinaryObjectException {
assert reader != null;
Object res;
switch (mode) {
case PORTABLE:
res = newInstance();
reader.setHandle(res);
if (serializer != null)
serializer.readBinary(res, reader);
else
((Binarylizable)res).readBinary(reader);
break;
case EXTERNALIZABLE:
res = newInstance();
reader.setHandle(res);
try {
((Externalizable)res).readExternal(reader);
}
catch (IOException | ClassNotFoundException e) {
throw new BinaryObjectException("Failed to read Externalizable object: " +
res.getClass().getName(), e);
}
break;
case OBJECT:
res = newInstance();
reader.setHandle(res);
for (BinaryFieldAccessor info : fields)
info.read(res, reader);
break;
default:
assert false : "Invalid mode: " + mode;
return null;
}
if (readResolveMtd != null) {
try {
res = readResolveMtd.invoke(res);
reader.setHandle(res);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
if (e.getTargetException() instanceof BinaryObjectException)
throw (BinaryObjectException)e.getTargetException();
throw new BinaryObjectException("Failed to execute readResolve() method on " + res, e);
}
}
return res;
}
/**
* Pre-write phase.
*
* @param writer Writer.
* @param obj Object.
* @return Whether further write is needed.
*/
private boolean preWrite(BinaryWriterExImpl writer, Object obj) {
if (writer.tryWriteAsHandle(obj))
return false;
writer.preWrite(registered ? null : cls.getName());
return true;
}
/**
* Post-write phase.
*
* @param writer Writer.
* @param obj Object.
*/
private void postWrite(BinaryWriterExImpl writer, Object obj) {
writer.postWrite(userType, registered, obj instanceof CacheObjectImpl ? 0 : obj.hashCode());
}
/**
* @return Instance.
* @throws BinaryObjectException In case of error.
*/
private Object newInstance() throws BinaryObjectException {
assert ctor != null;
try {
return ctor.newInstance();
}
catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
throw new BinaryObjectException("Failed to instantiate instance: " + cls, e);
}
}
/**
* @param cls Class.
* @return Constructor.
* @throws BinaryObjectException If constructor doesn't exist.
*/
@SuppressWarnings("ConstantConditions")
@Nullable private static Constructor<?> constructor(Class<?> cls) throws BinaryObjectException {
assert cls != null;
try {
Constructor<?> ctor = U.forceEmptyConstructor(cls);
if (ctor == null)
throw new BinaryObjectException("Failed to find empty constructor for class: " + cls.getName());
ctor.setAccessible(true);
return ctor;
}
catch (IgniteCheckedException e) {
throw new BinaryObjectException("Failed to get constructor for class: " + cls.getName(), e);
}
}
/**
* Determines whether to use {@link OptimizedMarshaller} for serialization or
* not.
*
* @return {@code true} if to use, {@code false} otherwise.
*/
@SuppressWarnings("unchecked")
private boolean initUseOptimizedMarshallerFlag() {
for (Class c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
try {
Method writeObj = c.getDeclaredMethod("writeObject", ObjectOutputStream.class);
Method readObj = c.getDeclaredMethod("readObject", ObjectInputStream.class);
if (!Modifier.isStatic(writeObj.getModifiers()) && !Modifier.isStatic(readObj.getModifiers()) &&
writeObj.getReturnType() == void.class && readObj.getReturnType() == void.class)
return true;
}
catch (NoSuchMethodException e) {
// No-op.
}
}
return false;
}
}