org.eclipse.persistence.internal.oxm.Context Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eclipselink Show documentation
Show all versions of eclipselink Show documentation
EclipseLink build based upon Git transaction f2b9fc5
/*
* Copyright (c) 2012, 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:
// Blaise Doughan - 2.5 - initial implementation
package org.eclipse.persistence.internal.oxm;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.xml.namespace.QName;
import org.eclipse.persistence.core.mappings.CoreMapping;
import org.eclipse.persistence.core.sessions.CoreProject;
import org.eclipse.persistence.core.sessions.CoreSession;
import org.eclipse.persistence.core.sessions.CoreSessionEventListener;
import org.eclipse.persistence.exceptions.XMLMarshalException;
import org.eclipse.persistence.internal.core.databaseaccess.CorePlatform;
import org.eclipse.persistence.internal.core.descriptors.CoreObjectBuilder;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.mappings.Field;
import org.eclipse.persistence.oxm.schema.XMLSchemaReference;
public abstract class Context<
ABSTRACT_SESSION extends CoreAbstractSession,
DESCRIPTOR extends Descriptor, ?, ?, ?, ?, NAMESPACE_RESOLVER, ?, ?, ?, ?>,
FIELD extends Field,
NAMESPACE_RESOLVER extends NamespaceResolver,
PROJECT extends CoreProject,
SESSION extends CoreSession,
SESSION_EVENT_LISTENER extends CoreSessionEventListener> {
public static class ContextState<
ABSTRACT_SESSION extends CoreAbstractSession,
DESCRIPTOR extends Descriptor,
PROJECT extends CoreProject,
SESSION extends CoreSession,
SESSION_EVENT_LISTENER extends CoreSessionEventListener> {
protected Context context;
protected Map descriptorsByQName;
protected Map descriptorsByGlobalType;
protected SESSION session;
private Collection sessionEventListeners;
protected ContextState() {
descriptorsByQName = new HashMap<>();
descriptorsByGlobalType = new HashMap<>();
}
protected ContextState(Context context, PROJECT project, ClassLoader classLoader, Collection sessionEventListeners) {
this();
this.context = context;
preLogin(project, classLoader);
session = (SESSION) project.createDatabaseSession();
// if an event listener was passed in as a parameter, register it with the event manager
if (sessionEventListeners != null) {
for(SESSION_EVENT_LISTENER sessionEventListener : sessionEventListeners) {
session.getEventManager().addListener(sessionEventListener);
}
}
setupSession(session);
storeDescriptorsByQName(session);
}
public void addDescriptorByQName(QName qName, DESCRIPTOR descriptor) {
XPathQName xpathQName = new XPathQName(qName, true);
addDescriptorByQName(xpathQName, descriptor);
}
private void addDescriptorByQName(XPathQName qName, DESCRIPTOR descriptor) {
descriptorsByQName.put(qName, descriptor);
}
protected void preLogin(PROJECT project, ClassLoader classLoader) {
}
/**
* INTERNAL: Return the Descriptor with the default root mapping matching
* the QName parameter.
*/
private DESCRIPTOR getDescriptor(QName qName) {
XPathQName xpathQName = new XPathQName(qName, true);
return descriptorsByQName.get(xpathQName);
}
/**
* INTERNAL: Return the Descriptor with the default root mapping matching
* the QName parameter.
*/
private DESCRIPTOR getDescriptor(XPathQName qName) {
return descriptorsByQName.get(qName);
}
/**
* INTERNAL: Return the Descriptor mapped to the global type matching the
* XPathFragment parameter.
*/
private DESCRIPTOR getDescriptorByGlobalType(XPathFragment xPathFragment) {
return this.descriptorsByGlobalType.get(xPathFragment);
}
protected SESSION getSession() {
return session;
}
/**
* INTERNAL: Return the session corresponding to this class. Since the class
* may be mapped by more that one of the projects used to create the
* Context, this method will return the first match.
*/
protected ABSTRACT_SESSION getSession(Class clazz) {
if (null == clazz) {
return null;
}
if (session.getDescriptor(clazz) != null) {
return (ABSTRACT_SESSION) session;
}
throw XMLMarshalException.descriptorNotFoundInProject(clazz.getName());
}
/**
* INTERNAL: Return the session corresponding to this Descriptor. Since
* the class may be mapped by more that one of the projects used to create
* the Context, this method will return the first match.
*/
protected ABSTRACT_SESSION getSession(DESCRIPTOR descriptor) {
if (null == descriptor) {
return null;
}
if (session.getProject().getOrderedDescriptors().contains(descriptor)) {
return (ABSTRACT_SESSION) session;
}
throw XMLMarshalException.descriptorNotFoundInProject(descriptor.getJavaClass().getName());
}
/**
* INTERNAL: Return the session corresponding to this object. Since the
* object may be mapped by more that one of the projects used to create the
* Context, this method will return the first match.
*/
protected ABSTRACT_SESSION getSession(Object object) {
if (null == object) {
return null;
}
if (session.getDescriptor(object) != null) {
return (ABSTRACT_SESSION) session;
}
throw XMLMarshalException.descriptorNotFoundInProject(object.getClass().getName());
}
protected void setupSession(SESSION session) {
}
/**
* INTERNAL:
*/
public void storeDescriptorByQName(DESCRIPTOR descriptor, CorePlatform platform, Set processedDescriptors) {
XPathQName descriptorQName;
String defaultRootName;
if (processedDescriptors == null) {
processedDescriptors = new HashSet<>();
}
if (processedDescriptors.contains(descriptor)) {
return;
} else {
processedDescriptors.add(descriptor);
if (descriptor.hasInheritance() && !descriptor.getInheritancePolicy().isRootParentDescriptor()) {
//this means we have a descriptor that is a child in an inheritance hierarchy
storeDescriptorByQName((DESCRIPTOR) descriptor.getInheritancePolicy().getParentDescriptor(), platform, processedDescriptors);
}
}
List tableNames = descriptor.getTableNames();
for (int i = 0; i < tableNames.size(); i++) {
defaultRootName = (String) tableNames.get(i);
if (null != defaultRootName) {
int index = defaultRootName.indexOf(':');
String defaultRootLocalName = defaultRootName.substring(index + 1);
if (!defaultRootLocalName.isEmpty()) {
if (index > -1) {
String defaultRootPrefix = defaultRootName.substring(0, index);
String defaultRootNamespaceURI = descriptor.getNamespaceResolver().resolveNamespacePrefix(defaultRootPrefix);
descriptorQName = new XPathQName(defaultRootNamespaceURI, defaultRootLocalName, true);
} else {
if(descriptor.getNamespaceResolver() != null) {
descriptorQName = new XPathQName(descriptor.getNamespaceResolver().getDefaultNamespaceURI(), defaultRootLocalName, true);
} else {
descriptorQName = new XPathQName(defaultRootLocalName, true);
}
}
if (!descriptor.hasInheritance() || descriptor.getInheritancePolicy().isRootParentDescriptor()) {
addDescriptorByQName(descriptorQName, descriptor);
} else {
Descriptor existingDescriptor = getDescriptor(descriptorQName);
if (existingDescriptor == null) {
addDescriptorByQName(descriptorQName, descriptor);
}
}
}
}
}
XMLSchemaReference xmlSchemaReference = descriptor.getSchemaReference();
if (null != xmlSchemaReference) {
String schemaContext = xmlSchemaReference.getSchemaContext();
if ((xmlSchemaReference.getType() == XMLSchemaReference.COMPLEX_TYPE) || (xmlSchemaReference.getType() == XMLSchemaReference.SIMPLE_TYPE)) {
if ((null != schemaContext) && (schemaContext.lastIndexOf('/') == 0)) {
schemaContext = schemaContext.substring(1, schemaContext.length());
XPathFragment typeFragment = new XPathFragment(schemaContext);
if (null != descriptor.getNamespaceResolver()) {
String uri = descriptor.getNamespaceResolver().resolveNamespacePrefix(typeFragment.getPrefix());
if(uri == null && xmlSchemaReference.getSchemaContextAsQName() != null){
uri = xmlSchemaReference.getSchemaContextAsQName().getNamespaceURI();
}
typeFragment.setNamespaceURI(uri);
}
this.descriptorsByGlobalType.put(typeFragment, descriptor);
} else {
QName qname = xmlSchemaReference.getSchemaContextAsQName();
if (qname != null) {
if (descriptor.isWrapper() && descriptor.getJavaClassName().contains("ObjectWrapper")) {
return;
}
XPathFragment typeFragment = new XPathFragment();
typeFragment.setLocalName(qname.getLocalPart());
typeFragment.setNamespaceURI(qname.getNamespaceURI());
this.descriptorsByGlobalType.put(typeFragment, descriptor);
}
}
}
}
}
public void storeDescriptorsByQName(CoreSession session) {
Iterator iterator = session.getProject().getOrderedDescriptors().iterator();
Set processedDescriptors = new HashSet<>();
while (iterator.hasNext()) {
DESCRIPTOR descriptor = (DESCRIPTOR) iterator.next();
storeDescriptorByQName(descriptor, session.getDatasourcePlatform(), processedDescriptors);
}
}
}
private class XPathQueryResult {
/*
* Mapping corresponding to the XPath query
*/
private CoreMapping mapping;
/*
* Mapping's owning object
*/
private Object owner;
/*
* Index into mapping, from XPath query (may be null)
*/
private Integer index;
}
protected volatile ContextState contextState;
private T createByXPath(Object object, CoreObjectBuilder objectBuilder, StringTokenizer stringTokenizer, NAMESPACE_RESOLVER namespaceResolver, Class returnType) {
XPathQueryResult queryResult = getMappingForXPath(object, objectBuilder, stringTokenizer, namespaceResolver);
if (null != queryResult.mapping) {
DESCRIPTOR refDescriptor = (DESCRIPTOR) queryResult.mapping.getReferenceDescriptor();
if (null != refDescriptor) {
return (T) refDescriptor.getInstantiationPolicy().buildNewInstance();
}
}
return null;
}
/**
* Create a new object instance for a given XPath, relative to the parentObject.
*
* @param
* The return type of this method corresponds to the returnType parameter.
* @param parentObject
* The XPath will be executed relative to this object.
* @param xPath
* The XPath statement.
* @param namespaceResolver
* A NamespaceResolver containing the prefix/URI pairings from the XPath statement.
* @param returnType
* The return type.
*
* @return
* An instance of the Java class mapped to the supplied return type, or null
* if no result was found.
*/
public T createByXPath(Object parentObject, String xPath, NAMESPACE_RESOLVER namespaceResolver, Class returnType) {
ABSTRACT_SESSION session = this.getSession(parentObject);
DESCRIPTOR descriptor = (DESCRIPTOR) session.getDescriptor(parentObject);
StringTokenizer stringTokenizer = new StringTokenizer(xPath, "/");
return createByXPath(parentObject, descriptor.getObjectBuilder(), stringTokenizer, namespaceResolver, returnType);
}
protected abstract FIELD createField(String path);
public abstract Marshaller createMarshaller();
public abstract Unmarshaller createUnmarshaller();
/**
* INTERNAL:
* Return the Descriptor with the default root mapping matching the QName
* parameter.
*/
public DESCRIPTOR getDescriptor(QName qName) {
XPathQName xpathQName = new XPathQName(qName, true);
return contextState.getDescriptor(xpathQName);
}
/**
* INTERNAL:
* Return the Descriptor with the default root mapping matching the
* XPathQName parameter.
*/
public DESCRIPTOR getDescriptor(XPathQName xpathQName) {
return contextState.getDescriptor(xpathQName);
}
/**
* INTERNAL:
* Return the Descriptor mapped to the global type matching the
* XPathFragment parameter.
*/
public DESCRIPTOR getDescriptorByGlobalType(XPathFragment xPathFragment) {
return contextState.getDescriptorByGlobalType(xPathFragment);
}
private XPathQueryResult getMappingForXPath(Object object, CoreObjectBuilder objectBuilder, StringTokenizer stringTokenizer, NAMESPACE_RESOLVER namespaceResolver) {
XPathQueryResult queryResult = new XPathQueryResult();
String xPath = "";
FIELD field = createField(null);
field.setNamespaceResolver(namespaceResolver);
while (stringTokenizer.hasMoreElements()) {
String nextToken = stringTokenizer.nextToken();
field.setXPath(xPath + nextToken);
field.initialize();
CoreMapping mapping = objectBuilder.getMappingForField(field);
if (null == mapping) {
// XPath might have indexes, while the mapping's XPath may not,
// so remove them and look again
XPathFragment xPathFragment = new XPathFragment(nextToken);
int fieldIndex = field.getXPathFragment().getIndexValue();
int fragmentIndex = xPathFragment.getIndexValue();
if (fieldIndex > 0 || fragmentIndex > 0) {
int index = fieldIndex - 1;
if (index < 0) {
index = fragmentIndex - 1;
}
String xPathNoIndexes = removeIndexesFromXPath(field.getXPath());
field.setXPath(xPathNoIndexes);
field.initialize();
mapping = objectBuilder.getMappingForField(field);
if (null == mapping) {
// Try adding /text()
field.setXPath(xPathNoIndexes + Constants.XPATH_SEPARATOR + Constants.TEXT);
field.initialize();
mapping = objectBuilder.getMappingForField(field);
}
if (null != mapping) {
if (field.getXPath().endsWith(Constants.TEXT) || !stringTokenizer.hasMoreElements()) {
// End of the line, we found a mapping so return it
queryResult.mapping = mapping;
queryResult.owner = object;
queryResult.index = index;
return queryResult;
} else {
// We need to keep looking -- get the mapping value,
// then recurse into getMappingForXPath with new root object
Object childObject = mapping.getAttributeValueFromObject(object);
if (mapping.isCollectionMapping()) {
Object collection = mapping.getAttributeValueFromObject(object);
if (null != collection && List.class.isAssignableFrom(collection.getClass())) {
List list = (List) collection;
if (index >= list.size()) {
// Index used in query is out of range, no matches
return null;
}
childObject = list.get(index);
}
}
if (null == childObject) {
childObject = mapping.getReferenceDescriptor().getObjectBuilder().buildNewInstance();
}
CoreObjectBuilder childObjectBuilder = mapping.getReferenceDescriptor().getObjectBuilder();
return getMappingForXPath(childObject, childObjectBuilder, stringTokenizer, namespaceResolver);
}
}
}
} else {
if (!stringTokenizer.hasMoreElements()) {
// End of the line, we found a mapping so return it
queryResult.mapping = mapping;
queryResult.owner = object;
return queryResult;
} else {
// We need to keep looking -- get the mapping value,
// then recurse into getMappingForXPath with new root object
Object childObject = mapping.getAttributeValueFromObject(object);
if (mapping.isCollectionMapping()) {
Object collection = mapping.getAttributeValueFromObject(object);
if (null != collection && List.class.isAssignableFrom(collection.getClass())) {
List list = (List) collection;
if (0 >= list.size()) {
return null;
}
childObject = list.get(0);
}
}
if (null == childObject) {
childObject = mapping.getReferenceDescriptor().getObjectBuilder().buildNewInstance();
}
CoreObjectBuilder childObjectBuilder = mapping.getReferenceDescriptor().getObjectBuilder();
return getMappingForXPath(childObject, childObjectBuilder, stringTokenizer, namespaceResolver);
}
}
xPath = xPath + nextToken + Constants.XPATH_SEPARATOR;
}
return null;
}
/**
* INTERNAL:
* Return the session corresponding to this class. Since the class
* may be mapped by more that one of the projects used to create the
* Context, this method will return the first match.
*/
public ABSTRACT_SESSION getSession(Class clazz) {
return contextState.getSession(clazz);
}
/**
* INTERNAL:
*/
public SESSION getSession() {
return contextState.getSession();
}
public ABSTRACT_SESSION getSession(DESCRIPTOR descriptor) {
return contextState.getSession(descriptor);
}
/**
* INTERNAL:
* Return the session corresponding to this object. Since the
* object may be mapped by more that one of the projects used to create the
* Context, this method will return the first match.
*/
public ABSTRACT_SESSION getSession(Object object) {
return contextState.getSession(object);
}
/**
* Query the object model based on the corresponding document. The following pairings are equivalent:
*
* Return the Customer's ID
* Integer id = context.getValueByXPath(customer, "@id", null, Integer.class);
* Integer id = customer.getId();
*
* Return the Customer's Name
* String name = context.getValueByXPath(customer, "ns:personal-info/ns:name/text()", null, String.class);
* String name = customer.getName();
*
* Return the Customer's Address
* Address address = context.getValueByXPath(customer, "ns:contact-info/ns:address", aNamespaceResolver, Address.class);
* Address address = customer.getAddress();
*
* Return all the Customer's PhoneNumbers
* List phoneNumbers = context.getValueByXPath(customer, "ns:contact-info/ns:phone-number", aNamespaceResolver, List.class);
* List phoneNumbers = customer.getPhoneNumbers();
*
* Return the Customer's second PhoneNumber
* PhoneNumber phoneNumber = context.getValueByXPath(customer, "ns:contact-info/ns:phone-number[2]", aNamespaceResolver, PhoneNumber.class);
* PhoneNumber phoneNumber = customer.getPhoneNumbers().get(1);
*
* Return the base object
* Customer customer = context.getValueByXPath(customer, ".", aNamespaceResolver, Customer.class);
* Customer customer = customer;
*
*
* @param The return type of this method corresponds to the returnType parameter.
* @param object The XPath will be executed relative to this object.
* @param xPath The XPath statement
* @param namespaceResolver A NamespaceResolver containing the prefix/URI pairings from the XPath statement.
* @param returnType The return type.
* @return The object corresponding to the XPath or null if no result was found.
*/
public T getValueByXPath(Object object, String xPath, NAMESPACE_RESOLVER namespaceResolver, Class returnType) {
if (null == xPath || null == object) {
return null;
}
if (".".equals(xPath)) {
return (T) object;
}
ABSTRACT_SESSION session = this.getSession(object);
DESCRIPTOR descriptor = (DESCRIPTOR) session.getDescriptor(object);
StringTokenizer stringTokenizer = new StringTokenizer(xPath, Constants.XPATH_SEPARATOR);
T value = getValueByXPath(object, descriptor.getObjectBuilder(), stringTokenizer, namespaceResolver, returnType);
if (null == value) {
CoreMapping selfMapping = descriptor.getObjectBuilder().getMappingForField(createField(String.valueOf(Constants.DOT)));
if (null != selfMapping && selfMapping.getReferenceDescriptor() != null) {
return getValueByXPath(selfMapping.getAttributeValueFromObject(object), selfMapping.getReferenceDescriptor().getObjectBuilder(),
new StringTokenizer(xPath, Constants.XPATH_SEPARATOR), ((DESCRIPTOR) selfMapping.getReferenceDescriptor()).getNamespaceResolver(), returnType);
}
}
return value;
}
private T getValueByXPath(Object object, CoreObjectBuilder objectBuilder, StringTokenizer stringTokenizer, NAMESPACE_RESOLVER namespaceResolver, Class returnType) {
XPathQueryResult queryResult = getMappingForXPath(object, objectBuilder, stringTokenizer, namespaceResolver);
if (null != queryResult) {
CoreMapping mapping = queryResult.mapping;
Object owner = queryResult.owner;
Integer index = queryResult.index;
if (null != owner) {
Object childObject = null;
if (mapping.isCollectionMapping()) {
Object collection = mapping.getAttributeValueFromObject(owner);
if (List.class.isAssignableFrom(collection.getClass())) {
List list = (List) collection;
if (null == index) {
return (T) collection;
}
if (index >= list.size()) {
return null;
}
childObject = list.get(index);
}
} else {
childObject = mapping.getAttributeValueFromObject(owner);
}
return (T) childObject;
}
}
return null;
}
/**
* INTERNAL:
* Return true if any session held onto by this context has a document preservation
* policy that requires unmarshalling from a Node.
*/
public abstract boolean hasDocumentPreservation();
private String removeIndexesFromXPath(String xpathWithIndexes) {
String newXPath = xpathWithIndexes;
while (newXPath.contains(Constants.XPATH_INDEX_OPEN)) {
int open = newXPath.lastIndexOf(Constants.XPATH_INDEX_OPEN);
int closed = newXPath.lastIndexOf(Constants.XPATH_INDEX_CLOSED);
newXPath = newXPath.substring(0, open) + newXPath.substring(closed + 1);
}
return newXPath;
}
private void setValueByXPath(Object object, CoreObjectBuilder objectBuilder, StringTokenizer stringTokenizer, NAMESPACE_RESOLVER namespaceResolver, Object value) {
XPathQueryResult queryResult = getMappingForXPath(object, objectBuilder, stringTokenizer, namespaceResolver);
if (null != queryResult) {
CoreMapping mapping = queryResult.mapping;
Object owner = queryResult.owner;
Integer index = queryResult.index;
if (null != owner) {
if (mapping.isCollectionMapping()) {
Object childObject = null;
Object collection = mapping.getAttributeValueFromObject(owner);
if (List.class.isAssignableFrom(collection.getClass())) {
List list = (List) collection;
if (null == index) {
// We are setting the whole collection, not an element in the collection
if (value.getClass().isArray()) {
ArrayList newList = new ArrayList();
int length = Array.getLength(value);
for (int i = 0; i < length; i++) {
newList.add(Array.get(value, i));
}
value = newList;
}
mapping.setAttributeValueInObject(owner, value);
return;
}
if (index >= list.size()) {
return;
}
// Set into collection
list.set(index, value);
mapping.setAttributeValueInObject(owner, list);
return;
}
} else {
mapping.setAttributeValueInObject(owner, value);
}
}
}
}
/**
* Set values in the object model based on the corresponding document. The following pairings are equivalent:
*
* Set the Customer's ID
* context.setValueByXPath(customer, "@id", null, Integer.valueOf(123));
* customer.setId(Integer.valueOf(123));
*
* Set the Customer's Name
* context.setValueByXPath(customer, "ns:personal-info/ns:name/text()", aNamespaceResolver, "Jane Doe");
* customer.setName("Jane Doe");
*
* Set the Customer's Address
* context.setValueByXPath(customer, "ns:contact-info/ns:address", aNamespaceResolver, anAddress);
* customer.setAddress(anAddress);
*
* Set the Customer's PhoneNumbers
* context.setValueByXPath(customer, "ns:contact-info/ns:phone-number", aNamespaceResolver, phoneNumbers);
* customer.setPhoneNumbers(phoneNumbers);
*
* Set the Customer's second PhoneNumber
* context.setValueByXPath(customer, "ns:contact-info/ns:phone-number[2]", aNamespaceResolver, aPhoneNumber);
* customer.getPhoneNumbers().get(1);
*
* @param object The XPath will be executed relative to this object.
* @param xPath The XPath statement
* @param namespaceResolver A NamespaceResolver containing the prefix/URI pairings from the XPath statement.
* @param value The value to be set.
*/
public void setValueByXPath(Object object, String xPath, NAMESPACE_RESOLVER namespaceResolver, Object value) {
ABSTRACT_SESSION session = this.getSession(object);
DESCRIPTOR descriptor = (DESCRIPTOR) session.getDescriptor(object);
StringTokenizer stringTokenizer = new StringTokenizer(xPath, "/");
setValueByXPath(object, descriptor.getObjectBuilder(), stringTokenizer, namespaceResolver, value);
}
}