| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more contributor license |
| * agreements. See the NOTICE file distributed with this work for additional information regarding |
| * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. You may obtain a |
| * copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software distributed under the License |
| * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
| * or implied. See the License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package org.apache.geode.pdx.internal; |
| |
| import static java.lang.Integer.valueOf; |
| |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.apache.logging.log4j.Logger; |
| |
| import org.apache.geode.annotations.internal.MakeNotStatic; |
| import org.apache.geode.cache.CacheClosedException; |
| import org.apache.geode.cache.DiskStore; |
| import org.apache.geode.cache.DiskStoreFactory; |
| import org.apache.geode.cache.wan.GatewaySender; |
| import org.apache.geode.distributed.internal.DistributionConfig; |
| import org.apache.geode.internal.Assert; |
| import org.apache.geode.internal.InternalDataSerializer; |
| import org.apache.geode.internal.cache.InternalCache; |
| import org.apache.geode.internal.util.concurrent.CopyOnWriteHashMap; |
| import org.apache.geode.internal.util.concurrent.CopyOnWriteWeakHashMap; |
| import org.apache.geode.logging.internal.log4j.api.LogService; |
| import org.apache.geode.pdx.PdxSerializationException; |
| import org.apache.geode.pdx.PdxSerializer; |
| import org.apache.geode.pdx.ReflectionBasedAutoSerializer; |
| |
| public class TypeRegistry { |
| private static final Logger logger = LogService.getLogger(); |
| |
| private static final boolean DISABLE_TYPE_REGISTRY = |
| Boolean.getBoolean(DistributionConfig.GEMFIRE_PREFIX + "TypeRegistry.DISABLE_PDX_REGISTRY"); |
| |
| private final Map<Integer, PdxType> idToType = new CopyOnWriteHashMap<>(); |
| |
| private final Map<PdxType, Integer> typeToId = new CopyOnWriteHashMap<>(); |
| |
| private final Map<Class<?>, PdxType> localTypeIds = new CopyOnWriteWeakHashMap<>(); |
| |
| private final Map<Class<?>, Map<Integer, UnreadPdxType>> localTypeIdMaps = |
| new CopyOnWriteWeakHashMap<>(); |
| |
| private final WeakConcurrentIdentityHashMap<Object, PdxUnreadData> unreadDataMap = |
| WeakConcurrentIdentityHashMap.make(); |
| |
| private final Map<Integer, EnumInfo> idToEnum = new CopyOnWriteHashMap<>(); |
| |
| private final Map<EnumInfo, Integer> enumInfoToId = new CopyOnWriteHashMap<>(); |
| |
| private final Map<Enum<?>, Integer> localEnumIds = new CopyOnWriteWeakHashMap<>(); |
| |
| private final TypeRegistration distributedTypeRegistry; |
| |
| private final InternalCache cache; |
| |
| private final ThreadLocal<Boolean> pdxReadSerializedOverride = |
| ThreadLocal.withInitial(() -> Boolean.FALSE); |
| |
| public TypeRegistry(InternalCache cache, boolean disableTypeRegistry) { |
| this.cache = cache; |
| |
| if (DISABLE_TYPE_REGISTRY || disableTypeRegistry) { |
| this.distributedTypeRegistry = new NullTypeRegistration(); |
| } else if (cache.hasPool()) { |
| this.distributedTypeRegistry = new ClientTypeRegistration(cache); |
| } else if (LonerTypeRegistration.isIndeterminateLoner(cache)) { |
| this.distributedTypeRegistry = new LonerTypeRegistration(cache); |
| } else { |
| this.distributedTypeRegistry = new PeerTypeRegistration(cache); |
| } |
| } |
| |
| TypeRegistry(InternalCache cache, TypeRegistration distributedTypeRegistry) { |
| this.cache = cache; |
| this.distributedTypeRegistry = distributedTypeRegistry; |
| } |
| |
| public void testClearLocalTypeRegistry() { |
| this.localTypeIds.clear(); |
| this.localTypeIdMaps.clear(); |
| this.localEnumIds.clear(); |
| } |
| |
| public static boolean mayNeedDiskStore(InternalCache cache) { |
| return !DISABLE_TYPE_REGISTRY && !cache.hasPool() && cache.getPdxPersistent(); |
| } |
| |
| public static String getPdxDiskStoreName(InternalCache cache) { |
| if (!mayNeedDiskStore(cache)) { |
| return null; |
| } else { |
| String result = cache.getPdxDiskStore(); |
| if (result == null) { |
| result = DiskStoreFactory.DEFAULT_DISK_STORE_NAME; |
| } |
| return result; |
| } |
| } |
| |
| public void initialize() { |
| if (!this.cache.getPdxPersistent() || this.cache.getPdxDiskStore() == null |
| || this.cache.findDiskStore(this.cache.getPdxDiskStore()) != null) { |
| this.distributedTypeRegistry.initialize(); |
| } |
| } |
| |
| public void flushCache() { |
| InternalDataSerializer.flushClassCache(); |
| for (EnumInfo ei : this.idToEnum.values()) { |
| ei.flushCache(); |
| } |
| } |
| |
| public PdxType getType(int typeId) { |
| PdxType pdxType = this.idToType.get(typeId); |
| if (pdxType != null) { |
| return pdxType; |
| } |
| |
| synchronized (this) { |
| pdxType = this.distributedTypeRegistry.getType(typeId); |
| if (pdxType != null) { |
| this.idToType.put(typeId, pdxType); |
| this.typeToId.put(pdxType, typeId); |
| if (logger.isInfoEnabled()) { |
| logger.info("Adding: {}", pdxType.toFormattedString()); |
| } |
| if (logger.isDebugEnabled()) { |
| logger.debug("Adding entry into pdx type registry, typeId: {} {}", typeId, pdxType); |
| } |
| return pdxType; |
| } |
| } |
| |
| return null; |
| } |
| |
| PdxType getExistingType(Object o) { |
| return getExistingTypeForClass(o.getClass()); |
| } |
| |
| public PdxType getExistingTypeForClass(Class<?> aClass) { |
| PdxType result = this.localTypeIds.get(aClass); |
| return result; |
| } |
| |
| /** |
| * Returns the local type that should be used for deserializing blobs of the given typeId for the |
| * given local class. Returns null if no such local type exists. |
| */ |
| UnreadPdxType getExistingTypeForClass(Class<?> aClass, int typeId) { |
| Map<Integer, UnreadPdxType> map = this.localTypeIdMaps.get(aClass); |
| if (map != null) { |
| return map.get(typeId); |
| } else { |
| return null; |
| } |
| } |
| |
| void defineUnreadType(Class<?> aClass, UnreadPdxType unreadPdxType) { |
| int typeId = unreadPdxType.getTypeId(); |
| // even though localTypeIdMaps is copy on write we need to sync it |
| // during write to safely update the nested map. |
| // We make the nested map copy-on-write so that readers don't need to sync. |
| synchronized (this.localTypeIdMaps) { |
| Map<Integer, UnreadPdxType> map = this.localTypeIdMaps.get(aClass); |
| if (map == null) { |
| map = new CopyOnWriteHashMap<Integer, UnreadPdxType>(); |
| this.localTypeIdMaps.put(aClass, map); |
| } |
| map.put(typeId, unreadPdxType); |
| } |
| } |
| |
| /** |
| * Create a type id for a type that may come locally, or from a remote member. |
| * |
| * @return the existing type or the new type |
| */ |
| public PdxType defineType(PdxType newType) { |
| Integer existingId = this.typeToId.get(newType); |
| if (existingId != null) { |
| PdxType existingType = this.idToType.get(existingId); |
| if (existingType != null) { |
| return existingType; |
| } |
| } |
| |
| int id = this.distributedTypeRegistry.defineType(newType); |
| PdxType oldType = this.idToType.get(id); |
| if (oldType == null) { |
| newType.setTypeId(id); |
| this.idToType.put(id, newType); |
| this.typeToId.put(newType, id); |
| if (logger.isInfoEnabled()) { |
| logger.info("Caching {}", newType.toFormattedString()); |
| } |
| return newType; |
| } else { |
| if (!oldType.equals(newType)) { |
| Assert.fail("Old type does not equal new type for the same id. oldType=" + oldType |
| + " new type=" + newType); |
| } |
| return oldType; |
| } |
| } |
| |
| public void addRemoteType(int typeId, PdxType newType) { |
| PdxType oldType = this.idToType.get(typeId); |
| if (oldType == null) { |
| this.distributedTypeRegistry.addRemoteType(typeId, newType); |
| this.idToType.put(typeId, newType); |
| this.typeToId.put(newType, typeId); |
| if (logger.isInfoEnabled()) { |
| logger.info("Adding, from remote WAN: {}", newType.toFormattedString()); |
| } |
| } else if (!oldType.equals(newType)) { |
| Assert.fail("Old type does not equal new type for the same id. oldType=" + oldType |
| + " new type=" + newType); |
| } |
| } |
| |
| /** |
| * Create a type id for a type that was generated locally. |
| */ |
| PdxType defineLocalType(Object o, PdxType newType) { |
| if (o != null) { |
| PdxType t = getExistingType(o); |
| if (t != null) { |
| return t; |
| } |
| PdxType existingType = defineType(newType); |
| this.localTypeIds.put(o.getClass(), existingType); |
| return existingType; |
| } else { |
| // Defining a type for PdxInstanceFactory. |
| return defineType(newType); |
| } |
| } |
| |
| public TypeRegistration getTypeRegistration() { |
| return this.distributedTypeRegistry; |
| } |
| |
| public void gatewaySenderStarted(GatewaySender gatewaySender) { |
| if (this.distributedTypeRegistry != null) { |
| this.distributedTypeRegistry.gatewaySenderStarted(gatewaySender); |
| } |
| } |
| |
| public void creatingDiskStore(DiskStore dsi) { |
| if (this.cache.getPdxDiskStore() != null |
| && dsi.getName().equals(this.cache.getPdxDiskStore())) { |
| this.distributedTypeRegistry.initialize(); |
| } |
| } |
| |
| public void creatingPersistentRegion() { |
| this.distributedTypeRegistry.creatingPersistentRegion(); |
| } |
| |
| public void creatingPool() { |
| this.distributedTypeRegistry.creatingPool(); |
| } |
| |
| // test hook |
| public void removeLocal(Object o) { |
| this.localTypeIds.remove(o.getClass()); |
| } |
| |
| PdxUnreadData getUnreadData(Object o) { |
| return this.unreadDataMap.get(o); |
| } |
| |
| void putUnreadData(Object o, PdxUnreadData ud) { |
| this.unreadDataMap.put(o, ud); |
| } |
| |
| @MakeNotStatic |
| private static final AtomicReference<PdxSerializer> pdxSerializer = new AtomicReference<>(null); |
| |
| @MakeNotStatic |
| private static final AtomicReference<AutoSerializableManager> asm = new AtomicReference<>(null); |
| |
| /** |
| * To fix bug 45116 we want any attempt to get the PdxSerializer after it has been closed to fail |
| * with an exception. |
| */ |
| @MakeNotStatic |
| private static volatile boolean open = false; |
| |
| /** |
| * If the pdxSerializer is ever set to a non-null value then set this to true. It gets reset to |
| * false when init() is called. This was added to fix bug 45116. |
| */ |
| @MakeNotStatic |
| private static volatile boolean pdxSerializerWasSet = false; |
| |
| public static void init() { |
| pdxSerializerWasSet = false; |
| } |
| |
| public static void open() { |
| open = true; |
| } |
| |
| public static void close() { |
| open = false; |
| } |
| |
| public static PdxSerializer getPdxSerializer() { |
| PdxSerializer result = pdxSerializer.get(); |
| if (result == null && !open && pdxSerializerWasSet) { |
| throw new CacheClosedException("Could not PDX serialize because the cache was closed"); |
| } |
| return result; |
| } |
| |
| public static AutoSerializableManager getAutoSerializableManager() { |
| return asm.get(); |
| } |
| |
| public static void setPdxSerializer(PdxSerializer v) { |
| if (v == null) { |
| PdxSerializer oldValue = pdxSerializer.getAndSet(null); |
| if (oldValue instanceof ReflectionBasedAutoSerializer) { |
| asm.compareAndSet( |
| (AutoSerializableManager) ((ReflectionBasedAutoSerializer) oldValue).getManager(), |
| null); |
| } |
| } else { |
| pdxSerializerWasSet = true; |
| pdxSerializer.set(v); |
| if (v instanceof ReflectionBasedAutoSerializer) { |
| asm.set((AutoSerializableManager) ((ReflectionBasedAutoSerializer) v).getManager()); |
| } |
| } |
| } |
| |
| /** |
| * Given an enum compute and return a code for it. |
| */ |
| public int getEnumId(Enum<?> v) { |
| int result = 0; |
| if (v != null) { |
| Integer id = this.localEnumIds.get(v); |
| if (id != null) { |
| result = id; |
| } else { |
| result = this.distributedTypeRegistry.getEnumId(v); |
| id = valueOf(result); |
| this.localEnumIds.put(v, id); |
| EnumInfo ei = new EnumInfo(v); |
| this.idToEnum.put(id, ei); |
| this.enumInfoToId.put(ei, id); |
| } |
| } |
| return result; |
| } |
| |
| public void addRemoteEnum(int enumId, EnumInfo newInfo) { |
| EnumInfo oldInfo = this.idToEnum.get(enumId); |
| if (oldInfo == null) { |
| this.distributedTypeRegistry.addRemoteEnum(enumId, newInfo); |
| this.idToEnum.put(enumId, newInfo); |
| this.enumInfoToId.put(newInfo, enumId); |
| } else if (!oldInfo.equals(newInfo)) { |
| Assert.fail("Old enum does not equal new enum for the same id. oldEnum=" + oldInfo |
| + " new enum=" + newInfo); |
| } |
| } |
| |
| public int defineEnum(EnumInfo newInfo) { |
| Integer existingId = this.enumInfoToId.get(newInfo); |
| if (existingId != null) { |
| return existingId; |
| } |
| int id = this.distributedTypeRegistry.defineEnum(newInfo); |
| EnumInfo oldInfo = this.idToEnum.get(id); |
| if (oldInfo == null) { |
| this.idToEnum.put(id, newInfo); |
| this.enumInfoToId.put(newInfo, id); |
| if (logger.isInfoEnabled()) { |
| logger.info("Caching PDX Enum: {}, dsid={} typenum={}", newInfo, id >> 24, id & 0xFFFFFF); |
| } |
| } else if (!oldInfo.equals(newInfo)) { |
| Assert.fail("Old enum does not equal new enum for the same id. oldEnum=" + oldInfo |
| + " newEnum=" + newInfo); |
| } |
| return id; |
| } |
| |
| public Object getEnumById(int enumId) { |
| if (enumId == 0) { |
| return null; |
| } |
| EnumInfo ei = getEnumInfoById(enumId); |
| if (ei == null) { |
| throw new PdxSerializationException( |
| "Could not find a PDX registration for the enum with id " + enumId); |
| } |
| if (this.cache.getPdxReadSerializedByAnyGemFireServices()) { |
| return ei.getPdxInstance(enumId); |
| } else { |
| try { |
| return ei.getEnum(); |
| } catch (ClassNotFoundException ex) { |
| throw new PdxSerializationException( |
| "PDX enum field could not be read because the enum class could not be loaded", ex); |
| } |
| } |
| } |
| |
| public EnumInfo getEnumInfoById(int enumId) { |
| if (enumId == 0) { |
| return null; |
| } |
| |
| EnumInfo ei = this.idToEnum.get(enumId); |
| if (ei == null) { |
| ei = this.distributedTypeRegistry.getEnumById(enumId); |
| if (ei != null) { |
| this.idToEnum.put(enumId, ei); |
| this.enumInfoToId.put(ei, enumId); |
| } |
| } |
| return ei; |
| } |
| |
| /** |
| * Clear all of the cached PDX types in this registry. This method is used on a client when the |
| * server side distributed system is cycled |
| */ |
| public void clear() { |
| if (this.distributedTypeRegistry.isClient()) { |
| this.idToType.clear(); |
| this.typeToId.clear(); |
| this.localTypeIds.clear(); |
| this.localTypeIdMaps.clear(); |
| this.unreadDataMap.clear(); |
| this.idToEnum.clear(); |
| this.enumInfoToId.clear(); |
| this.localEnumIds.clear(); |
| AutoSerializableManager autoSerializer = getAutoSerializableManager(); |
| if (autoSerializer != null) { |
| autoSerializer.resetCachedTypes(); |
| } |
| } |
| } |
| |
| /** |
| * Returns the currently defined types. |
| * |
| * @return the types |
| */ |
| public Map<Integer, PdxType> typeMap() { |
| return this.distributedTypeRegistry.types(); |
| } |
| |
| /** |
| * Returns the currently defined enums. |
| * |
| * @return the enums |
| */ |
| public Map<Integer, EnumInfo> enumMap() { |
| return this.distributedTypeRegistry.enums(); |
| } |
| |
| /** |
| * searches a field in different versions (PdxTypes) of a class in the distributed type registry |
| * |
| * @param fieldName the field to look for in the PdxTypes |
| * @param className the PdxTypes for this class would be searched |
| * @return PdxType having the field or null if not found |
| */ |
| public PdxType getPdxTypeForField(String fieldName, String className) { |
| return this.distributedTypeRegistry.getPdxTypeForField(fieldName, className); |
| } |
| |
| /** |
| * Returns all the PdxTypes for the given class name. |
| * An empty set will be returned if no types exist. |
| */ |
| public Set<PdxType> getPdxTypesForClassName(String className) { |
| return this.distributedTypeRegistry.getPdxTypesForClassName(className); |
| } |
| |
| public void addImportedType(int typeId, PdxType importedType) { |
| PdxType existing = getType(typeId); |
| if (existing != null && !existing.equals(importedType)) { |
| throw new PdxSerializationException( |
| String.format( |
| "Detected conflicting PDX types during import:%s%sSnapshot data containing PDX types must be imported into an empty cache with no pre-existing type definitions. Allow the import to complete prior to inserting additional data into the cache.", |
| importedType, existing)); |
| } |
| |
| this.distributedTypeRegistry.addImportedType(typeId, importedType); |
| this.idToType.put(typeId, importedType); |
| this.typeToId.put(importedType, typeId); |
| if (logger.isInfoEnabled()) { |
| logger.info("Importing type: {}", importedType.toFormattedString()); |
| } |
| } |
| |
| public void addImportedEnum(int enumId, EnumInfo importedEnum) { |
| EnumInfo existing = getEnumInfoById(enumId); |
| if (existing != null && !existing.equals(importedEnum)) { |
| throw new PdxSerializationException( |
| String.format( |
| "Detected conflicting PDX types during import:%s%sSnapshot data containing PDX types must be imported into an empty cache with no pre-existing type definitions. Allow the import to complete prior to inserting additional data into the cache.", |
| importedEnum, existing)); |
| } |
| |
| this.distributedTypeRegistry.addImportedEnum(enumId, importedEnum); |
| this.idToEnum.put(enumId, importedEnum); |
| this.enumInfoToId.put(importedEnum, enumId); |
| } |
| |
| /** |
| * Get the size of the the type registry in this local member |
| */ |
| int getLocalSize() { |
| int result = this.distributedTypeRegistry.getLocalSize(); |
| if (result == 0) { |
| // If this is the client, go ahead and return the number of cached types we have |
| return this.idToType.size(); |
| } |
| return result; |
| } |
| |
| public Boolean getPdxReadSerializedOverride() { |
| return pdxReadSerializedOverride.get(); |
| } |
| |
| public void setPdxReadSerializedOverride(boolean overridePdxReadSerialized) { |
| pdxReadSerializedOverride.set(overridePdxReadSerialized); |
| } |
| |
| // accessors for unit test |
| |
| Map<Integer, PdxType> getIdToType() { |
| return idToType; |
| } |
| |
| Map<PdxType, Integer> getTypeToId() { |
| return typeToId; |
| } |
| |
| Map<Class<?>, PdxType> getLocalTypeIds() { |
| return localTypeIds; |
| } |
| } |