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, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */

package com.tangosol.io.pof;


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.run.xml.SimpleElement;
import com.tangosol.run.xml.XmlConfigurable;
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.SafeHashMap;

import java.io.IOException;

import java.lang.ref.WeakReference;

import java.lang.reflect.Modifier;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;


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

* 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 */ 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; } } // ----- 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; } // ----- 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; if (xmlConfig == null) { xmlConfig = XmlHelper.loadFileOrResource(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 fPreferJavaTime = xmlConfig.getSafeElement("prefer-java-time").getBoolean(); // 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; } } } boolean fAutoNumber = fSomeMissing; int cElements = fAutoNumber ? listTypes.size() : 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; try { clz = loadClass(sClass); } catch (RuntimeException e) { throw report(sURI, nTypeId, sClass, e, "Unable to load class for user type"); } // check if it is an interface or abstract class if (clz.isInterface()) { if (!fAllowInterfaces) { throw report(sURI, nTypeId, sClass, null, "User Type cannot be an interface (allow-interfaces=false)"); } } else if (Modifier.isAbstract(clz.getModifiers())) { if (!fAllowSubclasses) { throw report(sURI, nTypeId, sClass, null, "User Type cannot be an abstract class (allow-subclasses=false)"); } } // determine the serializer implementation, and register it XmlElement xmlSer = xmlType.getElement("serializer"); PofSerializer serializer; if (xmlSer == null) { if (PortableObject.class.isAssignableFrom(clz)) { serializer = new PortableObjectSerializer(nTypeId); } else if (clz.getAnnotation(Portable.class) == null) { throw 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; } // 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_aClzByTypeId = aClzByTypeId; cfg.m_aserByTypeId = aserByTypeId; cfg.m_fInterfaceAllowed = fAllowInterfaces; cfg.m_fSubclassAllowed = fAllowSubclasses; cfg.m_fReferenceEnabled = fEnableReferences; cfg.m_fPreferJavaTime = fPreferJavaTime; return cfg; } /** * 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 = Integer.valueOf(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; try { clzSer = loadClass(sSerClass); } catch (RuntimeException e) { throw report(sURI, nTypeId, clz.getName(), e, "Unable to load PofSerializer class: " + sSerClass); } if (!PofSerializer.class.isAssignableFrom(clzSer)) { throw 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 throw 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; try { aoParams = XmlHelper.parseInitParams(xmlParams, resolver); } catch (RuntimeException e) { throw 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) { throw 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 * * @return this method does not return; it always throws an exception * * @throws IllegalStateException always thrown */ protected RuntimeException 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); } /** * 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(); XmlElement xmlAllTypes = xmlConfig.getElement("user-type-list"); // add default-serializer to each user-type appendDefaultSerializerToUserTypes(xmlConfig); // 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(); for (Iterator iter = xmlAllTypes.getElements("include"); iter.hasNext(); ) { String sIncludeURI = ((XmlElement) iter.next()).getString(); iter.remove(); if (!listURI.contains(sIncludeURI)) { listURI.add(sIncludeURI); listInclude.add(XmlHelper.loadFileOrResource( sIncludeURI, "included 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(); XmlHelper.addElements(xmlAllTypes, xmlIncludeTypes.getElements("user-type")); XmlHelper.addElements(xmlAllTypes, xmlIncludeTypes.getElements("include")); } } xmlConfig.ensureElement("allow-interfaces").setBoolean(fAllowInterfaces); xmlConfig.ensureElement("allow-subclasses").setBoolean(fAllowSubclasses); xmlConfig.ensureElement("enable-references").setBoolean(fEnableReferences); } /** * 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; } // ----- 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; // ----- 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; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy