All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.io.pof.ConfigurablePofContext Maven / Gradle / Ivy

There is a newer version: 24.09
Show newest version
/*
 * Copyright (c) 2000, 2022, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * https://oss.oracle.com/licenses/upl.
 */

package com.tangosol.io.pof;


import com.oracle.coherence.common.base.Logger;
import com.tangosol.coherence.config.Config;

import com.tangosol.io.ClassLoaderAware;
import com.tangosol.io.Evolvable;
import com.tangosol.io.ReadBuffer;
import com.tangosol.io.WriteBuffer;

import com.tangosol.io.pof.annotation.Portable;
import com.tangosol.io.pof.schema.annotation.PortableType;

import com.tangosol.run.xml.SimpleElement;
import com.tangosol.run.xml.XmlConfigurable;
import com.tangosol.run.xml.XmlDocument;
import com.tangosol.run.xml.XmlElement;
import com.tangosol.run.xml.XmlHelper;

import com.tangosol.util.Base;
import com.tangosol.util.ClassHelper;
import com.tangosol.util.CopyOnWriteMap;
import com.tangosol.util.ExternalizableHelper;
import com.tangosol.util.LiteMap;
import com.tangosol.util.Resources;
import com.tangosol.util.SafeHashMap;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.DotName;
import org.jboss.jandex.Index;
import org.jboss.jandex.IndexReader;

import jakarta.inject.Named;

import java.io.IOException;
import java.io.InputStream;

import java.lang.ref.WeakReference;

import java.lang.reflect.Modifier;

import java.net.URL;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.stream.Collectors;


