| /* |
| * 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.IgniteLogger; |
| import org.apache.ignite.binary.BinaryIdMapper; |
| import org.apache.ignite.binary.BinaryInvalidTypeException; |
| import org.apache.ignite.binary.BinaryObjectException; |
| import org.apache.ignite.binary.BinaryReflectiveSerializer; |
| 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.AffinityKey; |
| import org.apache.ignite.cache.affinity.AffinityKeyMapped; |
| import org.apache.ignite.configuration.BinaryConfiguration; |
| import org.apache.ignite.configuration.IgniteConfiguration; |
| import org.apache.ignite.internal.IgnitionEx; |
| import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataKey; |
| 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.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; |
| |
| /** |
| * Binary context. |
| */ |
| public class BinaryContext { |
| /** */ |
| private static final ClassLoader dfltLdr = U.gridClassLoader(); |
| |
| /** */ |
| private final ConcurrentMap<Class<?>, BinaryClassDescriptor> descByCls = new ConcurrentHashMap8<>(); |
| |
| /** Holds classes loaded by default class loader only. */ |
| private final ConcurrentMap<Integer, BinaryClassDescriptor> userTypes = new ConcurrentHashMap8<>(); |
| |
| /** */ |
| private final Map<Integer, BinaryClassDescriptor> 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 IgniteConfiguration igniteCfg; |
| |
| /** Logger. */ |
| private IgniteLogger log; |
| |
| /** */ |
| private final OptimizedMarshaller optmMarsh = new OptimizedMarshaller(false); |
| |
| /** Compact footer flag. */ |
| private boolean compactFooter; |
| |
| /** Object schemas. */ |
| private volatile Map<Integer, BinarySchemaRegistry> schemas; |
| |
| /** |
| * For {@link Externalizable}. |
| */ |
| public BinaryContext() { |
| // No-op. |
| } |
| |
| /** |
| * @param metaHnd Meta data handler. |
| * @param igniteCfg Ignite configuration. |
| * @param log Logger. |
| */ |
| public BinaryContext(BinaryMetadataHandler metaHnd, IgniteConfiguration igniteCfg, IgniteLogger log) { |
| assert metaHnd != null; |
| assert igniteCfg != null; |
| |
| this.metaHnd = metaHnd; |
| this.igniteCfg = igniteCfg; |
| this.log = log; |
| |
| colTypes.put(ArrayList.class, GridBinaryMarshaller.ARR_LIST); |
| colTypes.put(LinkedList.class, GridBinaryMarshaller.LINKED_LIST); |
| colTypes.put(HashSet.class, GridBinaryMarshaller.HASH_SET); |
| colTypes.put(LinkedHashSet.class, GridBinaryMarshaller.LINKED_HASH_SET); |
| |
| mapTypes.put(HashMap.class, GridBinaryMarshaller.HASH_MAP); |
| mapTypes.put(LinkedHashMap.class, GridBinaryMarshaller.LINKED_HASH_MAP); |
| |
| // IDs range from [0..200] is used by Java SDK API and GridGain legacy API |
| |
| registerPredefinedType(Byte.class, GridBinaryMarshaller.BYTE); |
| registerPredefinedType(Boolean.class, GridBinaryMarshaller.BOOLEAN); |
| registerPredefinedType(Short.class, GridBinaryMarshaller.SHORT); |
| registerPredefinedType(Character.class, GridBinaryMarshaller.CHAR); |
| registerPredefinedType(Integer.class, GridBinaryMarshaller.INT); |
| registerPredefinedType(Long.class, GridBinaryMarshaller.LONG); |
| registerPredefinedType(Float.class, GridBinaryMarshaller.FLOAT); |
| registerPredefinedType(Double.class, GridBinaryMarshaller.DOUBLE); |
| registerPredefinedType(String.class, GridBinaryMarshaller.STRING); |
| registerPredefinedType(BigDecimal.class, GridBinaryMarshaller.DECIMAL); |
| registerPredefinedType(Date.class, GridBinaryMarshaller.DATE); |
| registerPredefinedType(Timestamp.class, GridBinaryMarshaller.TIMESTAMP); |
| registerPredefinedType(UUID.class, GridBinaryMarshaller.UUID); |
| |
| registerPredefinedType(byte[].class, GridBinaryMarshaller.BYTE_ARR); |
| registerPredefinedType(short[].class, GridBinaryMarshaller.SHORT_ARR); |
| registerPredefinedType(int[].class, GridBinaryMarshaller.INT_ARR); |
| registerPredefinedType(long[].class, GridBinaryMarshaller.LONG_ARR); |
| registerPredefinedType(float[].class, GridBinaryMarshaller.FLOAT_ARR); |
| registerPredefinedType(double[].class, GridBinaryMarshaller.DOUBLE_ARR); |
| registerPredefinedType(char[].class, GridBinaryMarshaller.CHAR_ARR); |
| registerPredefinedType(boolean[].class, GridBinaryMarshaller.BOOLEAN_ARR); |
| registerPredefinedType(BigDecimal[].class, GridBinaryMarshaller.DECIMAL_ARR); |
| registerPredefinedType(String[].class, GridBinaryMarshaller.STRING_ARR); |
| registerPredefinedType(UUID[].class, GridBinaryMarshaller.UUID_ARR); |
| registerPredefinedType(Date[].class, GridBinaryMarshaller.DATE_ARR); |
| registerPredefinedType(Timestamp[].class, GridBinaryMarshaller.TIMESTAMP_ARR); |
| registerPredefinedType(Object[].class, GridBinaryMarshaller.OBJ_ARR); |
| |
| // Special collections. |
| registerPredefinedType(ArrayList.class, 0); |
| registerPredefinedType(LinkedList.class, 0); |
| registerPredefinedType(HashSet.class, 0); |
| registerPredefinedType(LinkedHashSet.class, 0); |
| registerPredefinedType(HashMap.class, 0); |
| registerPredefinedType(LinkedHashMap.class, 0); |
| |
| // Classes with overriden default serialization flag. |
| registerPredefinedType(AffinityKey.class, 0, affinityFieldName(AffinityKey.class)); |
| |
| registerPredefinedType(GridMapEntry.class, 60); |
| registerPredefinedType(IgniteBiTuple.class, 61); |
| registerPredefinedType(T2.class, 62); |
| |
| registerPredefinedType(BinaryObjectImpl.class, 0); |
| registerPredefinedType(BinaryObjectOffheapImpl.class, 0); |
| registerPredefinedType(BinaryMetadataKey.class, 0); |
| registerPredefinedType(BinaryMetadata.class, 0); |
| |
| // IDs range [200..1000] is used by Ignite internal APIs. |
| } |
| |
| /** |
| * @return Logger. |
| */ |
| public IgniteLogger log() { |
| return log; |
| } |
| |
| /** |
| * @return Marshaller. |
| */ |
| public BinaryMarshaller marshaller() { |
| return marsh; |
| } |
| |
| /** |
| * Check whether class must be deserialized anyway. |
| * |
| * @param cls Class. |
| * @return {@code True} if must be deserialized. |
| */ |
| public boolean mustDeserialize(Class cls) { |
| BinaryClassDescriptor desc = descByCls.get(cls); |
| |
| if (desc == null) |
| return marshCtx.isSystemType(cls.getName()) || serializerForClass(cls) == null; |
| else |
| return desc.useOptimizedMarshaller(); |
| } |
| |
| /** |
| * @return Ignite configuration. |
| */ |
| public IgniteConfiguration configuration(){ |
| return igniteCfg; |
| } |
| |
| /** |
| * @param marsh Binary 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 binary 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 BinaryClassDescriptor descriptorForClass(Class<?> cls, boolean deserialize) |
| throws BinaryObjectException { |
| assert cls != null; |
| |
| BinaryClassDescriptor 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 BinaryClassDescriptor descriptorForTypeId( |
| boolean userType, |
| int typeId, |
| ClassLoader ldr, |
| boolean deserialize |
| ) { |
| assert typeId != GridBinaryMarshaller.UNREGISTERED_TYPE_ID; |
| |
| //TODO: As a workaround for IGNITE-1358 we always check the predefined map before without checking 'userType' |
| BinaryClassDescriptor 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 BinaryClassDescriptor} for the given {@code class}. |
| * |
| * @param cls Class. |
| * @return Class descriptor. |
| */ |
| private BinaryClassDescriptor registerClassDescriptor(Class<?> cls, boolean deserialize) { |
| BinaryClassDescriptor desc; |
| |
| String clsName = cls.getName(); |
| |
| if (marshCtx.isSystemType(clsName)) { |
| desc = new BinaryClassDescriptor(this, |
| cls, |
| false, |
| clsName.hashCode(), |
| clsName, |
| null, |
| BinaryInternalIdMapper.defaultInstance(), |
| null, |
| false, |
| true /* registered */ |
| ); |
| |
| BinaryClassDescriptor old = descByCls.putIfAbsent(cls, desc); |
| |
| if (old != null) |
| desc = old; |
| } |
| else |
| desc = registerUserClassDescriptor(cls, deserialize); |
| |
| return desc; |
| } |
| |
| /** |
| * Creates and registers {@link BinaryClassDescriptor} for the given user {@code class}. |
| * |
| * @param cls Class. |
| * @return Class descriptor. |
| */ |
| private BinaryClassDescriptor 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); |
| } |
| |
| BinarySerializer serializer = serializerForClass(cls); |
| |
| String affFieldName = affinityFieldName(cls); |
| |
| BinaryClassDescriptor desc = new BinaryClassDescriptor(this, |
| cls, |
| true, |
| typeId, |
| typeName, |
| affFieldName, |
| idMapper, |
| serializer, |
| true, |
| registered |
| ); |
| |
| if (!deserialize) { |
| Collection<BinarySchema> 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; |
| } |
| |
| /** |
| * Get serializer for class taking in count default one. |
| * |
| * @param cls Class. |
| * @return Serializer for class or {@code null} if none exists. |
| */ |
| private @Nullable BinarySerializer serializerForClass(Class cls) { |
| BinarySerializer serializer = defaultSerializer(); |
| |
| if (serializer == null && canUseReflectiveSerializer(cls)) |
| serializer = new BinaryReflectiveSerializer(); |
| |
| return serializer; |
| } |
| |
| /** |
| * @return Default serializer. |
| */ |
| private BinarySerializer defaultSerializer() { |
| BinaryConfiguration binCfg = igniteCfg.getBinaryConfiguration(); |
| |
| return binCfg != null ? binCfg.getSerializer() : null; |
| } |
| |
| /** |
| * @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) ? GridBinaryMarshaller.USER_SET : GridBinaryMarshaller.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 : GridBinaryMarshaller.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; |
| } |
| |
| /** |
| * @param cls Class. |
| * @param id Type ID. |
| * @return GridBinaryClassDescriptor. |
| */ |
| public BinaryClassDescriptor registerPredefinedType(Class<?> cls, int id) { |
| return registerPredefinedType(cls, id, null); |
| } |
| |
| /** |
| * @param cls Class. |
| * @param id Type ID. |
| * @return GridBinaryClassDescriptor. |
| */ |
| public BinaryClassDescriptor registerPredefinedType(Class<?> cls, int id, String affFieldName) { |
| String typeName = typeName(cls.getName()); |
| |
| if (id == 0) |
| id = BinaryInternalIdMapper.defaultInstance().typeId(typeName); |
| |
| BinaryClassDescriptor desc = new BinaryClassDescriptor( |
| this, |
| cls, |
| false, |
| id, |
| typeName, |
| affFieldName, |
| BinaryInternalIdMapper.defaultInstance(), |
| new BinaryReflectiveSerializer(), |
| false, |
| true /* registered */ |
| ); |
| |
| predefinedTypeNames.put(typeName, id); |
| predefinedTypes.put(id, desc); |
| |
| descByCls.put(cls, desc); |
| |
| if (affFieldName != null) |
| affKeyFieldNames.putIfAbsent(id, affFieldName); |
| |
| 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<BinarySchema> schemas = null; |
| |
| if (cls != null) { |
| if (serializer == null) { |
| // At this point we must decide whether to rely on Java serialization mechanics or not. |
| // If no serializer is provided, we examine the class and if it doesn't contain non-trivial |
| // serialization logic we are safe to fallback to reflective binary serialization. |
| if (canUseReflectiveSerializer(cls)) |
| serializer = new BinaryReflectiveSerializer(); |
| } |
| |
| BinaryClassDescriptor desc = new BinaryClassDescriptor( |
| this, |
| cls, |
| true, |
| id, |
| typeName, |
| affKeyFieldName, |
| idMapper, |
| serializer, |
| true, |
| true /* registered */ |
| ); |
| |
| 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)); |
| } |
| |
| /** |
| * Check whether reflective serializer can be used for class. |
| * |
| * @param cls Class. |
| * @return {@code True} if reflective serializer can be used. |
| */ |
| private static boolean canUseReflectiveSerializer(Class cls) { |
| return BinaryUtils.isBinarylizable(cls) || !BinaryUtils.isCustomJavaSerialization(cls); |
| } |
| |
| /** |
| * Create binary field. |
| * |
| * @param typeId Type ID. |
| * @param fieldName Field name. |
| * @return Binary field. |
| */ |
| public BinaryFieldImpl createField(int typeId, String fieldName) { |
| BinarySchemaRegistry 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 BinarySchemaRegistry schemaRegistry(int typeId) { |
| Map<Integer, BinarySchemaRegistry> schemas0 = schemas; |
| |
| if (schemas0 == null) { |
| synchronized (this) { |
| schemas0 = schemas; |
| |
| if (schemas0 == null) { |
| schemas0 = new HashMap<>(); |
| |
| BinarySchemaRegistry reg = new BinarySchemaRegistry(); |
| |
| schemas0.put(typeId, reg); |
| |
| schemas = schemas0; |
| |
| return reg; |
| } |
| } |
| } |
| |
| BinarySchemaRegistry reg = schemas0.get(typeId); |
| |
| if (reg == null) { |
| synchronized (this) { |
| reg = schemas.get(typeId); |
| |
| if (reg == null) { |
| reg = new BinarySchemaRegistry(); |
| |
| 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 binary 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; |
| isEnum = other.isEnum; |
| 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; |
| } |
| } |
| } |