http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java new file mode 100644 index 0000000..9b4d444 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableClassDescriptor.java @@ -0,0 +1,813 @@ +/* + * 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.binary; + +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.GridUnsafe; +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 sun.misc.Unsafe; + +import static java.lang.reflect.Modifier.isStatic; +import static java.lang.reflect.Modifier.isTransient; + +/** + * Portable class descriptor. + */ +public class PortableClassDescriptor { + /** */ + public static final Unsafe UNSAFE = GridUnsafe.unsafe(); + + /** */ + 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: + // Must not use constructor to honor transient fields semantics. + ctor = null; + 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 { + try { + return ctor != null ? ctor.newInstance() : UNSAFE.allocateInstance(cls); + } + 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 ignored) { + // No-op. + } + } + + return false; + } +}
http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java new file mode 100644 index 0000000..f7375a4 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableContext.java @@ -0,0 +1,1102 @@ +/* + * 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.binary; + +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.binary.BinaryIdMapper; +import org.apache.ignite.binary.BinaryInvalidTypeException; +import org.apache.ignite.binary.BinaryObjectException; +import org.apache.ignite.binary.BinarySerializer; +import org.apache.ignite.binary.BinaryType; +import org.apache.ignite.binary.BinaryTypeConfiguration; +import org.apache.ignite.cache.CacheKeyConfiguration; +import org.apache.ignite.cache.affinity.AffinityKeyMapped; +import org.apache.ignite.configuration.BinaryConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteKernal; +import org.apache.ignite.internal.IgnitionEx; +import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl; +import org.apache.ignite.internal.processors.datastructures.CollocatedQueueItemKey; +import org.apache.ignite.internal.processors.datastructures.CollocatedSetItemKey; +import org.apache.ignite.internal.util.IgniteUtils; +import org.apache.ignite.internal.util.lang.GridMapEntry; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.T2; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteBiTuple; +import org.apache.ignite.marshaller.MarshallerContext; +import org.apache.ignite.marshaller.optimized.OptimizedMarshaller; +import org.jetbrains.annotations.Nullable; +import org.jsr166.ConcurrentHashMap8; + +import java.io.Externalizable; +import java.io.File; +import java.io.IOException; +import java.io.InvalidObjectException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.ObjectStreamException; +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * Portable context. + */ +public class PortableContext implements Externalizable { + /** */ + private static final long serialVersionUID = 0L; + + /** */ + private static final ClassLoader dfltLdr = U.gridClassLoader(); + + /** */ + private final ConcurrentMap<Class<?>, PortableClassDescriptor> descByCls = new ConcurrentHashMap8<>(); + + /** Holds classes loaded by default class loader only. */ + private final ConcurrentMap<Integer, PortableClassDescriptor> userTypes = new ConcurrentHashMap8<>(); + + /** */ + private final Map<Integer, PortableClassDescriptor> predefinedTypes = new HashMap<>(); + + /** */ + private final Map<String, Integer> predefinedTypeNames = new HashMap<>(); + + /** */ + private final Map<Class<? extends Collection>, Byte> colTypes = new HashMap<>(); + + /** */ + private final Map<Class<? extends Map>, Byte> mapTypes = new HashMap<>(); + + /** */ + private final ConcurrentMap<Integer, BinaryIdMapper> mappers = new ConcurrentHashMap8<>(0); + + /** Affinity key field names. */ + private final ConcurrentMap<Integer, String> affKeyFieldNames = new ConcurrentHashMap8<>(0); + + /** */ + private final Map<String, BinaryIdMapper> typeMappers = new ConcurrentHashMap8<>(0); + + /** */ + private BinaryMetadataHandler metaHnd; + + /** Actual marshaller. */ + private BinaryMarshaller marsh; + + /** */ + private MarshallerContext marshCtx; + + /** */ + private String gridName; + + /** */ + private IgniteConfiguration igniteCfg; + + /** */ + private final OptimizedMarshaller optmMarsh = new OptimizedMarshaller(); + + /** Compact footer flag. */ + private boolean compactFooter; + + /** Object schemas. */ + private volatile Map<Integer, PortableSchemaRegistry> schemas; + + /** + * For {@link Externalizable}. + */ + public PortableContext() { + // No-op. + } + + /** + * @param metaHnd Meta data handler. + * @param igniteCfg Ignite configuration. + */ + public PortableContext(BinaryMetadataHandler metaHnd, IgniteConfiguration igniteCfg) { + assert metaHnd != null; + assert igniteCfg != null; + + this.metaHnd = metaHnd; + this.igniteCfg = igniteCfg; + + gridName = igniteCfg.getGridName(); + + colTypes.put(ArrayList.class, GridPortableMarshaller.ARR_LIST); + colTypes.put(LinkedList.class, GridPortableMarshaller.LINKED_LIST); + colTypes.put(HashSet.class, GridPortableMarshaller.HASH_SET); + colTypes.put(LinkedHashSet.class, GridPortableMarshaller.LINKED_HASH_SET); + + mapTypes.put(HashMap.class, GridPortableMarshaller.HASH_MAP); + mapTypes.put(LinkedHashMap.class, GridPortableMarshaller.LINKED_HASH_MAP); + + // IDs range from [0..200] is used by Java SDK API and GridGain legacy API + + registerPredefinedType(Byte.class, GridPortableMarshaller.BYTE); + registerPredefinedType(Boolean.class, GridPortableMarshaller.BOOLEAN); + registerPredefinedType(Short.class, GridPortableMarshaller.SHORT); + registerPredefinedType(Character.class, GridPortableMarshaller.CHAR); + registerPredefinedType(Integer.class, GridPortableMarshaller.INT); + registerPredefinedType(Long.class, GridPortableMarshaller.LONG); + registerPredefinedType(Float.class, GridPortableMarshaller.FLOAT); + registerPredefinedType(Double.class, GridPortableMarshaller.DOUBLE); + registerPredefinedType(String.class, GridPortableMarshaller.STRING); + registerPredefinedType(BigDecimal.class, GridPortableMarshaller.DECIMAL); + registerPredefinedType(Date.class, GridPortableMarshaller.DATE); + registerPredefinedType(Timestamp.class, GridPortableMarshaller.TIMESTAMP); + registerPredefinedType(UUID.class, GridPortableMarshaller.UUID); + + registerPredefinedType(byte[].class, GridPortableMarshaller.BYTE_ARR); + registerPredefinedType(short[].class, GridPortableMarshaller.SHORT_ARR); + registerPredefinedType(int[].class, GridPortableMarshaller.INT_ARR); + registerPredefinedType(long[].class, GridPortableMarshaller.LONG_ARR); + registerPredefinedType(float[].class, GridPortableMarshaller.FLOAT_ARR); + registerPredefinedType(double[].class, GridPortableMarshaller.DOUBLE_ARR); + registerPredefinedType(char[].class, GridPortableMarshaller.CHAR_ARR); + registerPredefinedType(boolean[].class, GridPortableMarshaller.BOOLEAN_ARR); + registerPredefinedType(BigDecimal[].class, GridPortableMarshaller.DECIMAL_ARR); + registerPredefinedType(String[].class, GridPortableMarshaller.STRING_ARR); + registerPredefinedType(UUID[].class, GridPortableMarshaller.UUID_ARR); + registerPredefinedType(Date[].class, GridPortableMarshaller.DATE_ARR); + registerPredefinedType(Timestamp[].class, GridPortableMarshaller.TIMESTAMP_ARR); + registerPredefinedType(Object[].class, GridPortableMarshaller.OBJ_ARR); + + registerPredefinedType(ArrayList.class, 0); + registerPredefinedType(LinkedList.class, 0); + registerPredefinedType(HashSet.class, 0); + registerPredefinedType(LinkedHashSet.class, 0); + + registerPredefinedType(HashMap.class, 0); + registerPredefinedType(LinkedHashMap.class, 0); + + registerPredefinedType(GridMapEntry.class, 60); + registerPredefinedType(IgniteBiTuple.class, 61); + registerPredefinedType(T2.class, 62); + + // IDs range [200..1000] is used by Ignite internal APIs. + } + + /** + * @return Marshaller. + */ + public BinaryMarshaller marshaller() { + return marsh; + } + + /** + * @return Ignite configuration. + */ + public IgniteConfiguration configuration(){ + return igniteCfg; + } + + /** + * @param marsh Portable marshaller. + * @param cfg Configuration. + * @throws BinaryObjectException In case of error. + */ + public void configure(BinaryMarshaller marsh, IgniteConfiguration cfg) throws BinaryObjectException { + if (marsh == null) + return; + + this.marsh = marsh; + + marshCtx = marsh.getContext(); + + BinaryConfiguration binaryCfg = cfg.getBinaryConfiguration(); + + if (binaryCfg == null) + binaryCfg = new BinaryConfiguration(); + + assert marshCtx != null; + + optmMarsh.setContext(marshCtx); + + configure( + binaryCfg.getIdMapper(), + binaryCfg.getSerializer(), + binaryCfg.getTypeConfigurations() + ); + + compactFooter = binaryCfg.isCompactFooter(); + } + + /** + * @param globalIdMapper ID mapper. + * @param globalSerializer Serializer. + * @param typeCfgs Type configurations. + * @throws BinaryObjectException In case of error. + */ + private void configure( + BinaryIdMapper globalIdMapper, + BinarySerializer globalSerializer, + Collection<BinaryTypeConfiguration> typeCfgs + ) throws BinaryObjectException { + TypeDescriptors descs = new TypeDescriptors(); + + Map<String, String> affFields = new HashMap<>(); + + if (!F.isEmpty(igniteCfg.getCacheKeyConfiguration())) { + for (CacheKeyConfiguration keyCfg : igniteCfg.getCacheKeyConfiguration()) + affFields.put(keyCfg.getTypeName(), keyCfg.getAffinityKeyFieldName()); + } + + if (typeCfgs != null) { + for (BinaryTypeConfiguration typeCfg : typeCfgs) { + String clsName = typeCfg.getTypeName(); + + if (clsName == null) + throw new BinaryObjectException("Class name is required for portable type configuration."); + + BinaryIdMapper idMapper = globalIdMapper; + + if (typeCfg.getIdMapper() != null) + idMapper = typeCfg.getIdMapper(); + + idMapper = BinaryInternalIdMapper.create(idMapper); + + BinarySerializer serializer = globalSerializer; + + if (typeCfg.getSerializer() != null) + serializer = typeCfg.getSerializer(); + + if (clsName.endsWith(".*")) { + String pkgName = clsName.substring(0, clsName.length() - 2); + + for (String clsName0 : classesInPackage(pkgName)) + descs.add(clsName0, idMapper, serializer, affFields.get(clsName0), + typeCfg.isEnum(), true); + } + else + descs.add(clsName, idMapper, serializer, affFields.get(clsName), + typeCfg.isEnum(), false); + } + } + + for (TypeDescriptor desc : descs.descriptors()) + registerUserType(desc.clsName, desc.idMapper, desc.serializer, desc.affKeyFieldName, desc.isEnum); + + BinaryInternalIdMapper dfltMapper = BinaryInternalIdMapper.create(globalIdMapper); + + // Put affinity field names for unconfigured types. + for (Map.Entry<String, String> entry : affFields.entrySet()) { + String typeName = entry.getKey(); + + int typeId = dfltMapper.typeId(typeName); + + affKeyFieldNames.putIfAbsent(typeId, entry.getValue()); + } + + addSystemClassAffinityKey(CollocatedSetItemKey.class); + addSystemClassAffinityKey(CollocatedQueueItemKey.class); + } + + /** + * @param cls Class. + */ + private void addSystemClassAffinityKey(Class<?> cls) { + String fieldName = affinityFieldName(cls); + + assert fieldName != null : cls; + + affKeyFieldNames.putIfAbsent(cls.getName().hashCode(), affinityFieldName(cls)); + } + + /** + * @param pkgName Package name. + * @return Class names. + */ + @SuppressWarnings("ConstantConditions") + private static Iterable<String> classesInPackage(String pkgName) { + assert pkgName != null; + + Collection<String> clsNames = new ArrayList<>(); + + ClassLoader ldr = U.gridClassLoader(); + + if (ldr instanceof URLClassLoader) { + String pkgPath = pkgName.replaceAll("\\.", "/"); + + URL[] urls = ((URLClassLoader)ldr).getURLs(); + + for (URL url : urls) { + String proto = url.getProtocol().toLowerCase(); + + if ("file".equals(proto)) { + try { + File cpElement = new File(url.toURI()); + + if (cpElement.isDirectory()) { + File pkgDir = new File(cpElement, pkgPath); + + if (pkgDir.isDirectory()) { + for (File file : pkgDir.listFiles()) { + String fileName = file.getName(); + + if (file.isFile() && fileName.toLowerCase().endsWith(".class")) + clsNames.add(pkgName + '.' + fileName.substring(0, fileName.length() - 6)); + } + } + } + else if (cpElement.isFile()) { + try { + JarFile jar = new JarFile(cpElement); + + Enumeration<JarEntry> entries = jar.entries(); + + while (entries.hasMoreElements()) { + String entry = entries.nextElement().getName(); + + if (entry.startsWith(pkgPath) && entry.endsWith(".class")) { + String clsName = entry.substring(pkgPath.length() + 1, entry.length() - 6); + + if (!clsName.contains("/") && !clsName.contains("\\")) + clsNames.add(pkgName + '.' + clsName); + } + } + } + catch (IOException ignored) { + // No-op. + } + } + } + catch (URISyntaxException ignored) { + // No-op. + } + } + } + } + + return clsNames; + } + + /** + * @param cls Class. + * @return Class descriptor. + * @throws BinaryObjectException In case of error. + */ + public PortableClassDescriptor descriptorForClass(Class<?> cls, boolean deserialize) + throws BinaryObjectException { + assert cls != null; + + PortableClassDescriptor desc = descByCls.get(cls); + + if (desc == null || !desc.registered()) + desc = registerClassDescriptor(cls, deserialize); + + return desc; + } + + /** + * @param userType User type or not. + * @param typeId Type ID. + * @param ldr Class loader. + * @return Class descriptor. + */ + public PortableClassDescriptor descriptorForTypeId( + boolean userType, + int typeId, + ClassLoader ldr, + boolean deserialize + ) { + assert typeId != GridPortableMarshaller.UNREGISTERED_TYPE_ID; + + //TODO: As a workaround for IGNITE-1358 we always check the predefined map before without checking 'userType' + PortableClassDescriptor desc = predefinedTypes.get(typeId); + + if (desc != null) + return desc; + + if (ldr == null) + ldr = dfltLdr; + + // If the type hasn't been loaded by default class loader then we mustn't return the descriptor from here + // giving a chance to a custom class loader to reload type's class. + if (userType && ldr.equals(dfltLdr)) { + desc = userTypes.get(typeId); + + if (desc != null) + return desc; + } + + Class cls; + + try { + cls = marshCtx.getClass(typeId, ldr); + + desc = descByCls.get(cls); + } + catch (ClassNotFoundException e) { + // Class might have been loaded by default class loader. + if (userType && !ldr.equals(dfltLdr) && (desc = descriptorForTypeId(true, typeId, dfltLdr, deserialize)) != null) + return desc; + + throw new BinaryInvalidTypeException(e); + } + catch (IgniteCheckedException e) { + // Class might have been loaded by default class loader. + if (userType && !ldr.equals(dfltLdr) && (desc = descriptorForTypeId(true, typeId, dfltLdr, deserialize)) != null) + return desc; + + throw new BinaryObjectException("Failed resolve class for ID: " + typeId, e); + } + + if (desc == null) { + desc = registerClassDescriptor(cls, deserialize); + + assert desc.typeId() == typeId; + } + + return desc; + } + + /** + * Creates and registers {@link PortableClassDescriptor} for the given {@code class}. + * + * @param cls Class. + * @return Class descriptor. + */ + private PortableClassDescriptor registerClassDescriptor(Class<?> cls, boolean deserialize) { + PortableClassDescriptor desc; + + String clsName = cls.getName(); + + if (marshCtx.isSystemType(clsName)) { + desc = new PortableClassDescriptor(this, + cls, + false, + clsName.hashCode(), + clsName, + null, + BinaryInternalIdMapper.defaultInstance(), + null, + false, + true, /* registered */ + false /* predefined */ + ); + + PortableClassDescriptor old = descByCls.putIfAbsent(cls, desc); + + if (old != null) + desc = old; + } + else + desc = registerUserClassDescriptor(cls, deserialize); + + return desc; + } + + /** + * Creates and registers {@link PortableClassDescriptor} for the given user {@code class}. + * + * @param cls Class. + * @return Class descriptor. + */ + private PortableClassDescriptor registerUserClassDescriptor(Class<?> cls, boolean deserialize) { + boolean registered; + + String typeName = typeName(cls.getName()); + + BinaryIdMapper idMapper = userTypeIdMapper(typeName); + + int typeId = idMapper.typeId(typeName); + + try { + registered = marshCtx.registerClass(typeId, cls); + } + catch (IgniteCheckedException e) { + throw new BinaryObjectException("Failed to register class.", e); + } + + String affFieldName = affinityFieldName(cls); + + PortableClassDescriptor desc = new PortableClassDescriptor(this, + cls, + true, + typeId, + typeName, + affFieldName, + idMapper, + null, + true, + registered, + false /* predefined */ + ); + + if (!deserialize) { + Collection<PortableSchema> schemas = desc.schema() != null ? Collections.singleton(desc.schema()) : null; + + metaHnd.addMeta(typeId, + new BinaryMetadata(typeId, typeName, desc.fieldsMeta(), affFieldName, schemas, desc.isEnum()).wrap(this)); + } + + // perform put() instead of putIfAbsent() because "registered" flag might have been changed or class loader + // might have reloaded described class. + if (IgniteUtils.detectClassLoader(cls).equals(dfltLdr)) + userTypes.put(typeId, desc); + + descByCls.put(cls, desc); + + mappers.putIfAbsent(typeId, idMapper); + + return desc; + } + + /** + * @param cls Collection class. + * @return Collection type ID. + */ + public byte collectionType(Class<? extends Collection> cls) { + assert cls != null; + + Byte type = colTypes.get(cls); + + if (type != null) + return type; + + return Set.class.isAssignableFrom(cls) ? GridPortableMarshaller.USER_SET : GridPortableMarshaller.USER_COL; + } + + /** + * @param cls Map class. + * @return Map type ID. + */ + public byte mapType(Class<? extends Map> cls) { + assert cls != null; + + Byte type = mapTypes.get(cls); + + return type != null ? type : GridPortableMarshaller.USER_COL; + } + + /** + * @param typeName Type name. + * @return Type ID. + */ + public int typeId(String typeName) { + String typeName0 = typeName(typeName); + + Integer id = predefinedTypeNames.get(typeName0); + + if (id != null) + return id; + + if (marshCtx.isSystemType(typeName)) + return typeName.hashCode(); + + return userTypeIdMapper(typeName0).typeId(typeName0); + } + + /** + * @param typeId Type ID. + * @param fieldName Field name. + * @return Field ID. + */ + public int fieldId(int typeId, String fieldName) { + return userTypeIdMapper(typeId).fieldId(typeId, fieldName); + } + + /** + * @param typeId Type ID. + * @return Instance of ID mapper. + */ + public BinaryIdMapper userTypeIdMapper(int typeId) { + BinaryIdMapper idMapper = mappers.get(typeId); + + return idMapper != null ? idMapper : BinaryInternalIdMapper.defaultInstance(); + } + + /** + * @param typeName Type name. + * @return Instance of ID mapper. + */ + private BinaryIdMapper userTypeIdMapper(String typeName) { + BinaryIdMapper idMapper = typeMappers.get(typeName); + + return idMapper != null ? idMapper : BinaryInternalIdMapper.defaultInstance(); + } + + /** + * @param cls Class to get affinity field for. + * @return Affinity field name or {@code null} if field name was not found. + */ + private String affinityFieldName(Class cls) { + for (; cls != Object.class && cls != null; cls = cls.getSuperclass()) { + for (Field f : cls.getDeclaredFields()) { + if (f.getAnnotation(AffinityKeyMapped.class) != null) + return f.getName(); + } + } + + return null; + } + + /** {@inheritDoc} */ + @Override public void writeExternal(ObjectOutput out) throws IOException { + U.writeString(out, igniteCfg.getGridName()); + } + + /** {@inheritDoc} */ + @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + gridName = U.readString(in); + } + + /** + * @return Portable context. + * @throws ObjectStreamException In case of error. + */ + protected Object readResolve() throws ObjectStreamException { + try { + IgniteKernal g = IgnitionEx.gridx(gridName); + + if (g == null) + throw new IllegalStateException("Failed to find grid for name: " + gridName); + + return ((CacheObjectBinaryProcessorImpl)g.context().cacheObjects()).portableContext(); + } + catch (IllegalStateException e) { + throw U.withCause(new InvalidObjectException(e.getMessage()), e); + } + } + + /** + * @param cls Class. + * @param id Type ID. + * @return GridPortableClassDescriptor. + */ + public PortableClassDescriptor registerPredefinedType(Class<?> cls, int id) { + String typeName = typeName(cls.getName()); + + PortableClassDescriptor desc = new PortableClassDescriptor( + this, + cls, + false, + id, + typeName, + null, + BinaryInternalIdMapper.defaultInstance(), + null, + false, + true, /* registered */ + true /* predefined */ + ); + + predefinedTypeNames.put(typeName, id); + predefinedTypes.put(id, desc); + + descByCls.put(cls, desc); + + return desc; + } + + /** + * @param clsName Class name. + * @param idMapper ID mapper. + * @param serializer Serializer. + * @param affKeyFieldName Affinity key field name. + * @param isEnum If enum. + * @throws BinaryObjectException In case of error. + */ + @SuppressWarnings("ErrorNotRethrown") + public void registerUserType(String clsName, + BinaryIdMapper idMapper, + @Nullable BinarySerializer serializer, + @Nullable String affKeyFieldName, + boolean isEnum) + throws BinaryObjectException { + assert idMapper != null; + + Class<?> cls = null; + + try { + cls = Class.forName(clsName); + } + catch (ClassNotFoundException | NoClassDefFoundError ignored) { + // No-op. + } + + String typeName = typeName(clsName); + + int id = idMapper.typeId(typeName); + + //Workaround for IGNITE-1358 + if (predefinedTypes.get(id) != null) + throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']'); + + if (mappers.put(id, idMapper) != null) + throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']'); + + if (affKeyFieldName != null) { + if (affKeyFieldNames.put(id, affKeyFieldName) != null) + throw new BinaryObjectException("Duplicate type ID [clsName=" + clsName + ", id=" + id + ']'); + } + + typeMappers.put(typeName, idMapper); + + Map<String, Integer> fieldsMeta = null; + Collection<PortableSchema> schemas = null; + + if (cls != null) { + PortableClassDescriptor desc = new PortableClassDescriptor( + this, + cls, + true, + id, + typeName, + affKeyFieldName, + idMapper, + serializer, + true, + true, /* registered */ + false /* predefined */ + ); + + fieldsMeta = desc.fieldsMeta(); + schemas = desc.schema() != null ? Collections.singleton(desc.schema()) : null; + + if (IgniteUtils.detectClassLoader(cls).equals(dfltLdr)) + userTypes.put(id, desc); + + descByCls.put(cls, desc); + } + + metaHnd.addMeta(id, new BinaryMetadata(id, typeName, fieldsMeta, affKeyFieldName, schemas, isEnum).wrap(this)); + } + + /** + * Create binary field. + * + * @param typeId Type ID. + * @param fieldName Field name. + * @return Binary field. + */ + public BinaryFieldImpl createField(int typeId, String fieldName) { + PortableSchemaRegistry schemaReg = schemaRegistry(typeId); + + int fieldId = userTypeIdMapper(typeId).fieldId(typeId, fieldName); + + return new BinaryFieldImpl(typeId, schemaReg, fieldName, fieldId); + } + + /** + * @param typeId Type ID. + * @return Meta data. + * @throws BinaryObjectException In case of error. + */ + @Nullable public BinaryType metadata(int typeId) throws BinaryObjectException { + return metaHnd != null ? metaHnd.metadata(typeId) : null; + } + + /** + * @param typeId Type ID. + * @return Affinity key field name. + */ + public String affinityKeyFieldName(int typeId) { + return affKeyFieldNames.get(typeId); + } + + /** + * @param typeId Type ID. + * @param meta Meta data. + * @throws BinaryObjectException In case of error. + */ + public void updateMetadata(int typeId, BinaryMetadata meta) throws BinaryObjectException { + metaHnd.addMeta(typeId, meta.wrap(this)); + } + + /** + * @return Whether field IDs should be skipped in footer or not. + */ + public boolean isCompactFooter() { + return compactFooter; + } + + /** + * Get schema registry for type ID. + * + * @param typeId Type ID. + * @return Schema registry for type ID. + */ + public PortableSchemaRegistry schemaRegistry(int typeId) { + Map<Integer, PortableSchemaRegistry> schemas0 = schemas; + + if (schemas0 == null) { + synchronized (this) { + schemas0 = schemas; + + if (schemas0 == null) { + schemas0 = new HashMap<>(); + + PortableSchemaRegistry reg = new PortableSchemaRegistry(); + + schemas0.put(typeId, reg); + + schemas = schemas0; + + return reg; + } + } + } + + PortableSchemaRegistry reg = schemas0.get(typeId); + + if (reg == null) { + synchronized (this) { + reg = schemas.get(typeId); + + if (reg == null) { + reg = new PortableSchemaRegistry(); + + schemas0 = new HashMap<>(schemas); + + schemas0.put(typeId, reg); + + schemas = schemas0; + } + } + } + + return reg; + } + + /** + * Returns instance of {@link OptimizedMarshaller}. + * + * @return Optimized marshaller. + */ + OptimizedMarshaller optimizedMarsh() { + return optmMarsh; + } + + /** + * @param clsName Class name. + * @return Type name. + */ + @SuppressWarnings("ResultOfMethodCallIgnored") + public static String typeName(String clsName) { + assert clsName != null; + + int idx = clsName.lastIndexOf('$'); + + if (idx == clsName.length() - 1) + // This is a regular (not inner) class name that ends with '$'. Common use case for Scala classes. + idx = -1; + else if (idx >= 0) { + String typeName = clsName.substring(idx + 1); + + try { + Integer.parseInt(typeName); + + // This is an anonymous class. Don't cut off enclosing class name for it. + idx = -1; + } + catch (NumberFormatException ignore) { + // This is a lambda class. + if (clsName.indexOf("$$Lambda$") > 0) + idx = -1; + else + return typeName; + } + } + + if (idx < 0) + idx = clsName.lastIndexOf('.'); + + return idx >= 0 ? clsName.substring(idx + 1) : clsName; + } + + /** + * Undeployment callback invoked when class loader is being undeployed. + * + * Some marshallers may want to clean their internal state that uses the undeployed class loader somehow. + * + * @param ldr Class loader being undeployed. + */ + public void onUndeploy(ClassLoader ldr) { + for (Class<?> cls : descByCls.keySet()) { + if (ldr.equals(cls.getClassLoader())) + descByCls.remove(cls); + } + + U.clearClassCache(ldr); + } + + /** + * Type descriptors. + */ + private static class TypeDescriptors { + /** Descriptors map. */ + private final Map<String, TypeDescriptor> descs = new LinkedHashMap<>(); + + /** + * Add type descriptor. + * + * @param clsName Class name. + * @param idMapper ID mapper. + * @param serializer Serializer. + * @param affKeyFieldName Affinity key field name. + * @param isEnum Enum flag. + * @param canOverride Whether this descriptor can be override. + * @throws BinaryObjectException If failed. + */ + private void add(String clsName, + BinaryIdMapper idMapper, + BinarySerializer serializer, + String affKeyFieldName, + boolean isEnum, + boolean canOverride) + throws BinaryObjectException { + TypeDescriptor desc = new TypeDescriptor(clsName, + idMapper, + serializer, + affKeyFieldName, + isEnum, + canOverride); + + TypeDescriptor oldDesc = descs.get(clsName); + + if (oldDesc == null) + descs.put(clsName, desc); + else + oldDesc.override(desc); + } + + /** + * Get all collected descriptors. + * + * @return Descriptors. + */ + private Iterable<TypeDescriptor> descriptors() { + return descs.values(); + } + } + + /** + * Type descriptor. + */ + private static class TypeDescriptor { + /** Class name. */ + private final String clsName; + + /** ID mapper. */ + private BinaryIdMapper idMapper; + + /** Serializer. */ + private BinarySerializer serializer; + + /** Affinity key field name. */ + private String affKeyFieldName; + + /** Enum flag. */ + private boolean isEnum; + + /** Whether this descriptor can be override. */ + private boolean canOverride; + + /** + * Constructor. + * + * @param clsName Class name. + * @param idMapper ID mapper. + * @param serializer Serializer. + * @param affKeyFieldName Affinity key field name. + * @param isEnum Enum type. + * @param canOverride Whether this descriptor can be override. + */ + private TypeDescriptor(String clsName, BinaryIdMapper idMapper, BinarySerializer serializer, + String affKeyFieldName, boolean isEnum, boolean canOverride) { + this.clsName = clsName; + this.idMapper = idMapper; + this.serializer = serializer; + this.affKeyFieldName = affKeyFieldName; + this.isEnum = isEnum; + this.canOverride = canOverride; + } + + /** + * Override portable class descriptor. + * + * @param other Other descriptor. + * @throws BinaryObjectException If failed. + */ + private void override(TypeDescriptor other) throws BinaryObjectException { + assert clsName.equals(other.clsName); + + if (canOverride) { + idMapper = other.idMapper; + serializer = other.serializer; + affKeyFieldName = other.affKeyFieldName; + canOverride = other.canOverride; + } + else if (!other.canOverride) + throw new BinaryObjectException("Duplicate explicit class definition in configuration: " + clsName); + } + } + + /** + * Type id wrapper. + */ + static class Type { + /** Type id */ + private final int id; + + /** Whether the following type is registered in a cache or not */ + private final boolean registered; + + /** + * @param id Id. + * @param registered Registered. + */ + public Type(int id, boolean registered) { + this.id = id; + this.registered = registered; + } + + /** + * @return Type ID. + */ + public int id() { + return id; + } + + /** + * @return Registered flag value. + */ + public boolean registered() { + return registered; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java new file mode 100644 index 0000000..8db6384 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePositionReadable.java @@ -0,0 +1,47 @@ +/* + * 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.binary; + +/** + * Interface allowing for positioned read. + */ +public interface PortablePositionReadable { + /** + * Read byte at the given position. + * + * @param pos Position. + * @return Value. + */ + public byte readBytePositioned(int pos); + + /** + * Read short at the given position. + * + * @param pos Position. + * @return Value. + */ + public short readShortPositioned(int pos); + + /** + * Read integer at the given position. + * + * @param pos Position. + * @return Value. + */ + public int readIntPositioned(int pos); +} http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java new file mode 100644 index 0000000..e5ff494 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortablePrimitives.java @@ -0,0 +1,382 @@ +/* + * 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.binary; + +import org.apache.ignite.internal.util.GridUnsafe; +import sun.misc.Unsafe; + +import java.nio.ByteOrder; + +/** + * Primitives writer. + */ +public abstract class PortablePrimitives { + /** */ + private static final Unsafe UNSAFE = GridUnsafe.unsafe(); + + /** */ + private static final long BYTE_ARR_OFF = UNSAFE.arrayBaseOffset(byte[].class); + + /** */ + private static final long CHAR_ARR_OFF = UNSAFE.arrayBaseOffset(char[].class); + + /** Whether little endian is set. */ + private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeByte(byte[] arr, int off, byte val) { + UNSAFE.putByte(arr, BYTE_ARR_OFF + off, val); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static byte readByte(byte[] arr, int off) { + return UNSAFE.getByte(arr, BYTE_ARR_OFF + off); + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static byte readByte(long ptr, int off) { + return UNSAFE.getByte(ptr + off); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static byte[] readByteArray(byte[] arr, int off, int len) { + byte[] arr0 = new byte[len]; + + UNSAFE.copyMemory(arr, BYTE_ARR_OFF + off, arr0, BYTE_ARR_OFF, len); + + return arr0; + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static byte[] readByteArray(long ptr, int off, int len) { + byte[] arr0 = new byte[len]; + + UNSAFE.copyMemory(null, ptr + off, arr0, BYTE_ARR_OFF, len); + + return arr0; + } + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeBoolean(byte[] arr, int off, boolean val) { + writeByte(arr, off, val ? (byte)1 : (byte)0); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static boolean readBoolean(byte[] arr, int off) { + return readByte(arr, off) == 1; + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static boolean readBoolean(long ptr, int off) { + return readByte(ptr, off) == 1; + } + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeShort(byte[] arr, int off, short val) { + if (BIG_ENDIAN) + val = Short.reverseBytes(val); + + UNSAFE.putShort(arr, BYTE_ARR_OFF + off, val); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static short readShort(byte[] arr, int off) { + short val = UNSAFE.getShort(arr, BYTE_ARR_OFF + off); + + if (BIG_ENDIAN) + val = Short.reverseBytes(val); + + return val; + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static short readShort(long ptr, int off) { + short val = UNSAFE.getShort(ptr + off); + + if (BIG_ENDIAN) + val = Short.reverseBytes(val); + + return val; + } + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeChar(byte[] arr, int off, char val) { + if (BIG_ENDIAN) + val = Character.reverseBytes(val); + + UNSAFE.putChar(arr, BYTE_ARR_OFF + off, val); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static char readChar(byte[] arr, int off) { + char val = UNSAFE.getChar(arr, BYTE_ARR_OFF + off); + + if (BIG_ENDIAN) + val = Character.reverseBytes(val); + + return val; + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static char readChar(long ptr, int off) { + char val = UNSAFE.getChar(ptr + off); + + if (BIG_ENDIAN) + val = Character.reverseBytes(val); + + return val; + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static char[] readCharArray(byte[] arr, int off, int len) { + char[] arr0 = new char[len]; + + UNSAFE.copyMemory(arr, BYTE_ARR_OFF + off, arr0, CHAR_ARR_OFF, len << 1); + + if (BIG_ENDIAN) { + for (int i = 0; i < len; i++) + arr0[i] = Character.reverseBytes(arr0[i]); + } + + return arr0; + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static char[] readCharArray(long ptr, int off, int len) { + char[] arr0 = new char[len]; + + UNSAFE.copyMemory(null, ptr + off, arr0, CHAR_ARR_OFF, len << 1); + + if (BIG_ENDIAN) { + for (int i = 0; i < len; i++) + arr0[i] = Character.reverseBytes(arr0[i]); + } + + return arr0; + } + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeInt(byte[] arr, int off, int val) { + if (BIG_ENDIAN) + val = Integer.reverseBytes(val); + + UNSAFE.putInt(arr, BYTE_ARR_OFF + off, val); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static int readInt(byte[] arr, int off) { + int val = UNSAFE.getInt(arr, BYTE_ARR_OFF + off); + + if (BIG_ENDIAN) + val = Integer.reverseBytes(val); + + return val; + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static int readInt(long ptr, int off) { + int val = UNSAFE.getInt(ptr + off); + + if (BIG_ENDIAN) + val = Integer.reverseBytes(val); + + return val; + } + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeLong(byte[] arr, int off, long val) { + if (BIG_ENDIAN) + val = Long.reverseBytes(val); + + UNSAFE.putLong(arr, BYTE_ARR_OFF + off, val); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static long readLong(byte[] arr, int off) { + long val = UNSAFE.getLong(arr, BYTE_ARR_OFF + off); + + if (BIG_ENDIAN) + val = Long.reverseBytes(val); + + return val; + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static long readLong(long ptr, int off) { + long val = UNSAFE.getLong(ptr + off); + + if (BIG_ENDIAN) + val = Long.reverseBytes(val); + + return val; + } + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeFloat(byte[] arr, int off, float val) { + int val0 = Float.floatToIntBits(val); + + writeInt(arr, off, val0); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static float readFloat(byte[] arr, int off) { + int val = readInt(arr, off); + + return Float.intBitsToFloat(val); + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static float readFloat(long ptr, int off) { + int val = readInt(ptr, off); + + return Float.intBitsToFloat(val); + } + + /** + * @param arr Array. + * @param off Offset. + * @param val Value. + */ + public static void writeDouble(byte[] arr, int off, double val) { + long val0 = Double.doubleToLongBits(val); + + writeLong(arr, off, val0); + } + + /** + * @param arr Array. + * @param off Offset. + * @return Value. + */ + public static double readDouble(byte[] arr, int off) { + long val = readLong(arr, off); + + return Double.longBitsToDouble(val); + } + + /** + * @param ptr Pointer. + * @param off Offset. + * @return Value. + */ + public static double readDouble(long ptr, int off) { + long val = readLong(ptr, off); + + return Double.longBitsToDouble(val); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/1dbf20e0/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java new file mode 100644 index 0000000..61b5d45 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/PortableSchema.java @@ -0,0 +1,466 @@ +/* + * 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.binary; + +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Schema describing portable object content. We rely on the following assumptions: + * - When amount of fields in the object is low, it is better to inline these values into int fields thus allowing + * for quick comparisons performed within already fetched L1 cache line. + * - When there are more fields, we store them inside a hash map. + */ +public class PortableSchema implements Externalizable { + /** */ + private static final long serialVersionUID = 0L; + + /** Order returned if field is not found. */ + public static final int ORDER_NOT_FOUND = -1; + + /** Minimum sensible size. */ + private static final int MAP_MIN_SIZE = 32; + + /** Empty cell. */ + private static final int MAP_EMPTY = 0; + + /** Schema ID. */ + private int schemaId; + + /** IDs depending on order. */ + private int[] ids; + + /** Interned names of associated fields. */ + private String[] names; + + /** ID-to-order data. */ + private int[] idToOrderData; + + /** ID-to-order mask. */ + private int idToOrderMask; + + /** ID 1. */ + private int id0; + + /** ID 2. */ + private int id1; + + /** ID 3. */ + private int id2; + + /** ID 4. */ + private int id3; + + /** + * {@link Externalizable} support. + */ + public PortableSchema() { + // No-op. + } + + /** + * Constructor. + * + * @param schemaId Schema ID. + * @param fieldIds Field IDs. + */ + private PortableSchema(int schemaId, List<Integer> fieldIds) { + assert fieldIds != null; + + this.schemaId = schemaId; + + initialize(fieldIds); + } + + /** + * @return Schema ID. + */ + public int schemaId() { + return schemaId; + } + + /** + * Try speculatively confirming order for the given field name. + * + * @param expOrder Expected order. + * @param expName Expected name. + * @return Field ID. + */ + @SuppressWarnings("StringEquality") + public Confirmation confirmOrder(int expOrder, String expName) { + assert expName != null; + + if (expOrder < names.length) { + String name = names[expOrder]; + + // Note that we use only reference equality assuming that field names are interned literals. + if (name == expName) + return Confirmation.CONFIRMED; + + if (name == null) + return Confirmation.CLARIFY; + } + + return Confirmation.REJECTED; + } + + /** + * Add field name. + * + * @param order Order. + * @param name Name. + */ + public void clarifyFieldName(int order, String name) { + assert name != null; + assert order < names.length; + + names[order] = name.intern(); + } + + /** + * Get field ID by order in footer. + * + * @param order Order. + * @return Field ID. + */ + public int fieldId(int order) { + return order < ids.length ? ids[order] : 0; + } + + /** + * Get field order in footer by field ID. + * + * @param id Field ID. + * @return Offset or {@code 0} if there is no such field. + */ + public int order(int id) { + if (idToOrderData == null) { + if (id == id0) + return 0; + + if (id == id1) + return 1; + + if (id == id2) + return 2; + + if (id == id3) + return 3; + + return ORDER_NOT_FOUND; + } + else { + int idx = (id & idToOrderMask) << 1; + + int curId = idToOrderData[idx]; + + if (id == curId) // Hit! + return idToOrderData[idx + 1]; + else if (curId == MAP_EMPTY) // No such ID! + return ORDER_NOT_FOUND; + else { + // Unlikely collision scenario. + for (int i = 2; i < idToOrderData.length; i += 2) { + int newIdx = (idx + i) % idToOrderData.length; + + assert newIdx < idToOrderData.length - 1; + + curId = idToOrderData[newIdx]; + + if (id == curId) + return idToOrderData[newIdx + 1]; + else if (curId == MAP_EMPTY) + return ORDER_NOT_FOUND; + } + + return ORDER_NOT_FOUND; + } + } + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return schemaId; + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + return o != null && o instanceof PortableSchema && schemaId == ((PortableSchema)o).schemaId; + } + + /** {@inheritDoc} */ + @Override public void writeExternal(ObjectOutput out) throws IOException { + out.writeInt(schemaId); + + out.writeInt(ids.length); + + for (Integer id : ids) + out.writeInt(id); + } + + /** {@inheritDoc} */ + @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + schemaId = in.readInt(); + + int idsCnt = in.readInt(); + + List<Integer> fieldIds = new ArrayList<>(idsCnt); + + for (int i = 0; i < idsCnt; i++) + fieldIds.add(in.readInt()); + + initialize(fieldIds); + } + + /** + * Parse values. + * + * @param vals Values. + * @param size Proposed result size. + * @return Parse result. + */ + private static ParseResult parse(int[] vals, int size) { + int mask = maskForPowerOfTwo(size); + + int totalSize = size * 2; + + int[] data = new int[totalSize]; + int collisions = 0; + + for (int order = 0; order < vals.length; order++) { + int id = vals[order]; + + assert id != 0; + + int idIdx = (id & mask) << 1; + + if (data[idIdx] == 0) { + // Found empty slot. + data[idIdx] = id; + data[idIdx + 1] = order; + } + else { + // Collision! + collisions++; + + boolean placeFound = false; + + for (int i = 2; i < totalSize; i += 2) { + int newIdIdx = (idIdx + i) % totalSize; + + if (data[newIdIdx] == 0) { + data[newIdIdx] = id; + data[newIdIdx + 1] = order; + + placeFound = true; + + break; + } + } + + assert placeFound : "Should always have a place for entry!"; + } + } + + return new ParseResult(data, collisions); + } + + /** + * Get next power of two which greater or equal to the given number. + * This implementation is not meant to be very efficient, so it is expected to be used relatively rare. + * + * @param val Number + * @return Nearest pow2. + */ + private static int nextPowerOfTwo(int val) { + int res = 1; + + while (res < val) + res = res << 1; + + if (res < 0) + throw new IllegalArgumentException("Value is too big to find positive pow2: " + val); + + return res; + } + + /** + * Calculate mask for the given value which is a power of two. + * + * @param val Value. + * @return Mask. + */ + private static int maskForPowerOfTwo(int val) { + int mask = 0; + int comparand = 1; + + while (comparand < val) { + mask |= comparand; + + comparand <<= 1; + } + + return mask; + } + + /** + * Initialization routine. + * + * @param fieldIds Field IDs. + */ + private void initialize(List<Integer> fieldIds) { + ids = new int[fieldIds.size()]; + + for (int i = 0; i < fieldIds.size(); i++) + ids[i] = fieldIds.get(i); + + names = new String[fieldIds.size()]; + + if (fieldIds.size() <= 4) { + Iterator<Integer> iter = fieldIds.iterator(); + + id0 = iter.hasNext() ? iter.next() : 0; + id1 = iter.hasNext() ? iter.next() : 0; + id2 = iter.hasNext() ? iter.next() : 0; + id3 = iter.hasNext() ? iter.next() : 0; + } + else { + id0 = id1 = id2 = id3 = 0; + + initializeMap(ids); + } + } + + /** + * Initialize the map. + * + * @param vals Values. + */ + private void initializeMap(int[] vals) { + int size = Math.max(nextPowerOfTwo(vals.length) << 2, MAP_MIN_SIZE); + + assert size > 0; + + ParseResult finalRes; + + ParseResult res1 = parse(vals, size); + + if (res1.collisions == 0) + finalRes = res1; + else { + ParseResult res2 = parse(vals, size * 2); + + // Failed to decrease aom + if (res2.collisions == 0) + finalRes = res2; + else + finalRes = parse(vals, size * 4); + } + + idToOrderData = finalRes.data; + idToOrderMask = maskForPowerOfTwo(idToOrderData.length / 2); + } + + /** + * Schema builder. + */ + public static class Builder { + /** Schema ID. */ + private int schemaId = PortableUtils.schemaInitialId(); + + /** Fields. */ + private final ArrayList<Integer> fields = new ArrayList<>(); + + /** + * Create new schema builder. + * + * @return Schema builder. + */ + public static Builder newBuilder() { + return new Builder(); + } + + /** + * Private constructor. + */ + private Builder() { + // No-op. + } + + /** + * Add field. + * + * @param fieldId Field ID. + */ + public void addField(int fieldId) { + fields.add(fieldId); + + schemaId = PortableUtils.updateSchemaId(schemaId, fieldId); + } + + /** + * Build schema. + * + * @return Schema. + */ + public PortableSchema build() { + return new PortableSchema(schemaId, fields); + } + } + + /** + * Order confirmation result. + */ + public enum Confirmation { + /** Confirmed. */ + CONFIRMED, + + /** Denied. */ + REJECTED, + + /** Field name clarification is needed. */ + CLARIFY + } + + /** + * Result of map parsing. + */ + private static class ParseResult { + /** Data. */ + private int[] data; + + /** Collisions. */ + private int collisions; + + /** + * Constructor. + * + * @param data Data. + * @param collisions Collisions. + */ + private ParseResult(int[] data, int collisions) { + this.data = data; + this.collisions = collisions; + } + } +}