blob: 7eacda0679f209dc80563c13adbdbf3d2f32a2a4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.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.logging.LogService;
import org.apache.geode.internal.util.concurrent.CopyOnWriteHashMap;
import org.apache.geode.internal.util.concurrent.CopyOnWriteWeakHashMap;
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;
}
}