/******************************************************************************* * Copyright (c) 2012, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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<XPathQName, DESCRIPTOR> descriptorsByQName; protected Map<XPathFragment, DESCRIPTOR> descriptorsByGlobalType; protected SESSION session; private Collection<SESSION_EVENT_LISTENER> sessionEventListeners; protected ContextState() { descriptorsByQName = new HashMap<XPathQName, DESCRIPTOR>(); descriptorsByGlobalType = new HashMap<XPathFragment, DESCRIPTOR>(); } protected ContextState(Context context, PROJECT project, ClassLoader classLoader, Collection<SESSION_EVENT_LISTENER> 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<DESCRIPTOR> processedDescriptors) { XPathQName descriptorQName; String defaultRootName; if (processedDescriptors == null) { processedDescriptors = new HashSet<DESCRIPTOR>(); } 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 != null && !(defaultRootLocalName.equals(Constants.EMPTY_STRING))){ 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<DESCRIPTOR> processedDescriptors = new HashSet<DESCRIPTOR>(); 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<ABSTRACT_SESSION, DESCRIPTOR, PROJECT, SESSION, SESSION_EVENT_LISTENER> contextState; private <T> T createByXPath(Object object, CoreObjectBuilder objectBuilder, StringTokenizer stringTokenizer, NAMESPACE_RESOLVER namespaceResolver, Class<T> 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 <T> * 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> T createByXPath(Object parentObject, String xPath, NAMESPACE_RESOLVER namespaceResolver, Class<T> 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); } /** * <p>Query the object model based on the corresponding document. The following pairings are equivalent:</p> * * <i>Return the Customer's ID</i> * <pre> Integer id = context.getValueByXPath(customer, "@id", null, Integer.class); * Integer id = customer.getId();</pre> * * <i>Return the Customer's Name</i> * <pre> String name = context.getValueByXPath(customer, "ns:personal-info/ns:name/text()", null, String.class); * String name = customer.getName();</pre> * * <i>Return the Customer's Address</i> * <pre> Address address = context.getValueByXPath(customer, "ns:contact-info/ns:address", aNamespaceResolver, Address.class); * Address address = customer.getAddress();</pre> * * <i>Return all the Customer's PhoneNumbers</i> * <pre> List phoneNumbers = context.getValueByXPath(customer, "ns:contact-info/ns:phone-number", aNamespaceResolver, List.class); * List phoneNumbers = customer.getPhoneNumbers();</pre> * * <i>Return the Customer's second PhoneNumber</i> * <pre> PhoneNumber phoneNumber = context.getValueByXPath(customer, "ns:contact-info/ns:phone-number[2]", aNamespaceResolver, PhoneNumber.class); * PhoneNumber phoneNumber = customer.getPhoneNumbers().get(1);</pre> * * <i>Return the base object</i> * <pre> Customer customer = context.getValueByXPath(customer, ".", aNamespaceResolver, Customer.class); * Customer customer = customer; * </pre> * * @param <T> 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> T getValueByXPath(Object object, String xPath, NAMESPACE_RESOLVER namespaceResolver, Class<T> 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> T getValueByXPath(Object object, CoreObjectBuilder objectBuilder, StringTokenizer stringTokenizer, NAMESPACE_RESOLVER namespaceResolver, Class<T> 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); } } } } /** * <p>Set values in the object model based on the corresponding document. The following pairings are equivalent:</p> * * <i>Set the Customer's ID</i> * <pre> context.setValueByXPath(customer, "@id", null, new Integer(123)); * customer.setId(new Integer(123));</pre> * * <i>Set the Customer's Name</i> * <pre> context.setValueByXPath(customer, "ns:personal-info/ns:name/text()", aNamespaceResolver, "Jane Doe"); * customer.setName("Jane Doe");</pre> * * <i>Set the Customer's Address</i> * <pre> context.setValueByXPath(customer, "ns:contact-info/ns:address", aNamespaceResolver, anAddress); * customer.setAddress(anAddress);</pre> * * <i>Set the Customer's PhoneNumbers</i> * <pre> context.setValueByXPath(customer, "ns:contact-info/ns:phone-number", aNamespaceResolver, phoneNumbers); * customer.setPhoneNumbers(phoneNumbers);</pre> * * <i>Set the Customer's second PhoneNumber</i> * <pre> context.setValueByXPath(customer, "ns:contact-info/ns:phone-number[2]", aNamespaceResolver, aPhoneNumber); * customer.getPhoneNumbers().get(1);</pre> * * @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); } }