/**
* This class implements the {@link PofContext} interface using information
* provided in a configuration file (or in a passed XML configuration) as well as
* classes annotated with {@link PortableType}.
* 

* For each user type supported by this POF context, it must be provided with: *

    *
  • A valid user type ID that is unique within this POF context;
  • *
  • A Java class name that identifies a Java class or interface that all * values of the user type are type-assignable to (and that no values of * other user types are type-assignable to); in other words, all values of * the user type (and no values of other user types) are instances of the * specified class, instances of a sub-class of the specified class, or * (if it is an interface) instances of a class that implements the * specified interface;
  • *
  • A Java class name that identifies a non-abstract implementation of * the PofSerializer interface.
  • *
*

* The format of the configuration XML is as follows: *

{@code
* <pof-config>
*   <user-type-list>
*     ..
*     <user-type>
*       <type-id>53</type-id>
*       <class-name>com.mycompany.data.Trade</class-name>
*       <serializer>
*         <class-name>com.tangosol.io.pof.PortableObjectSerializer</class-name>
*         <init-params>
*           <init-param>
*             <param-type>int</param-type>
*             <param-value>{type-id}</param-value>
*           </init-param>
*         </init-params>
*       </serializer>
*     </user-type>

*     <user-type>
*       <type-id>54</type-id>
*       <class-name>com.mycompany.data.Position</class-name>
*     </user-type>
*
*     ..
*     <include>file:/my-pof-config.xml</include>
*
*     ..
*   </user-type-list>
*
*   <allow-interfaces>false</allow-interfaces>
*   <allow-subclasses>false</allow-subclasses>
* </pof-config>
* }
* For each user type, a user-type element must exist inside the * user-type-list element. The user-type-list element * contains up to three elements, in the following order: *
    *
  • The user-type element should contain a type-id * element whose value specifies the unique integer type ID; if none of * the user-type elements contains a type-id element, * then the type IDs for the user types will be based on the order in * which they appear in the configuration, with the first user type being * assigned the type ID 0, the second user type being assigned the type ID * 1, and so on. (It is strongly recommended that user types IDs always be * specified, in order to support schema versioning and evolution.)
  • *
  • The class-name element is required, and specifies the fully * qualified name of the Java class or interface that all values of the * user type are type-assignable to.
  • *
  • The serializer element is used to specify an implementation of * PofSerializer to use to serialize and deserialize user type values to * and from a POF stream. Within the serializer element, the * class-name element is required, and zero or more constructor * parameters can be defined within an init-params block. If no * serializer is specified, either implement the PortableObject * interface or have a {@link Portable} annotation. If the former, a * {@link PortableObjectSerializer} will be used. If the later, a * {@link PofAnnotationSerializer} will be used.
  • *
*

* The optional include element allows user-type elements * defined in another configuration XML to be added to the user type list. * The value of this element is a locator string (either a valid path or URL) * that specifies the location of the target PofContext configuration file. * The user-type elements of the target file are imported verbatum; * therefore, if the included elements contain explicit type identifiers, each * identifier must be unique with respect to the the user type identifiers * (either explicit or generated) defined within the including file. If the * included user types do not contain explicit type identifiers, then the type * identifiers will be based on the order in which the user types appear in * the composite configuration file. Multiple include elements may * be used within a single user-type-list element. *

* The ConfigurablePofContext is truly ClassLoader-aware. It is conceivable * that the ConfigurablePofContext is loaded by the system ClassLoader (or * some other relatively global ClassLoader), while the objects deserialized * by the PofContext are loaded by an application-specific ClassLoader, such * as is typical within an application server. The ConfigurablePofContext * is designed to load the configuration, the POF-able object classes and the * PofSerializer classes from within a specified ClassLoader context, and to * pass the ClassLoader information on to the PofSerializer instances, just in * case they are not loaded from within the application's ClassLoader context. * In other words, the ConfigurablePofContext, its configuration, the * PofSerializer classes and the POF-able classes can all be loaded by the * same ClassLoader, or they can all be loaded by different ClassLoaders, so * long as the configuration, the POF-able classes and the PofSerializer * classes can be loaded by either the specified ClassLoader or by the * ClassLoader that loaded the ConfigurablePofContext itself. *

* In order to be used by the ConfigurablePofContext, a PofSerializer * implementation must provide a public constructor that accepts the * parameters detailed by the init-params element. The parameter * values, as specified by the param-value element, can specify one * of the following substitutable values: *

    *
  • {type-id} - replaced with the Type ID of the User Type;
  • *
  • {class-name} - replaced with the name of the class for the * User Type;
  • *
  • {class} - replaced with the Class for the User Type;
  • *
  • {class-loader} - replaced with the ConfigurablePofContext's * ContextClassLoader.
  • *
* If the init-params element is not present, then the * ConfigurablePofContext attempts to construct the PofSerializer by searching * for one of the following constructors in the same order as they appear * here: *
    *
  • (int nTypeId, Class clz, ClassLoader loader)
  • *
  • (int nTypeId, Class clz)
  • *
  • (int nTypeId)
  • *
  • ()
  • *
*

* Once constructed, if the PofSerializer implements the XmlConfigurable * interface, the {@link XmlConfigurable#setConfig setConfig} method is * invoked, and it is passed the parameter XML information, transposed as * described by {@link XmlHelper#transformInitParams transformInitParams}, and * as described in the coherence-pof-config.xsd file. *

* Finally, if the PofSerializer implements the ClassLoaderAware interface and * a ClassLoader has been specified, then the * {@link ClassLoaderAware#setContextClassLoader setContextClassLoader} method * is invoked with the reference to the specified ClassLoader. *

* Conceptually, the identity of a ConfigurablePofContext is a combination of * a configuration locator and a ClassLoader. The ClassLoader is used to * resolve and load the configuration details whose location is specified by * the configuration locator, and to load all of the classes specified by the * configuration. To achieve acceptable performance, and to limit the * redundant use of resources, the ConfigurablePofContext maintains a * WeakHashMap keyed by ClassLoader, whose corresponding values are each a * SafeHashMap keyed by configuration locator, whose corresponding values * contain the data necessary to efficiently perform the operations prescribed * by the PofContext interface. *

* Note: The configuration for the default * {@link #ConfigurablePofContext() constructor} can be specified using the * {@link #PROPERTY_CONFIG tangosol.pof.config} system property. * * @author jh/cp 2006.07.24 * * @since Coherence 3.2 */ @Named("pof") public class ConfigurablePofContext implements PofContext, ClassLoaderAware, XmlConfigurable { // ----- constructors --------------------------------------------------- /** * Default constructor. *

* Create a default ConfigurablePofContext that will load configuration * information from the locator specified in {@link #DEFAULT_RESOURCE}. */ public ConfigurablePofContext() { this((String) null); } /** * Create a ConfigurablePofContext that will load configuration * information from the specified locator. * * @param sLocator the locator that specifies the location of the * PofContext configuration file; the locator is either * a valid path or a URL */ public ConfigurablePofContext(String sLocator) { m_sUri = sLocator; } /** * Create a ConfigurablePofContext that will use the passed configuration * information. * * @param xml an XmlElement containing information in the format of a * configuration file used by ConfigurablePofContext */ public ConfigurablePofContext(XmlElement xml) { setConfig(xml); } /** * Copy constructor for a ConfigurablePofContext. * * @param that the ConfigurablePofContext to (shallow) copy from */ public ConfigurablePofContext(ConfigurablePofContext that) { this.m_cfg = that.m_cfg; this.m_fReferenceEnabled = that.m_fReferenceEnabled; this.m_refLoader = that.m_refLoader; this.m_sUri = that.m_sUri; this.m_xml = that.m_xml; } // ----- XmlConfigurable interface -------------------------------------- /** * {@inheritDoc} */ public XmlElement getConfig() { return m_xml; } /** * {@inheritDoc} *

* Note that the configuration cannot be set after the * ConfigurablePofContext is fully initialized. * * @throws IllegalStateException if the ConfigurablePofContext is already * fully initialized */ public synchronized void setConfig(XmlElement xml) { if (xml != null && !XmlHelper.isEmpty(xml)) { checkNotInitialized(); if (m_sUri == null) { // generate a fake locator to use as a unique name for the // configuration, as if it were a URI m_sUri = "xml:" + Base.toDecString( xml.toString().hashCode() & 0x7FFFFFFF, 8); } m_xml = xml; } } // ----- ClassLoaderAware interface ------------------------------------- /** * {@inheritDoc} */ public ClassLoader getContextClassLoader() { ClassLoader loader = null; WeakReference ref = m_refLoader; if (ref != null) { loader = (ClassLoader) ref.get(); if (loader == null) { throw new IllegalStateException( "ClassLoader is no longer available"); } } return loader; } /** * {@inheritDoc} *

* Note that the ConfigurablePofContext will fully initialize when it is * provided a ClassLoader. * * @throws IllegalStateException if the ConfigurablePofContext is already * fully initialized */ public synchronized void setContextClassLoader(ClassLoader loader) { checkNotInitialized(); m_refLoader = loader == null ? null : new WeakReference<>(loader); initialize(); } // ----- Serializer interface ------------------------------------------- /** * {@inheritDoc} */ public void serialize(WriteBuffer.BufferOutput out, Object o) throws IOException { ensureInitialized(); PofBufferWriter writer = new PofBufferWriter(out, this); // COH-5065: due to the complexity of maintaining references // in future data, we won't support them for Evolvable objects if (isReferenceEnabled() && !(o instanceof Evolvable)) { writer.enableReference(); } try { writer.writeObject(-1, o); } catch (RuntimeException e) { // Guarantee that runtime exceptions from called methods are // IOException IOException ioex = new IOException(e.getMessage()); ioex.initCause(e); throw ioex; } } /** * {@inheritDoc} */ public Object deserialize(ReadBuffer.BufferInput in) throws IOException { ensureInitialized(); PofBufferReader reader = new PofBufferReader(in, this); try { return reader.readObject(-1); } catch (RuntimeException e) { // Guarantee that runtime exceptions from called methods are // IOException IOException ioex = new IOException(e.getMessage()); ioex.initCause(e); throw ioex; } } @Override public String getName() { return "pof"; } // ----- PofContext implementation -------------------------------------- /** * {@inheritDoc} */ public PofSerializer getPofSerializer(int nTypeId) { ensureInitialized(); PofSerializer serializer; try { serializer = m_cfg.m_aSerByTypeId[nTypeId]; } catch (IndexOutOfBoundsException e) { serializer = null; } if (serializer == null) { throw new IllegalArgumentException("unknown user type: " + nTypeId); } return serializer; } /** * {@inheritDoc} */ public int getUserTypeIdentifier(Object o) { if (o == null) { throw new IllegalArgumentException("Object cannot be null"); } return getUserTypeIdentifier(o.getClass()); } /** * {@inheritDoc} */ public int getUserTypeIdentifier(Class clz) { int nTypeId = getUserTypeIdentifierInternal(clz); if (nTypeId < 0) { throw new IllegalArgumentException("unknown user type: " + clz.getName()); } return nTypeId; } /** * {@inheritDoc} */ public int getUserTypeIdentifier(String sClass) { int nTypeId = getUserTypeIdentifierInternal(sClass); if (nTypeId < 0) { throw new IllegalArgumentException("unknown user type: " + sClass); } return nTypeId; } /** * {@inheritDoc} */ public String getClassName(int nTypeId) { return (String) m_cfg.m_mapClassNameByTypeId.get(Integer.valueOf(nTypeId)); } /** * {@inheritDoc} */ public Class getClass(int nTypeId) { ensureInitialized(); Class clz; try { clz = (Class) m_cfg.m_aClzByTypeId[nTypeId].get(); } catch (IndexOutOfBoundsException e) { clz = null; } if (clz == null) { String sClass = (String) m_cfg.m_mapClassNameByTypeId.get(Integer.valueOf(nTypeId)); if (sClass != null && !sClass.isEmpty()) { // since we hold a weak reference, Class may have been GC'd; // try loading again clz = loadClass(sClass); } } if (clz == null) { throw new IllegalArgumentException("unknown user type: " + nTypeId); } return clz; } /** * {@inheritDoc} */ public boolean isUserType(Object o) { if (o == null) { throw new IllegalArgumentException("Object cannot be null"); } return isUserType(o.getClass()); } /** * {@inheritDoc} */ public boolean isUserType(Class clz) { return getUserTypeIdentifierInternal(clz) >= 0; } /** * {@inheritDoc} */ public boolean isUserType(String sClass) { return getUserTypeIdentifierInternal(sClass) >= 0; } /** * {@inheritDoc} */ public boolean isPreferJavaTime() { return m_fPreferJavaTime; } // ----- internal helpers ----------------------------------------------- /** * Determine the user type identifier associated with the given class. * * @param clz a user type class; must not be null * * @return the type identifier of the user type associated with the given * class or -1 if the user type is unknown to this PofContext */ protected int getUserTypeIdentifierInternal(Class clz) { ensureInitialized(); Integer ITypeId = (Integer) m_cfg.m_mapTypeIdByClass.get(clz); return ITypeId == null ? getInheritedUserTypeIdentifier(clz) : ITypeId.intValue(); } /** * Helper method for determining the user type identifier associated with * a given class that does not have a direct configured association. * * @param clz a user type class; must not be null * * @return the type identifier of the user type associated with the given * class or -1 if the user type and its superclass(es) and * implemented interface(s) are unknown to this PofContext */ protected int getInheritedUserTypeIdentifier(Class clz) { Map mapClzToId = m_cfg.m_mapTypeIdByClass; if (clz == null) { throw new IllegalArgumentException("class is required"); } if (isSubclassAllowed()) { Class clzSuper = clz.getSuperclass(); while (clzSuper != null) { Integer ITypeId = (Integer) mapClzToId.get(clzSuper); if (ITypeId != null) { int nTypeId = ITypeId.intValue(); // update the mapping so that we don't have to // brute-force search again mapClzToId.put(clz, ITypeId); return nTypeId; } clzSuper = clzSuper.getSuperclass(); } } if (isInterfaceAllowed()) { // check each user type interface to see if the passed class // implements it synchronized (mapClzToId) { for (Iterator iter = mapClzToId.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = (Map.Entry) iter.next(); Class clzCur = (Class) entry.getKey(); Integer ICurId = (Integer) entry.getValue(); if (clzCur != null && ICurId != null && clzCur.isInterface() && clzCur.isAssignableFrom(clz)) { int nTypeId = ICurId.intValue(); // update the mapping so that we don't have to // brute-force search again mapClzToId.put(clz, ICurId); return nTypeId; } } } } // update the mapping with the miss so that we don't have to // brute-force search again mapClzToId.put(clz, Integer.valueOf(-1)); return -1; } /** * Determine the user type identifier associated with the given class * name. * * @param sClass the name of a user type class; must not be null * * @return the type identifier of the user type associated with the given * class name or -1 if the user type is unknown to this PofContext */ protected int getUserTypeIdentifierInternal(String sClass) { ensureInitialized(); int nTypeId = -1; Map mapNameToId = m_cfg.m_mapTypeIdByClassName; Integer ITypeId = (Integer) mapNameToId.get(sClass); if (ITypeId == null) { if (sClass == null || sClass.length() == 0) { throw new IllegalArgumentException("class name is required"); } // special cases: the class name is a sub-class of a user type // or a class that implements an interface that is a user type if (isSubclassAllowed() || isInterfaceAllowed() || isLambdaAllowed()) { nTypeId = getUserTypeIdentifierInternal(loadClass(sClass)); if (nTypeId >= 0) { mapNameToId.put(sClass, Integer.valueOf(nTypeId)); } } } else { nTypeId = ITypeId.intValue(); } return nTypeId; } // ----- accessors ------------------------------------------------------ /** * Determine if the ConfigurablePofContext has completed its * initialization. * * @return true iff the initialization is complete */ protected boolean isInitialized() { return m_cfg != null; } /** * Obtain the location of the configuration that the * ConfigurablePofContext used to configure itself. * * @return the location information for the configuration for the * ConfigurablePofContext, or null if not yet initialized and no * location was specified */ protected String getConfigLocation() { return m_sUri; } /** * Obtain the PofConfig that represents the initialized state of the * ConfigurablePofContext. * * @return the PofConfig for the ConfigurablePofContext, or null if not * yet initialized */ protected PofConfig getPofConfig() { return m_cfg; } /** * Determine if the ConfigurablePofContext supports the configuration of * user types by specifying an interface (instead of a class) for the * Java type. * * @return true iff an interface name is acceptable in the configuration * as the class of a user type */ protected boolean isInterfaceAllowed() { PofConfig cfg = m_cfg; return cfg != null && cfg.m_fInterfaceAllowed; } /** * Determine if the ConfigurablePofContext supports the serialization of * an object that is an instance of a sub-class of a configured type, * but not actually an instance of a class of a configured type. * * @return true iff serialization of sub-classes is explicitly enabled */ protected boolean isSubclassAllowed() { PofConfig cfg = m_cfg; return cfg != null && cfg.m_fSubclassAllowed; } /** * Determine if implicit root lambda class processing is allowed. * * @return true iff implicit root lambda class processing is allowed. */ protected boolean isLambdaAllowed() { PofConfig cfg = m_cfg; return cfg != null && cfg.m_mapTypeIdByClass.containsKey(ROOT_LAMBDA_CLASS); } /** * Determine if Identity/Reference type support is enabled for this * ConfigurablePofContext. * * @return true if Identity/Reference type support is enabled */ public boolean isReferenceEnabled() { return m_fReferenceEnabled; } /** * Set the referenceEnabled flag. * * @param fReferenceEnabled the referenceEnabled flag to set */ public void setReferenceEnabled(boolean fReferenceEnabled) { m_fReferenceEnabled = fReferenceEnabled; } /** * Set the flag specifying if Java 8 date/time types (java.time.*) should be * preferred over legacy types. * * @param fPreferJavaTime whether Java 8 data/time types */ public void setPreferJavaTime(boolean fPreferJavaTime) { m_fPreferJavaTime = fPreferJavaTime; } /** * Set the Jandex index file name to use to discover portable types. * * @param sIndexFile Jandex index file name to use to discover portable types */ public void setIndexFileName(String sIndexFile) { m_sIndexFileName = sIndexFile; } /** * Return the Jandex index file name to use to discover portable types. * * @return the Jandex index file name to use to discover portable types */ public String getIndexFileName() { return m_sIndexFileName; } // ----- Object methods ------------------------------------------------- /** * Return a description of this ConfigurablePofContext. * * @return a String representation of the ConfigurablePofContext object */ public String toString() { return getClass().getName() + " {location=" + m_sUri +'}'; } // ----- internal methods ----------------------------------------------- /** * Verify that the ConfigurablePofContext has not already been * initialized. * * @throws IllegalStateException if the ConfigurablePofContext is already * fully initialized */ protected void checkNotInitialized() { if (m_cfg != null) { throw new IllegalStateException("already initialized"); } } /** * Fully initialize the ConfigurablePofContext if it has not already been * initialized. */ protected void ensureInitialized() { if (m_cfg == null) { initialize(); } } /** * Bind the ConfigurablePofContext to a ClassLoader, resolving all class * names, etc. */ protected synchronized void initialize() { if (m_cfg == null) { // dereference by ClassLoader Map mapConfigByLoader = s_mapConfigurations; Map mapConfigByURI; synchronized (mapConfigByLoader) { ClassLoader loader = getContextClassLoader(); mapConfigByURI = (Map) mapConfigByLoader.get(loader); if (mapConfigByURI == null) { mapConfigByURI = new SafeHashMap(); mapConfigByLoader.put(loader, mapConfigByURI); } } // dereference by URI String sURI = m_sUri; if (sURI == null) { m_sUri = sURI = DEFAULT_RESOURCE; } PofConfig cfg = (PofConfig) mapConfigByURI.get(sURI); if (cfg == null) { cfg = createPofConfig(); // now that a PofConfig has been created for the ClassLoader // and URI combination, store it for future use (assuming // that another thread didn't beat this thread to it) synchronized (mapConfigByURI) { if (mapConfigByURI.containsKey(sURI)) { cfg = (PofConfig) mapConfigByURI.get(sURI); } else { mapConfigByURI.put(sURI, cfg); } } } // store configuration m_cfg = cfg; m_fReferenceEnabled = cfg.m_fReferenceEnabled; m_fPreferJavaTime = cfg.m_fPreferJavaTime; } } /** * Create a PofConfig object based on a configuration that was either * provided as XML, or can be loaded from the specified (or default) URI * using the provided ClassLoader. * * @return a PofConfig for this ConfigurablePofContext */ protected PofConfig createPofConfig() { // load the XML configuration if it is not already provided String sURI = m_sUri; XmlElement xmlConfig = m_xml; int cClasses = 0; if (xmlConfig == null) { xmlConfig = XmlHelper.loadFileOrResourceOrDefault(sURI, "POF configuration", getContextClassLoader()); } // get the type configuration information XmlElement xmlAllTypes = xmlConfig.getElement("user-type-list"); if (xmlAllTypes == null) { report(sURI, -1, null, null, "Missing element"); } mergeIncludes(sURI, xmlConfig, getContextClassLoader()); // extract options boolean fAllowInterfaces = xmlConfig.getSafeElement("allow-interfaces").getBoolean(); boolean fAllowSubclasses = xmlConfig.getSafeElement("allow-subclasses").getBoolean(); boolean fEnableReferences = xmlConfig.getSafeElement("enable-references").getBoolean(); boolean fEnableTypeDiscovery = xmlConfig.getSafeElement("enable-type-discovery").getBoolean(); boolean fEnableConfigDiscovery = xmlConfig.getSafeElement("enable-config-discovery").getBoolean(true); boolean fPreferJavaTime = xmlConfig.getSafeElement("prefer-java-time").getBoolean(); Map mapPortableTypes = new SafeHashMap<>(); if (fEnableTypeDiscovery) { Map mapIndexes = loadIndexes(); // find the list of classes that implement PortableType if (mapIndexes.size() > 0) { for (Map.Entry entry : mapIndexes.entrySet()) { Index index = entry.getValue(); cClasses = 0; for (AnnotationInstance anno : index.getAnnotations(DotName.createSimple(PortableType.class.getName()))) { if (anno.target().kind().equals(AnnotationTarget.Kind.CLASS)) { AnnotationValue id = anno.value("id"); mapPortableTypes.put(anno.target().asClass().toString(), id == null ? -1 : id.asInt()); cClasses++; } } Logger.info(cClasses + " class" + (cClasses != 1 ? "es" : "") + " registered with PortableType annotation " + "from index: " + entry.getKey()); } } } // scan the types for the highest type-id List listTypes = xmlAllTypes.getElementList(); int nMaxTypeId = -1; boolean fSomeMissing = false; boolean fSomePresent = false; for (Iterator iter = listTypes.iterator(); iter.hasNext(); ) { XmlElement xmlType = (XmlElement) iter.next(); if (!xmlType.getName().equals("user-type")) { report(sURI, -1, null, null, " contains an illegal element: " + xmlType.getName()); } XmlElement xmlId = xmlType.getElement("type-id"); if (xmlId == null) { fSomeMissing = true; if (fSomePresent) { report(sURI, -1, null, null, " contains a" + " that is missing a type ID value"); } } else { int nTypeId = xmlId.getInt(-1); if (nTypeId < 0) { report(sURI, -1, null, null, " contains a" + " that has a missing or invalid type" + " ID value: " + xmlId.getString(null)); } fSomePresent = true; if (fSomeMissing) { report(sURI, -1, null, null, " contains a" + " that is missing a type ID value"); } if (nTypeId > nMaxTypeId) { nMaxTypeId = nTypeId; } } } // get max value of discovered PortableType int cPortableTypes = mapPortableTypes.size(); int nMaxPortableTypeId = cPortableTypes == 0 ? 0 : mapPortableTypes.values().stream().reduce(Integer::max).get(); int nMinPortableTypeId = cPortableTypes == 0 ? 0 : mapPortableTypes.values().stream().reduce(Integer::min).get(); if (nMaxPortableTypeId > nMaxTypeId) { nMaxTypeId = nMaxPortableTypeId; } boolean fAutoNumber = fSomeMissing || nMinPortableTypeId == -1; int cElements = fAutoNumber ? listTypes.size() + cPortableTypes : nMaxTypeId + 1; // create the relationships between type ids, class names and // classes Map mapTypeIdByClass = new WeakHashMap(); Map mapTypeIdByClassName = new SafeHashMap(); Map mapClassNameByTypeId = new SafeHashMap(); WeakReference[] aClzByTypeId = new WeakReference[cElements]; PofSerializer[] aSerByTypeId = new PofSerializer[cElements]; int cTypeIds = 0; for (Iterator iter = listTypes.iterator(); iter.hasNext(); ) { XmlElement xmlType = (XmlElement) iter.next(); // determine the user type ID int nTypeId = fAutoNumber ? cTypeIds : xmlType.getElement("type-id").getInt(); if (aClzByTypeId[nTypeId] != null) { report(sURI, nTypeId, null, null, "Duplicate user type id"); } // determine the class name for the user type, and register it final String sClass = xmlType.getSafeElement("class-name").getString(); if (sClass == null || sClass.length() == 0) { report(sURI, nTypeId, null, null, "Missing class name"); } final Integer ITypeId = Integer.valueOf(nTypeId); // load the class for the user type, and register it final Class clz = loadClass(sURI, sClass, nTypeId); // check if it is an interface or abstract class validateClass(sURI, sClass, clz, nTypeId, fAllowInterfaces, fAllowSubclasses); // determine the serializer implementation, and register it XmlElement xmlSer = xmlType.getElement("serializer"); PofSerializer serializer = null; if (xmlSer == null) { if (PortableObject.class.isAssignableFrom(clz)) { serializer = clz.isAnnotationPresent(PortableType.class) ? new PortableTypeSerializer<>(nTypeId, clz) : new PortableObjectSerializer(nTypeId); } else if (clz.getAnnotation(Portable.class) == null) { report(sURI, nTypeId, clz.getName(), null, "Missing PofSerializer configuration"); } else { serializer = new PofAnnotationSerializer(nTypeId, clz); } } else { serializer = instantiateSerializer(xmlSer, nTypeId, clz); } // PofSerializer initialization: XmlConfigurable if (serializer instanceof XmlConfigurable) { try { XmlElement xmlParams = new SimpleElement("config"); XmlHelper.transformInitParams(xmlParams, xmlSer.getSafeElement("init-params")); ((XmlConfigurable) serializer).setConfig(xmlParams); } catch (RuntimeException e) { report(sURI, nTypeId, sClass, e, "Unable to configure PofSerializer"); } } // PofSerializer initialization: ClassLoaderAware if (serializer instanceof ClassLoaderAware) { try { ((ClassLoaderAware) serializer).setContextClassLoader( getContextClassLoader()); } catch (RuntimeException e) { report(sURI, nTypeId, sClass, e, "Unable to set ContextClassLoader for PofSerializer"); } } // store information related to the user type mapTypeIdByClass.put(clz, ITypeId); mapTypeIdByClassName.put(sClass, ITypeId); mapClassNameByTypeId.put(ITypeId, sClass); aClzByTypeId[nTypeId] = new WeakReference(clz); aSerByTypeId[nTypeId] = serializer; ++cTypeIds; } // validate that all classes annotated with PortableType annotation have specified // id's or not. There cannot be a mix of types otherwise this could cause clashes or array bounds errors if (!mapPortableTypes.isEmpty() && nMinPortableTypeId <= 0 && nMaxPortableTypeId > 0) { report(sURI, -1, null, null, "Mixed mode of specifying 'id' attribute of annotation on some types and not others is not supported"); } // look for any classes that implement PortableType and add them automatically for (Map.Entry entry : mapPortableTypes.entrySet()) { String sClass = entry.getKey(); int nTypeId = entry.getValue(); if (nTypeId == -1) { // handle auto-generated type id nTypeId = cTypeIds++; } final Integer ITypeId = nTypeId; if (mapClassNameByTypeId.containsKey(ITypeId)) { report(sURI, nTypeId, null, null, "Duplicate user type id from PortableType annotation"); } final Class clz = loadClass(sURI, sClass, nTypeId); validateClass(sURI, sClass, clz, nTypeId, fAllowInterfaces, fAllowSubclasses); mapTypeIdByClass.put(clz, ITypeId); mapTypeIdByClassName.put(sClass, ITypeId); mapClassNameByTypeId.put(ITypeId, sClass); aClzByTypeId[nTypeId] = new WeakReference(clz); aSerByTypeId[nTypeId] = new PortableTypeSerializer<>(nTypeId, clz); } // store off the reusable configuring in a PofConfig object PofConfig cfg = new PofConfig(); cfg.m_mapTypeIdByClass = new CopyOnWriteMap(mapTypeIdByClass); cfg.m_mapTypeIdByClassName = mapTypeIdByClassName; cfg.m_mapClassNameByTypeId = mapClassNameByTypeId; cfg.m_mapPortableTypes = mapPortableTypes; cfg.m_aClzByTypeId = aClzByTypeId; cfg.m_aSerByTypeId = aSerByTypeId; cfg.m_fInterfaceAllowed = fAllowInterfaces; cfg.m_fSubclassAllowed = fAllowSubclasses; cfg.m_fReferenceEnabled = fEnableReferences; cfg.m_fPreferJavaTime = fPreferJavaTime; cfg.m_fEnableTypeDiscovery = fEnableTypeDiscovery; cfg.m_fEnableConfigDiscovery = fEnableConfigDiscovery; return cfg; } /** * Load a class from a given class name and throw and exception if it does not exist. * * @param sURI URI that specifies the location of the configuration file * @param sClass class name to load * @param nTypeId type id * @return the {@link Class} with the given name */ private Class loadClass(String sURI, String sClass, int nTypeId) { Class clz = null; try { clz = loadClass(sClass); } catch (RuntimeException e) { report(sURI, nTypeId, sClass, e, "Unable to load class for user type"); } return clz; } /** * Check if the given class is an interface or abstract class. * * @param sURI URI that specifies the location of the configuration file * @param sClass class name to load as a string * @param clz actual class name * @param nTypeId type id * @param fAllowInterfaces indicates if to allow interfaces * @param fAllowSubclasses indicates if to allow subclasses */ private void validateClass(String sURI, String sClass, Class clz, int nTypeId, boolean fAllowInterfaces, boolean fAllowSubclasses) { // check if it is an interface or abstract class if (clz.isInterface()) { if (!fAllowInterfaces) { report(sURI, nTypeId, sClass, null, "User Type cannot be an interface (allow-interfaces=false)"); } } else if (Modifier.isAbstract(clz.getModifiers())) { if (!fAllowSubclasses) { report(sURI, nTypeId, sClass, null, "User Type cannot be an abstract class (allow-subclasses=false)"); } } } /** * Create a {@link PofSerializer} from the provided XML serializer * element. * * @param xmlSer xml defining the serializer to create * @param nTypeId the user type id this class is registered with * @param clz the class of the user type * * @return a PofSerializer implementation capable of (de)serializing * clz */ protected PofSerializer instantiateSerializer(XmlElement xmlSer, int nTypeId, final Class clz) { final Integer ITypeId = nTypeId; PofSerializer serializer = null; String sURI = m_sUri; if (xmlSer != null) { String sSerClass = xmlSer.getElement("class-name").getString(); if (sSerClass == null || sSerClass.length() == 0) { report(sURI, nTypeId, clz.getName(), null, "Missing PofSerializer class name"); } // load the class for the user type, and register it Class clzSer = null; try { clzSer = loadClass(sSerClass); } catch (RuntimeException e) { report(sURI, nTypeId, clz.getName(), e, "Unable to load PofSerializer class: " + sSerClass); } if (!PofSerializer.class.isAssignableFrom(clzSer)) { report(sURI, nTypeId, clz.getName(), null, "Class is not a PofSerializer: " + sSerClass); } // only attempt the default PofSerializer constructors if // there are no parameters specified, or if there is at least // one parameter specified, but it doesn't have a type (which // indicates that the serializer is XmlConfigurable using a // transposed form of the parameters) XmlElement xmlParams = xmlSer.getElement("init-params"); Iterator iterParam; InstantiateSerializer: if (xmlParams == null || (iterParam = xmlParams.getElements("init-param")).hasNext() && ((XmlElement) iterParam.next()).getElement("param-type") == null) { // try the four prescribed constructors try { serializer = (PofSerializer) ClassHelper.newInstance(clzSer, new Object[] {ITypeId, clz, getContextClassLoader()}); break InstantiateSerializer; } catch (Throwable e) {} try { serializer = (PofSerializer) ClassHelper.newInstance(clzSer, new Object[] {ITypeId, clz}); break InstantiateSerializer; } catch (Throwable e) {} try { serializer = (PofSerializer) ClassHelper.newInstance(clzSer, new Object[] {ITypeId}); break InstantiateSerializer; } catch (Throwable e) {} try { serializer = (PofSerializer) clzSer.newInstance(); } catch (Throwable e) { // all four failed, so use the exception from this // most recent failure as the basis for reporting // the failure report(sURI, nTypeId, clz.getName(), e, "Unable to instantiate PofSerializer class using" + " predefined constructors: " + sSerClass); } } else { // create a parameter resolver for the substitutable // parameters XmlHelper.ParameterResolver resolver = new XmlHelper.ParameterResolver() { public Object resolveParameter(String sType, String sValue) { if (sValue.equals("{type-id}")) { return ITypeId; } else if (sValue.equals("{class-name}")) { return clz.getName(); } else if (sValue.equals("{class}")) { return clz; } else if (sValue.equals("{class-loader}")) { return getContextClassLoader(); } else { return sValue; } } }; // parse the constructor parameters Object[] aoParams = null; try { aoParams = XmlHelper.parseInitParams(xmlParams, resolver); } catch (RuntimeException e) { report(sURI, nTypeId, clz.getName(), e, "Error parsing constructor parameters for PofSerializer:" + sSerClass); } // instantiate the serializer try { serializer = (PofSerializer) ClassHelper.newInstance(clzSer, aoParams); } catch (Throwable e) { report(sURI, nTypeId, clz.getName(), e, "Unable to instantiate PofSerializer class: " + sSerClass); } } } return serializer; } /** * Find the specified class, return a Java Class object for it. * * @param sClass the fully qualified class name * * @return the Class object for the specified class name, never null * * @throws RuntimeException a RuntimeException (or a subclass thereof) * is thrown if the specified Class could not be loaded */ protected Class loadClass(String sClass) { try { return ExternalizableHelper.loadClass(sClass, getContextClassLoader(), null); } catch (ClassNotFoundException e) { throw Base.ensureRuntimeException(e); } } /** * Assemble and throw an informative exception based on the passed * details. * * @param sURI the URI of the configuration * @param nTypeId the type ID (if applicable and if known) * @param sClass the user type class name (if applicable and if known) * @param e the underlying exception, if any * @param sText the detailed description of the problem * * @throws IllegalStateException always thrown */ protected void report(String sURI, int nTypeId, String sClass, Throwable e, String sText) { StringBuffer sb = new StringBuffer(); if (sURI != null && sURI.length() > 0) { sb.append("Config=") .append(sURI); } if (nTypeId >= 0) { if (sb.length() > 0) { sb.append(", "); } sb.append("Type-Id=") .append(nTypeId); } if (sClass != null && sClass.length() > 0) { if (sb.length() > 0) { sb.append(", "); } sb.append("Class-Name=") .append(sClass); } if (sb.length() > 0) { sText = sText + " (" + sb + ')'; } throw e == null ? new IllegalStateException(sText) : Base.ensureRuntimeException(e, sText); } /** * Attempt to load all Jandex index files which will we will use to search for * classes that have the PortableType annotation. * * @return a {@link Map} of {@link Index}es keyed by {@link URL} or an empty {@link Map} if none found. */ private Map loadIndexes() { Map mapIndexes = new LiteMap<>(); String sIndexFileName = m_sIndexFileName; try { Iterable iterUrls = Resources.findResources(sIndexFileName, getContextClassLoader()); // loop through each URL and load the index for (URL url : iterUrls) { try (InputStream input = url.openStream()) { mapIndexes.put(url, new IndexReader(input).read()); } catch (Exception ignore) { Logger.warn("Unable to read Jandex index file " + url + ", error is " + ignore.getMessage()); } } } catch (IOException e) { // any Exception coming from getResources() or toURL() is ignored and // the Map of indexes remain empty } return mapIndexes; } /** * Merge all included POF configuration files into the given xml configuration. * * @param sURI the URI of the POF configuration file * @param xmlConfig the base POF configuration * @param loader the {@link ClassLoader} used to find the included * POF configuration resources */ public static void mergeIncludes(String sURI, XmlElement xmlConfig, ClassLoader loader) { // extract options boolean fAllowInterfaces = xmlConfig.getSafeElement("allow-interfaces").getBoolean(); boolean fAllowSubclasses = xmlConfig.getSafeElement("allow-subclasses").getBoolean(); boolean fEnableReferences = xmlConfig.getSafeElement("enable-references").getBoolean(); boolean fEnableTypeDiscovery = xmlConfig.getSafeElement("enable-type-discovery").getBoolean(); boolean fEnableConfigDiscovery = xmlConfig.getSafeElement("enable-config-discovery").getBoolean(true); boolean fAddedDiscovered = false; XmlElement xmlAllTypes = xmlConfig.getElement("user-type-list"); if (!fEnableConfigDiscovery) { Logger.info("POF config discovery is disabled in " + sURI); } // add default-serializer to each user-type appendDefaultSerializerToUserTypes(xmlConfig); // discover any config providers and add their config URIs to the list of includes Set setDiscovered = ServiceLoader.load(PofConfigProvider.class) .stream() .flatMap(p -> p.get().getConfigURIs().stream()) .filter(Objects::nonNull) .filter(s -> !s.isBlank()) .collect(Collectors.toSet()); if (fEnableConfigDiscovery && !xmlAllTypes.getElements("include").hasNext()) { // there are no includes and discovery is enabled fAddedDiscovered = true; for (String sDiscovered : setDiscovered) { xmlAllTypes.addElement("include").setString(sDiscovered); } } // locate and add all included user types for (List listURI = null; xmlAllTypes.getElement("include") != null; ) { if (listURI == null) { listURI = new ArrayList<>(); listURI.add(sURI); } // load included URIs, checking for duplicates List listInclude = new ArrayList<>(); String sIncludeURI = null; for (Iterator iter = xmlAllTypes.getElements("include"); iter.hasNext(); ) { sIncludeURI = ((XmlElement) iter.next()).getString(); iter.remove(); if (!listURI.contains(sIncludeURI)) { listURI.add(sIncludeURI); String sReason = fAddedDiscovered && setDiscovered.contains(sIncludeURI) ? "discovered" : "included"; listInclude.add(XmlHelper.loadFileOrResource( sIncludeURI, sReason + " POF configuration", loader)); } } // add the user types from all included URIs and adjust options for (Iterator iter = listInclude.iterator(); iter.hasNext(); ) { XmlElement xmlInclude = (XmlElement) iter.next(); XmlElement xmlIncludeTypes = xmlInclude.getSafeElement("user-type-list"); appendDefaultSerializerToUserTypes(xmlInclude); fAllowInterfaces |= xmlInclude.getSafeElement("allow-interfaces").getBoolean(); fAllowSubclasses |= xmlInclude.getSafeElement("allow-subclasses").getBoolean(); fEnableReferences |= xmlInclude.getSafeElement("enable-references").getBoolean(); fEnableTypeDiscovery |= xmlInclude.getSafeElement("enable-type-discovery").getBoolean(); fEnableConfigDiscovery &= xmlInclude.getSafeElement("enable-config-discovery").getBoolean(true); if (!fEnableConfigDiscovery) { Logger.info("POF config discovery is disabled in " + sIncludeURI); } XmlHelper.addElements(xmlAllTypes, xmlIncludeTypes.getElements("user-type")); XmlHelper.addElements(xmlAllTypes, xmlIncludeTypes.getElements("include")); } // if we're done (no include elements left in xmlAllTypes) // and discovery enabled (fEnableConfigDiscovery), // and we have not added any discovered includes yet, // then add any discovered if (fEnableConfigDiscovery && !xmlAllTypes.getElements("include").hasNext() && !fAddedDiscovered) { fAddedDiscovered = true; for (String sDiscovered : setDiscovered) { if (!listURI.contains(sDiscovered)) { xmlAllTypes.addElement("include").setString(sDiscovered); } } } } xmlConfig.ensureElement("allow-interfaces").setBoolean(fAllowInterfaces); xmlConfig.ensureElement("allow-subclasses").setBoolean(fAllowSubclasses); xmlConfig.ensureElement("enable-type-discovery").setBoolean(fEnableTypeDiscovery); xmlConfig.ensureElement("enable-references").setBoolean(fEnableReferences); xmlConfig.ensureElement("enable-config-discovery").setBoolean(fEnableConfigDiscovery); } /** * Process <default-serializer> element from the specified xml * configuration and append information about the serializer to each * <user-type> element within <user-type-list> unless * user type already has a serializer specified. *

* This method could be overridden to add new custom configuration * elements to pof-config. * * @param xmlConfig the XmlElement containing pof configuration */ protected static void appendDefaultSerializerToUserTypes(XmlElement xmlConfig) { XmlElement xmlDefaultSerializer = xmlConfig.getElement("default-serializer"); if (xmlDefaultSerializer != null) { XmlElement xmlAllTypes = xmlConfig.getElement("user-type-list"); for (Iterator iter = xmlAllTypes.getElements("user-type"); iter.hasNext(); ) { XmlElement xmlType = (XmlElement) iter.next(); XmlElement xmlSer = xmlType.getElement("serializer"); if (xmlSer == null) { // add the default-serializer to this user-type XmlElement xmlNewSer = (XmlElement) xmlDefaultSerializer.clone(); xmlNewSer.setName("serializer"); xmlType.getElementList().add(xmlNewSer); } } } } // ----- inner class: PofConfig ----------------------------------------- /** * The information related to the configuration of a particular PofContext * for a specific URI and ClassLoader. */ protected static class PofConfig { /** * Once initialized, this references a thread-safe Map that contains * mappings from Java classes to POF type identifiers (wrapped as * Integer objects). The initial contents of the Map reflect the * configuration, but the contents can increase over time as * sub-classes of the contained classes are resolved to type IDs (and * those mappings are added). */ public Map m_mapTypeIdByClass; /** * Once initialized, this references a thread-safe Map that contains * mappings from Java class names to POF type identifiers (wrapped as * Integer objects). The initial contents of the Map reflect the * configuration, but the contents can increase over time as the names * of sub-classes (i.e. of the classes corresponding to the contained * class names) are resolved to type IDs (and those mappings are * added). */ public Map m_mapTypeIdByClassName; /** * An array of WeakReferences to user type classes, indexed by type identifier. */ public WeakReference[] m_aClzByTypeId; /** * An array of PofSerializer objects, indexed by type identifier. */ public PofSerializer[] m_aSerByTypeId; /** * True iff an interface name is acceptable in the configuration as * the class of a user type. */ public boolean m_fInterfaceAllowed; /** * True iff serialization of sub-classes is explicitly enabled. */ public boolean m_fSubclassAllowed; /** * True iff POF Identity/Reference type support is enabled. */ public boolean m_fReferenceEnabled; /** * True if Java 8 date/time types (java.time.*) should be preferred over * legacy types. */ public boolean m_fPreferJavaTime; /** * Once initialized, this references a thread-safe Map that contains * mappings from POF type identifiers (wrapped as Integer objects) to * Java class names. The initial contents of the Map reflect the * configuration, but the contents can increase over time as the names * of sub-classes (i.e. of the classes corresponding to the contained * class names) are resolved to type IDs (and those mappings are * added). */ public Map m_mapClassNameByTypeId; /** * Discovered list of types that are annotated with {@link PortableType}. */ public Map m_mapPortableTypes; /** * True iff discovery of PortableTypes is enabled. */ public boolean m_fEnableTypeDiscovery; /** * A flag to enable POF config discovery. */ public boolean m_fEnableConfigDiscovery = true; } // ----- constants ------------------------------------------------------ /** * The name of the system property ("tangosol.pof.config") that * can be used to override the location of the default POF configuration * file. *

* The value of this property must be the name of a resource that contains * an XML document with the structure defined in /coherence-pof-config.xsd * (deployed in coherence.jar). *

* The default value for the "coherence.pof.config" system * property is "coherence-pof-config.xml". */ public static final String PROPERTY_CONFIG = "coherence.pof.config"; /** * The name of the application resource that contains the default set of * wire-format-to-object bindings. *

* The default value for the resource name is * "pof-config.xml". The default can be overriden by * specifying a value for the {@link #PROPERTY_CONFIG tangosol.pof.config} * system property. */ public static final String DEFAULT_RESOURCE = Config.getProperty(PROPERTY_CONFIG, "pof-config.xml"); /** * Marker serving as the implicit root class for all lambdas. */ protected static final Class ROOT_LAMBDA_CLASS = FunctionalInterface.class; /** * Default Jandex index file. */ protected static final String DEFAULT_INDEX_FILE_NAME = "META-INF/jandex.idx"; // ----- data members --------------------------------------------------- /** * Map of configuration information, keyed by ClassLoader. */ private static final Map s_mapConfigurations = new WeakHashMap(); /** * A WeakReference to the ClassLoader specified for this PofContext to * use. */ private WeakReference m_refLoader; /** * The URI that specifies the location of the configuration file. */ private String m_sUri; /** * The XML configuration, if supplied by constructor, or by the * XmlConfigurable interface. */ private XmlElement m_xml; /** * True if POF Identity/Reference type support is enabled. Allows us to * override the static one in PofConfig. */ private boolean m_fReferenceEnabled; /** * True if Java 8 date/time types (java.time.*) should be preferred over * legacy types. Allows us to override the static one in PofConfig. */ private boolean m_fPreferJavaTime; /** * The PofConfig for this PofContext to use. */ private volatile PofConfig m_cfg; /** * The Jandex index file name to use. */ private String m_sIndexFileName = DEFAULT_INDEX_FILE_NAME; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy