org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 2011, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Rick Barkhouse - 2.1 - Initial implementation
package org.eclipse.persistence.jaxb.dynamic;
import java.io.InputStream;
import java.util.Map;
import jakarta.xml.bind.JAXBException;
import javax.xml.transform.Source;
import org.eclipse.persistence.internal.core.helper.CoreClassConstants;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.jaxb.JAXBContextFactory;
import org.eclipse.persistence.jaxb.JAXBContextProperties;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext.MetadataContextInput;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext.SchemaContextInput;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext.SessionsXmlContextInput;
import org.w3c.dom.Node;
import org.xml.sax.EntityResolver;
/**
*
* DynamicJAXBContextFactory allows the user to create a DynamicJAXBContext without having
* realized Java classes available on the classpath. During context creation, the user's
* metadata will be analyzed, and in-memory classes will be generated.
*
*
*
* Objects that are returned by EclipseLink unmarshal methods will be subclasses of DynamicEntity.
* DynamicEntities offer a simple get(propertyName) / set(propertyName, propertyValue) API to
* manipulate their data.
*
*
*
* Example:
*
*
*
* ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
* InputStream iStream = classLoader.getResourceAsStream("resource/MySchema.xsd");
*
* Map<String, Object> properties = new HashMap<String, Object>();
* properties.put(DynamicJAXBContextFactory.XML_SCHEMA_KEY, iStream);
*
* DynamicJAXBContext jaxbContext = (DynamicJAXBContext) JAXBContext.newInstance("org.example", classLoader, properties);
*
* DynamicEntity employee = jaxbContext.newDynamicEntity("org.example.Employee");
* employee.set("firstName", "Bob");
* employee.set("lastName", "Barker");
* jaxbContext.createMarshaller().(employee, System.out);
*
*
* @see jakarta.xml.bind.JAXBContext
* @see org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext
* @see org.eclipse.persistence.dynamic.DynamicEntity
* @see org.eclipse.persistence.dynamic.DynamicType
*
* @author rbarkhouse
* @since EclipseLink 2.1
*/
public class DynamicJAXBContextFactory {
public static final String XML_SCHEMA_KEY = "xml-schema";
public static final String ENTITY_RESOLVER_KEY = "entity-resolver";
public static final String EXTERNAL_BINDINGS_KEY = "external-bindings";
public static final String SCHEMAMETADATA_CLASS_NAME = "org.eclipse.persistence.jaxb.dynamic.metadata.SchemaMetadata";
/**
* Create a DynamicJAXBContext
, using either an XML Schema, EclipseLink OXM file,
* or EclipseLink sessions.xml
as the metadata source. This creation method will be
* called if the user calls the newInstance()
method on jakarta.xml.bind.JAXBContext
,
* and has specified jakarta.xml.bind.JAXBContextFactory=org.eclipse.persistence.jaxb.DynamicJAXBContextFactory
in their
* jaxb.properties
file.
*
* -- Context Creation From XML Schema --
*
* The properties
map must contain the following key/value pairs:
*
* - DynamicJAXBContextFactory.XML_SCHEMA_KEY
*
- Either a
org.w3c.dom.Node
, javax.xml.transform.Source
, or java.io.InputStream
pointing to the XML Schema
* - DynamicJAXBContextFactory.ENTITY_RESOLVER_KEY
*
- An
org.xml.sax.EntityResolver
, used to resolve schema imports. Can be null.
*
*
* Example:
*
* ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
* InputStream iStream = classLoader.getResourceAsStream("resource/MySchema.xsd");
*
* Map<String, Object> properties = new HashMap<String, Object>();
* properties.put(DynamicJAXBContextFactory.XML_SCHEMA_KEY, iStream);
*
* DynamicJAXBContext jaxbContext = (DynamicJAXBContext) JAXBContext.newInstance("org.example", classLoader, properties);
* DynamicEntity emp = jaxbContext.newDynamicEntity("org.example.Employee");
* ...
*
*
* Context Creation From EclipseLink OXM:
*
* The properties
map must contain the key JAXBContextProperties.OXM_METADATA_SOURCE, which can have
* several possible values:
*
*
* - One of the following, pointing to your OXM file:
java.io.File
, java.io.InputStream
, java.io.Reader
, java.net.URL
,
* javax.xml.stream.XMLEventReader
, javax.xml.stream.XMLStreamReader
, javax.xml.transform.Source
,
* org.w3c.dom.Node
, or org.xml.sax.InputSource
.
* - A
List
of objects from the set above.
* - A
Map<String, Object>
, where String
is a package name, and Object
is the pointer to the OXM file, from the set
* of possibilities above. If using this option, a package-name
element is not required in the xml-bindings
* element of your OXM file.
*
*
* Example:
*
* ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
* InputStream iStream = classLoader.getResourceAsStream("resource/eclipselink-oxm.xml");
*
* Map<String, Object> properties = new HashMap<String, Object>();
* properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, iStream);
*
* DynamicJAXBContext jaxbContext = (DynamicJAXBContext) JAXBContext.newInstance("org.example", classLoader, properties);
* DynamicEntity emp = jaxbContext.newDynamicEntity("org.example.Employee");
* ...
*
*
* Context Creation From EclipseLink sessions.xml:
*
* The sessionNames
parameter is a colon-delimited list of session names within the
* sessions.xml
file. Descriptors
in this session's Project
must not
* have javaClass
set, but must have javaClassName
set.
*
* Example:
*
* ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
*
* DynamicJAXBContext jaxbContext = (DynamicJAXBContext) JAXBContext.newInstance("org.example", classLoader, null);
* DynamicEntity emp = jaxbContext.newDynamicEntity("org.example.Employee");
* ...
*
*
* @param contextPath
* A colon-delimited String
specifying the packages containing jaxb.properties
. If bootstrapping
* from EclipseLink sessions.xml
, this will also be the name(s) of your sessions.
* @param classLoader
* The application's current class loader, which will be used to first lookup
* classes to see if they exist before new DynamicTypes
are generated. Can be
* null
, in which case Thread.currentThread().getContextClassLoader()
will be used.
* @param properties
* Map of properties to use when creating a new DynamicJAXBContext
. Can be null if bootstrapping from sessions.xml.
*
* @return
* A new instance of DynamicJAXBContext
.
*
* @throws JAXBException
* if an error was encountered while creating the DynamicJAXBContext
.
*/
public static DynamicJAXBContext createContext(String contextPath, ClassLoader classLoader, Map properties) throws JAXBException {
Object schema = null;
EntityResolver resolver = null;
Object bindings = null;
if (properties != null) {
schema = properties.get(XML_SCHEMA_KEY);
resolver = (EntityResolver) properties.get(ENTITY_RESOLVER_KEY);
if ((bindings = properties.get(JAXBContextProperties.OXM_METADATA_SOURCE)) == null) {
// try looking up the 'old' metadata source key
bindings = properties.get(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY);
}
}
// First try looking for an XSD
if (schema != null) {
if (schema instanceof Node) {
return createContextFromXSD((Node) schema, resolver, classLoader, properties);
}
if (schema instanceof InputStream) {
return createContextFromXSD((InputStream) schema, resolver, classLoader, properties);
}
if (schema instanceof Source) {
return createContextFromXSD((Source) schema, resolver, classLoader, properties);
}
}
// Next, check for OXM
if (bindings != null) {
return createContextFromOXM(classLoader, properties);
}
// Lastly, try sessions.xml
if (contextPath != null) {
return new DynamicJAXBContext(new SessionsXmlContextInput(contextPath, properties, classLoader));
} else {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.nullSessionName());
}
}
/**
* Unsupported Operation. DynamicJAXBConexts can not be created from concrete classes. Use the standard
* JAXBContext to create a context from existing Classes.
*
* @see org.eclipse.persistence.jaxb.JAXBContext
*/
public static DynamicJAXBContext createContext(Class>[] classes, Map properties) throws JAXBException {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.cannotCreateDynamicContextFromClasses());
}
/**
* Create a DynamicJAXBContext
, using XML Schema as the metadata source.
*
* @param schemaDOM
* org.w3c.dom.Node
representing the XML Schema.
* @param resolver
* An org.xml.sax.EntityResolver
, used to resolve schema imports. Can be null.
* @param classLoader
* The application's current class loader, which will be used to first lookup
* classes to see if they exist before new DynamicTypes
are generated. Can be
* null
, in which case Thread.currentThread().getContextClassLoader()
will be used.
* @param properties
* Map of properties to use when creating a new DynamicJAXBContext
. Can be null.
*
* @return
* A new instance of DynamicJAXBContext
.
*
* @throws JAXBException
* if an error was encountered while creating the DynamicJAXBContext
.
*/
public static DynamicJAXBContext createContextFromXSD(Node schemaDOM, EntityResolver resolver, ClassLoader classLoader, Map properties) throws JAXBException {
if (schemaDOM == null) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.nullNode());
}
if (resolver != null) {
// If schema and resolver are both specified, this indicates a schema import
// This is not supported when boostrapping from a Node.
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.xsdImportNotSource());
}
DynamicJAXBContext ctx = new DynamicJAXBContext(new SchemaContextInput(schemaDOM, null, properties, classLoader));
fixDateTimeConversion(ctx);
return ctx;
}
/**
* Create a DynamicJAXBContext
, using XML Schema as the metadata source.
*
* @param schemaStream
* java.io.InputStream
from which to read the XML Schema.
* @param resolver
* An org.xml.sax.EntityResolver
, used to resolve schema imports. Can be null.
* @param classLoader
* The application's current class loader, which will be used to first lookup
* classes to see if they exist before new DynamicTypes
are generated. Can be
* null
, in which case Thread.currentThread().getContextClassLoader()
will be used.
* @param properties
* Map of properties to use when creating a new DynamicJAXBContext
. Can be null.
*
* @return
* A new instance of DynamicJAXBContext
.
*
* @throws JAXBException
* if an error was encountered while creating the DynamicJAXBContext
.
*/
public static DynamicJAXBContext createContextFromXSD(InputStream schemaStream, EntityResolver resolver, ClassLoader classLoader, Map properties) throws JAXBException {
if (schemaStream == null) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.nullInputStream());
}
DynamicJAXBContext ctx = new DynamicJAXBContext(new SchemaContextInput(schemaStream, resolver, properties, classLoader));
fixDateTimeConversion(ctx);
return ctx;
}
/**
* Create a DynamicJAXBContext
, using XML Schema as the metadata source.
*
* @param schemaSource
* javax.xml.transform.Source
from which to read the XML Schema.
* @param resolver
* An org.xml.sax.EntityResolver
, used to resolve schema imports. Can be null.
* @param classLoader
* The application's current class loader, which will be used to first lookup
* classes to see if they exist before new DynamicTypes
are generated. Can be
* null
, in which case Thread.currentThread().getContextClassLoader()
will be used.
* @param properties
* Map of properties to use when creating a new DynamicJAXBContext
. Can be null.
*
* @return
* A new instance of DynamicJAXBContext
.
*
* @throws JAXBException
* if an error was encountered while creating the DynamicJAXBContext
.
*/
public static DynamicJAXBContext createContextFromXSD(Source schemaSource, EntityResolver resolver, ClassLoader classLoader, Map properties) throws JAXBException {
if (schemaSource == null) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.nullSource());
}
DynamicJAXBContext ctx = new DynamicJAXBContext(new SchemaContextInput(schemaSource, resolver, properties, classLoader));
fixDateTimeConversion(ctx);
return ctx;
}
/**
* Create a DynamicJAXBContext
, using an EclipseLink OXM file as the metadata source.
*
* @param classLoader
* The application's current class loader, which will be used to first lookup
* classes to see if they exist before new DynamicTypes
are generated. Can be
* null
, in which case Thread.currentThread().getContextClassLoader()
will be used.
* @param properties
* Map of properties to use when creating a new DynamicJAXBContext
. This map must
* contain a key of JAXBContextProperties.OXM_METADATA_SOURCE, which can have several possible values:
*
*
* - One of the following, pointing to your OXM file:
java.io.File
, java.io.InputStream
, java.io.Reader
, java.net.URL
,
* javax.xml.stream.XMLEventReader
, javax.xml.stream.XMLStreamReader
, javax.xml.transform.Source
,
* org.w3c.dom.Node
, or org.xml.sax.InputSource
.
* - A
List
of objects from the set above.
* - A
Map<String, Object>
, where String
is a package name, and Object
is the pointer to the OXM file, from the set
* of possibilities above. If using this option, a package-name
element is not required in the xml-bindings
* element of your OXM file.
*
*
*
* @return
* A new instance of DynamicJAXBContext
.
*
* @throws JAXBException
* if an error was encountered while creating the DynamicJAXBContext
.
*/
public static DynamicJAXBContext createContextFromOXM(ClassLoader classLoader, Map properties) throws JAXBException {
if (properties == null || (properties.get(JAXBContextProperties.OXM_METADATA_SOURCE) == null && properties.get(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY) == null)) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.oxmKeyNotFound());
}
return new DynamicJAXBContext(new MetadataContextInput(properties, classLoader));
}
/**
* Ensures that XSD dateTimes will always be unmarshalled as XMLGregorianCalendars, and never
* as GregorianCalendars. CALENDAR entries are removed from the default XMLConversionManager,
* and replaced with XML_GREGORIAN_CALENDAR.
*/
private static void fixDateTimeConversion(DynamicJAXBContext ctx) {
XMLConversionManager conversionManager = (XMLConversionManager) ctx.getXMLContext().getSession().getDatasourcePlatform().getConversionManager();
Map defaultXmlTypes = XMLConversionManager.getDefaultXMLTypes();
defaultXmlTypes.remove(Constants.DATE_TIME_QNAME);
defaultXmlTypes.put(Constants.DATE_TIME_QNAME, CoreClassConstants.XML_GREGORIAN_CALENDAR);
Map defaultJavaTypes = XMLConversionManager.getDefaultJavaTypes();
defaultJavaTypes.remove(CoreClassConstants.CALENDAR);
defaultJavaTypes.put(CoreClassConstants.XML_GREGORIAN_CALENDAR, Constants.DATE_TIME_QNAME);
}
}