/******************************************************************************* * Copyright (c) 1998, 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: * Oracle - initial API and implementation from Oracle TopLink * 14/05/2012-2.4 Guy Pelletier * - 376603: Provide for table per tenant support for multitenant applications ******************************************************************************/ package org.eclipse.persistence.oxm; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.StringTokenizer; import java.util.Vector; import javax.xml.namespace.QName; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.descriptors.InheritancePolicy; import org.eclipse.persistence.exceptions.DatabaseException; import org.eclipse.persistence.exceptions.DescriptorException; import org.eclipse.persistence.exceptions.XMLMarshalException; import org.eclipse.persistence.internal.descriptors.InstantiationPolicy; import org.eclipse.persistence.internal.descriptors.ObjectBuilder; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.helper.DatabaseTable; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.helper.NonSynchronizedVector; import org.eclipse.persistence.internal.oxm.Root; import org.eclipse.persistence.internal.oxm.TreeObjectBuilder; import org.eclipse.persistence.internal.oxm.Unmarshaller; import org.eclipse.persistence.internal.oxm.XPathFragment; import org.eclipse.persistence.internal.oxm.XPathQName; import org.eclipse.persistence.internal.oxm.mappings.Descriptor; import org.eclipse.persistence.internal.oxm.record.UnmarshalRecord; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.mappings.AggregateMapping; import org.eclipse.persistence.mappings.AttributeAccessor; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; import org.eclipse.persistence.oxm.mappings.XMLChoiceCollectionMapping; import org.eclipse.persistence.oxm.mappings.XMLChoiceObjectMapping; import org.eclipse.persistence.oxm.mappings.XMLCompositeCollectionMapping; import org.eclipse.persistence.oxm.mappings.XMLCompositeDirectCollectionMapping; import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping; import org.eclipse.persistence.oxm.mappings.XMLDirectMapping; import org.eclipse.persistence.oxm.mappings.XMLMapping; import org.eclipse.persistence.oxm.record.XMLRecord; import org.eclipse.persistence.oxm.schema.XMLSchemaReference; import org.eclipse.persistence.queries.AttributeGroup; import org.eclipse.persistence.queries.DoesExistQuery; /** * Use an XML project for nontransactional, nonpersistent (in-memory) conversions between Java objects and XML documents. * * An XMLDescriptor is a set of mappings that describe how an objects's data is to be represented in an * XML document. XML descriptors describe Java objects that you map to simple and complex types defined * by an XML schema document (XSD). Using XML descriptors in an EclipseLink XML project, you can configure XML mappings. * * @see org.eclipse.persistence.oxm.mappings */ public class XMLDescriptor extends ClassDescriptor implements Descriptor<AttributeAccessor, DatabaseMapping, DatabaseField, InheritancePolicy, InstantiationPolicy, NamespaceResolver, ObjectBuilder, DatabaseTable, UnmarshalRecord, XMLUnmarshaller>{ /* * Character used to separate individual xPath elements. * TODO: Use some global value reference. */ private static final char XPATH_FRAGMENT_SEPARATOR = '/'; private static final Vector EMPTY_VECTOR = NonSynchronizedVector.newInstance(1); private NamespaceResolver namespaceResolver; private XMLSchemaReference schemaReference; private boolean shouldPreserveDocument = false; private XMLField defaultRootElementField; private boolean sequencedObject = false; private boolean isWrapper = false; private boolean resultAlwaysXMLRoot = false; private boolean lazilyInitialized = false; private AttributeAccessor locationAccessor = null; private boolean hasReferenceMappings = false; /** * PUBLIC: * Return a new XMLDescriptor. */ public XMLDescriptor() { this.tables = NonSynchronizedVector.newInstance(3); this.mappings = NonSynchronizedVector.newInstance(); this.primaryKeyFields = null; this.fields = NonSynchronizedVector.newInstance(); this.allFields = NonSynchronizedVector.newInstance(); this.constraintDependencies = EMPTY_VECTOR; this.multipleTableForeignKeys = Collections.EMPTY_MAP; this.queryKeys = Collections.EMPTY_MAP; this.initializationStage = UNINITIALIZED; this.interfaceInitializationStage = UNINITIALIZED; this.descriptorType = NORMAL; this.shouldOrderMappings = true; this.shouldBeReadOnly = false; this.shouldAlwaysConformResultsInUnitOfWork = false; this.shouldAcquireCascadedLocks = false; this.hasSimplePrimaryKey = false; this.idValidation = null; this.derivesIdMappings = Collections.EMPTY_MAP; this.additionalWritableMapKeyFields = Collections.EMPTY_LIST; // Policies this.objectBuilder = new TreeObjectBuilder(this); this.shouldOrderMappings = false; descriptorIsAggregate(); } /** * PUBLIC: * Return the default root element name for the ClassDescriptor * This value is stored in place of a table name * This value is mandatory for all root objects * @return the default root element specified on this ClassDescriptor */ public String getDefaultRootElement() { if (getTables().isEmpty()) { return null; } return getTables().firstElement().getName(); } /** * PUBLIC: * Return if unmapped information from the XML document should be maintained for this * descriptor * By default unmapped data is not preserved. * @return if this descriptor should preserve unmapped data */ public boolean shouldPreserveDocument() { return this.shouldPreserveDocument; } /** * PUBLIC: * Specifies that object built from this descriptor should retain any unmapped * information from their original XML Document when being written back out. * By default unmapped data is not preserved. * * @param shouldPreserveDocument if this descriptor should preserve unmapped data */ public void setShouldPreserveDocument(boolean shouldPreserveDocument) { this.shouldPreserveDocument = shouldPreserveDocument; } /** * PUBLIC: * Add a root element name for the Descriptor * This value is stored in place of a table name * @param rootElementName a root element to specify on this Descriptor */ public void addRootElement(String rootElementName) { if (rootElementName != null) { if (!getTableNames().contains(rootElementName)) { addTableName(rootElementName); } } } /** * PUBLIC: * Return the default root element name for the ClassDescriptor * This value is stored in place of a table name * This value is mandatory for all root objects * @param newDefaultRootElement the default root element to specify on this ClassDescriptor */ public void setDefaultRootElement(String newDefaultRootElement) { if(setDefaultRootElementField(newDefaultRootElement)) { int index = getTableNames().indexOf(newDefaultRootElement); if (index == 0) { return; } DatabaseTable databaseTable = new DatabaseTable(); databaseTable.setUseDelimiters(false); databaseTable.setName(newDefaultRootElement); if (index >= 0) { getTables().remove(index); getTables().add(0, databaseTable); } else { getTables().add(0, databaseTable); } } } /** * PUBLIC: * Return the NamespaceResolver associated with this descriptor * @return the NamespaceResolver associated with this descriptor * @see org.eclipse.persistence.oxm.NamespaceResolver */ public NamespaceResolver getNamespaceResolver() { return namespaceResolver; } public NamespaceResolver getNonNullNamespaceResolver() { if (namespaceResolver == null) { namespaceResolver = new NamespaceResolver(); } return namespaceResolver; } /** * PUBLIC: * The inheritance policy is used to define how a descriptor takes part in inheritance. * All inheritance properties for both child and parent classes is configured in inheritance policy. * Caution must be used in using this method as it lazy initializes an inheritance policy. * Calling this on a descriptor that does not use inheritance will cause problems, #hasInheritance() must always first be called. * @return the InheritancePolicy associated with this descriptor */ public InheritancePolicy getInheritancePolicy() { if (inheritancePolicy == null) { // Lazy initialize to conserve space in non-inherited classes. setInheritancePolicy(new org.eclipse.persistence.internal.oxm.QNameInheritancePolicy(this)); } return inheritancePolicy; } /** * PUBLIC: * Set the NamespaceResolver to associate with this descriptor * @param newNamespaceResolver the NamespaceResolver to associate with this descriptor * @see org.eclipse.persistence.oxm.NamespaceResolver */ public void setNamespaceResolver(NamespaceResolver newNamespaceResolver) { namespaceResolver = newNamespaceResolver; } /** * PUBLIC: * Return the SchemaReference associated with this descriptor * @return the SchemaReference associated with this descriptor * @see org.eclipse.persistence.oxm.schema */ public XMLSchemaReference getSchemaReference() { return schemaReference; } /** * PUBLIC: * Set the SchemaReference to associate with this descriptor * @param newSchemaReference the SchemaReference to associate with this descriptor * @see org.eclipse.persistence.oxm.schema */ public void setSchemaReference(XMLSchemaReference newSchemaReference) { schemaReference = newSchemaReference; } /** * PUBLIC: * Return if the descriptor maps to XML. */ @Override public boolean isXMLDescriptor() { return true; } /** * If true, the descriptor may be lazily initialized. This is useful if the * descriptor may not get used. */ public boolean isLazilyInitialized() { return lazilyInitialized; } /** * Specify in the descriptor may be lazily initialized. The default is * false. */ public void setLazilyInitialized(boolean shouldLazyInitiailize) { this.lazilyInitialized = shouldLazyInitiailize; } @Override public Vector<String> getPrimaryKeyFieldNames() { if(null == primaryKeyFields) { return new Vector<String>(0); } return super.getPrimaryKeyFieldNames(); } protected void validateMappingType(DatabaseMapping mapping) { if (!(mapping.isXMLMapping())) { throw DescriptorException.invalidMappingType(mapping); } } /** * INTERNAL: * Avoid SDK initialization. public void setQueryManager(DescriptorQueryManager queryManager) { this.queryManager = queryManager; if (queryManager != null) { queryManager.setDescriptor(this); } }*/ /** * INTERNAL: * Build(if necessary) and return the nested XMLRecord from the specified field value. * The field value should be an XMLRecord or and XMLElement */ public AbstractRecord buildNestedRowFromFieldValue(Object fieldValue) { if (fieldValue instanceof XMLRecord) { return (XMLRecord) fieldValue; } // BUG#2667762 - If the tag was empty this could be a string of whitespace. if (!(fieldValue instanceof Vector)) { return getObjectBuilder().createRecord(null); } Vector nestedRows = (Vector) fieldValue; if (nestedRows.isEmpty()) { return getObjectBuilder().createRecord(null); } else { // BUG#2667762 - If the tag was empty this could be a string of whitespace. if (!(nestedRows.firstElement() instanceof AbstractRecord)) { return getObjectBuilder().createRecord(null); } return (XMLRecord) nestedRows.firstElement(); } } /** * INTERNAL: * Build(if necessary) and return a Vector of the nested XMLRecords from the specified field value. * The field value should be a Vector, an XMLRecord, or an XMLElement */ public Vector buildNestedRowsFromFieldValue(Object fieldValue, AbstractSession session) { // BUG#2667762 - If the tag was empty this could be a string of whitespace. if (!(fieldValue instanceof Vector)) { return new Vector(0); } return (Vector) fieldValue; } /** * Return a new direct/basic mapping for this type of descriptor. */ @Override public AbstractDirectMapping newDirectMapping() { return new XMLDirectMapping(); } /** * Return a new aggregate/embedded mapping for this type of descriptor. */ @Override public AggregateMapping newAggregateMapping() { return new XMLCompositeObjectMapping(); } /** * Return a new aggregate collection/element collection mapping for this type of descriptor. */ @Override public DatabaseMapping newAggregateCollectionMapping() { return new XMLCompositeCollectionMapping(); } /** * Return a new direct collection/element collection mapping for this type of descriptor. */ @Override public DatabaseMapping newDirectCollectionMapping() { return new XMLCompositeDirectCollectionMapping(); } /** * PUBLIC: * Add a direct mapping to the receiver. The new mapping specifies that * an instance variable of the class of objects which the receiver describes maps in * the default manner for its type to the indicated database field. * * @param attributeName the name of an instance variable of the * class which the receiver describes. * @param xpathString the xpath of the xml element or attribute which corresponds * with the designated instance variable. * @return The newly created DatabaseMapping is returned. */ public DatabaseMapping addDirectMapping(String attributeName, String xpathString) { XMLDirectMapping mapping = new XMLDirectMapping(); mapping.setAttributeName(attributeName); mapping.setXPath(xpathString); return addMapping(mapping); } /** * PUBLIC: * Add a direct to node mapping to the receiver. The new mapping specifies that * a variable accessed by the get and set methods of the class of objects which * the receiver describes maps in the default manner for its type to the indicated * database field. */ public DatabaseMapping addDirectMapping(String attributeName, String getMethodName, String setMethodName, String xpathString) { XMLDirectMapping mapping = new XMLDirectMapping(); mapping.setAttributeName(attributeName); mapping.setSetMethodName(setMethodName); mapping.setGetMethodName(getMethodName); mapping.setXPath(xpathString); return addMapping(mapping); } @Override public void addPrimaryKeyFieldName(String fieldName) { addPrimaryKeyField(new XMLField(fieldName)); } @Override public void addPrimaryKeyField(DatabaseField field) { if (!(field instanceof XMLField)) { String fieldName = field.getName(); field = new XMLField(fieldName); } if(null == primaryKeyFields) { primaryKeyFields = new ArrayList<DatabaseField>(1); } super.addPrimaryKeyField(field); } @Override public void setPrimaryKeyFields(List<DatabaseField> thePrimaryKeyFields) { if(null == thePrimaryKeyFields) { return; } List<DatabaseField> xmlFields = new ArrayList(thePrimaryKeyFields.size()); Iterator<DatabaseField> it = thePrimaryKeyFields.iterator(); while (it.hasNext()) { DatabaseField field = it.next(); if (!(field instanceof XMLField)) { String fieldName = field.getName(); field = new XMLField(fieldName); } xmlFields.add(field); } super.setPrimaryKeyFields(xmlFields); } /** * INTERNAL: * Extract the direct values from the specified field value. * Return them in a vector. * The field value could be a vector or could be a text value if only a single value. */ public Vector buildDirectValuesFromFieldValue(Object fieldValue) throws DatabaseException { if (!(fieldValue instanceof Vector)) { Vector fieldValues = new Vector(1); fieldValues.add(fieldValue); return fieldValues; } return (Vector) fieldValue; } /** * INTERNAL: * Build the appropriate field value for the specified * set of direct values. * The database better be expecting a Vector. */ public Object buildFieldValueFromDirectValues(Vector directValues, String elementDataTypeName, AbstractSession session) throws DatabaseException { return directValues; } /** * INTERNAL: * Build and return the appropriate field value for the specified * set of nested rows. */ public Object buildFieldValueFromNestedRows(Vector nestedRows, String structureName, AbstractSession session) throws DatabaseException { return nestedRows; } /** * INTERNAL: * A DatabaseField is built from the given field name. */ public DatabaseField buildField(String fieldName) { XMLField xmlField = new XMLField(fieldName); xmlField.setNamespaceResolver(this.getNamespaceResolver()); //xmlField.initialize(); return xmlField; } /** * INTERNAL: * This is used only in initialization. */ public DatabaseField buildField(DatabaseField field) { try { XMLField xmlField = (XMLField) field; xmlField.setNamespaceResolver(this.getNamespaceResolver()); xmlField.initialize(); } catch (ClassCastException e) { // Assumes field is always an XMLField } return super.buildField(field); } /** * INTERNAL: * This is needed by regular aggregate descriptors (because they require review); * but not by XML aggregate descriptors. */ public void initializeAggregateInheritancePolicy(AbstractSession session) { // do nothing, since the parent descriptor was already modified during pre-initialize } @Override public void setTableNames(Vector tableNames) { if (null != tableNames && tableNames.size() > 0) { setDefaultRootElementField((String) tableNames.get(0)); } super.setTableNames(tableNames); } /** * INTERNAL: * Sets the tables */ public void setTables(Vector<DatabaseTable> theTables) { super.setTables(theTables); } /** * INTERNAL: * Allow the descriptor to initialize any dependencies on this session. */ public void preInitialize(AbstractSession session) throws DescriptorException { // Avoid repetitive initialization (this does not solve loops) if (isInitialized(PREINITIALIZED)) { return; } setInitializationStage(PREINITIALIZED); // Allow mapping pre init, must be done before validate. for (Enumeration mappingsEnum = getMappings().elements(); mappingsEnum.hasMoreElements();) { try { DatabaseMapping mapping = (DatabaseMapping) mappingsEnum.nextElement(); mapping.preInitialize(session); } catch (DescriptorException exception) { session.getIntegrityChecker().handleError(exception); } } getCachePolicy().useNoIdentityMap(); getQueryManager().getDoesExistQuery().setExistencePolicy(DoesExistQuery.CheckDatabase); validateBeforeInitialization(session); preInitializeInheritancePolicy(session); verifyTableQualifiers(session.getDatasourcePlatform()); initializeProperties(session); if (hasInterfacePolicy()) { preInterfaceInitialization(session); } getCachePolicy().assignDefaultValues(session); } @Override protected void preInitializeInheritancePolicy(AbstractSession session) throws DescriptorException { super.preInitializeInheritancePolicy(session); // Make sure that parent is already preinitialized if (hasInheritance()) { if(isChildDescriptor()) { XMLDescriptor parentDescriptor = (XMLDescriptor) getInheritancePolicy().getParentDescriptor(); NamespaceResolver parentNamespaceResolver = parentDescriptor.getNamespaceResolver(); if(null != parentNamespaceResolver && parentNamespaceResolver != namespaceResolver) { if(null == namespaceResolver) { namespaceResolver = getNonNullNamespaceResolver(); } if(parentNamespaceResolver.hasPrefixesToNamespaces()) { for(Entry<String, String> entry : parentNamespaceResolver.getPrefixesToNamespaces().entrySet()) { String namespaceURI = namespaceResolver.resolveNamespacePrefix(entry.getKey()); if(null == namespaceURI) { namespaceResolver.put(entry.getKey(), entry.getValue()); } else if(!namespaceURI.equals(entry.getValue())) { throw XMLMarshalException.subclassAttemptedToOverrideNamespaceDeclaration(entry.getKey(), getJavaClassName(), namespaceURI, parentDescriptor.getJavaClassName(), entry.getValue()); } } } } } // The default table will be set in this call once the duplicate // tables have been removed. getInheritancePolicy().preInitialize(session); } else { // This must be done now, after validate, before init anything else. setInternalDefaultTable(); } } /** * INTERNAL: * Post initializations after mappings are initialized. */ public void postInitialize(AbstractSession session) throws DescriptorException { // Avoid repetitive initialization (this does not solve loops) if (isInitialized(POST_INITIALIZED) || isInvalid()) { return; } setInitializationStage(POST_INITIALIZED); // Make sure that child is post initialized, // this initialize bottom up, unlike the two other phases that to top down. if (hasInheritance()) { for (ClassDescriptor child : getInheritancePolicy().getChildDescriptors()) { child.postInitialize(session); } } // Allow mapping to perform post initialization. for (DatabaseMapping mapping : getMappings()) { // This causes post init to be called multiple times in inheritance. mapping.postInitialize(session); } if (hasInheritance()) { getInheritancePolicy().postInitialize(session); } //PERF: Ensure that the identical primary key fields are used to avoid equals. if(null != primaryKeyFields) { for (int index = (primaryKeyFields.size() - 1); index >= 0; index--) { DatabaseField primaryKeyField = getPrimaryKeyFields().get(index); int fieldIndex = getFields().indexOf(primaryKeyField); // Aggregate/agg-collections may not have a mapping for pk field. if (fieldIndex != -1) { primaryKeyField = getFields().get(fieldIndex); getPrimaryKeyFields().set(index, primaryKeyField); } } } // Index and classify fields and primary key. // This is in post because it needs field classification defined in initializeMapping // this can come through a 1:1 so requires all descriptors to be initialized (mappings). // May 02, 2000 - Jon D. for (int index = 0; index < getFields().size(); index++) { DatabaseField field = getFields().elementAt(index); if (field.getType() == null) { DatabaseMapping mapping = getObjectBuilder().getMappingForField(field); if (mapping != null) { field.setType(mapping.getFieldClassification(field)); } } field.setIndex(index); } validateAfterInitialization(session); } /** * INTERNAL: * Initialize the mappings as a separate step. * This is done as a separate step to ensure that inheritance has been first resolved. */ public void initialize(AbstractSession session) throws DescriptorException { if (this.hasInheritance()) { ((org.eclipse.persistence.internal.oxm.QNameInheritancePolicy) this.getInheritancePolicy()).setNamespaceResolver(this.getNamespaceResolver()); } if(null != this.defaultRootElementField) { defaultRootElementField.setNamespaceResolver(this.namespaceResolver); defaultRootElementField.initialize(); } if(schemaReference != null && schemaReference.getSchemaContext() != null && (schemaReference.getType() == XMLSchemaReference.COMPLEX_TYPE || schemaReference.getType() == XMLSchemaReference.SIMPLE_TYPE) && getDefaultRootElementType() == null){ if(hasInheritance() && isChildDescriptor()){ XMLField parentField = ((XMLDescriptor)getInheritancePolicy().getParentDescriptor()).getDefaultRootElementField(); //if this descriptor has a root element field different than it's parent set the leaf element type of the default root field if(parentField == null || (parentField !=null && defaultRootElementField !=null && !defaultRootElementField.getXPathFragment().equals(parentField.getXPathFragment()))){ setDefaultRootElementType(schemaReference.getSchemaContextAsQName(getNamespaceResolver())); } }else{ setDefaultRootElementType(schemaReference.getSchemaContextAsQName(getNamespaceResolver())); } } if(null != primaryKeyFields) { for(int x = 0, primaryKeyFieldsSize = this.primaryKeyFields.size(); x<primaryKeyFieldsSize; x++) { XMLField pkField = (XMLField) this.primaryKeyFields.get(x); pkField.setNamespaceResolver(this.namespaceResolver); pkField.initialize(); } } // These cached settings on the project must be set even if descriptor is initialized. // If defined as read-only, add to it's project's default read-only classes collection. if (shouldBeReadOnly() && (!session.getDefaultReadOnlyClasses().contains(getJavaClass()))) { session.getDefaultReadOnlyClasses().add(getJavaClass()); } // Avoid repetitive initialization (this does not solve loops) if (isInitialized(INITIALIZED) || isInvalid()) { return; } setInitializationStage(INITIALIZED); // make sure that parent mappings are initialized? if (isChildDescriptor()) { ClassDescriptor parentDescriptor = getInheritancePolicy().getParentDescriptor(); parentDescriptor.initialize(session); if(parentDescriptor.hasEventManager()) { getEventManager(); } } for (Enumeration mappingsEnum = getMappings().elements(); mappingsEnum.hasMoreElements();) { DatabaseMapping mapping = (DatabaseMapping) mappingsEnum.nextElement(); validateMappingType(mapping); mapping.initialize(session); if(mapping.isObjectReferenceMapping()) { this.hasReferenceMappings = true; } if(mapping instanceof XMLChoiceObjectMapping) { XMLChoiceObjectMapping choiceMapping = ((XMLChoiceObjectMapping)mapping); for(XMLMapping next : choiceMapping.getChoiceElementMappings().values()) { if(((DatabaseMapping)next).isObjectReferenceMapping()) { this.hasReferenceMappings = true; } } } if(mapping instanceof XMLChoiceCollectionMapping) { XMLChoiceCollectionMapping choiceMapping = ((XMLChoiceCollectionMapping)mapping); for(XMLMapping next : choiceMapping.getChoiceElementMappings().values()) { if(((DatabaseMapping)next).isObjectReferenceMapping()) { this.hasReferenceMappings = true; } } } // Add all the fields in the mapping to myself. Helper.addAllUniqueToVector(getFields(), mapping.getFields()); } // If this has inheritance then it needs to be initialized before all fields is set. if (hasInheritance()) { getInheritancePolicy().initialize(session); } // Initialize the allFields to its fields, this can be done now because the fields have been computed. setAllFields((Vector) getFields().clone()); getObjectBuilder().initialize(session); if (hasInterfacePolicy()) { interfaceInitialization(session); } if (hasReturningPolicy()) { getReturningPolicy().initialize(session); } if (eventManager != null) { eventManager.initialize(session); } if (copyPolicy != null) { copyPolicy.initialize(session); } getInstantiationPolicy().initialize(session); if (getSchemaReference() != null) { getSchemaReference().initialize(session); } // If a Location Accessor is set on a superclass, inherit it if (getInheritancePolicyOrNull() != null && getInheritancePolicy().getParentDescriptor() != null) { XMLDescriptor d = (XMLDescriptor) getInheritancePolicy().getParentDescriptor(); locationAccessor = d.getLocationAccessor(); } if (locationAccessor != null) { locationAccessor.initializeAttributes(getJavaClass()); } } /** * INTERNAL: * XML descriptors are initialized normally, since they do * not need to be cloned by XML aggregate mappings. */ @Override public boolean requiresInitialization(AbstractSession session) { return (!isDescriptorForInterface()); } /** * Aggregates use a dummy table as default. */ protected DatabaseTable extractDefaultTable() { return new DatabaseTable(); } /** * INTERNAL: * Determines the appropriate object to return from the unmarshal * call. The method will either return the object created in the * xmlReader.parse() call or an instance of Root. An Root * instance will be returned if the DOMRecord element being * unmarshalled does not equal the descriptor's default root * element. * * @param unmarshalRecord * @return object */ public Object wrapObjectInXMLRoot(UnmarshalRecord unmarshalRecord, boolean forceWrap) { String elementLocalName = unmarshalRecord.getLocalName(); String elementNamespaceUri = unmarshalRecord.getRootElementNamespaceUri(); if (forceWrap || shouldWrapObject(unmarshalRecord.getCurrentObject(), elementNamespaceUri, elementLocalName, null, unmarshalRecord.isNamespaceAware())) { Root xmlRoot = new XMLRoot(); xmlRoot.setLocalName(elementLocalName); xmlRoot.setNamespaceURI(elementNamespaceUri); xmlRoot.setObject(unmarshalRecord.getCurrentObject()); xmlRoot.setEncoding(unmarshalRecord.getEncoding()); xmlRoot.setVersion(unmarshalRecord.getVersion()); xmlRoot.setSchemaLocation(unmarshalRecord.getSchemaLocation()); xmlRoot.setNoNamespaceSchemaLocation(unmarshalRecord.getNoNamespaceSchemaLocation()); xmlRoot.setNil(unmarshalRecord.isNil()); setDeclaredTypeOnXMLRoot(xmlRoot, elementNamespaceUri, elementLocalName, unmarshalRecord.isNamespaceAware(), unmarshalRecord.getUnmarshaller()); return xmlRoot; } return unmarshalRecord.getCurrentObject(); } /** * INTERNAL: * Determines the appropriate object to return from the unmarshal * call. The method will either return the object created in the * xmlReader.parse() call or an instance of Root. An Root * instance will be returned if the DOMRecord element being * unmarshalled does not equal the descriptor's default root * element. * * @param object * @param elementNamespaceUri * @param elementLocalName * @param elementPrefix * @return object */ public Object wrapObjectInXMLRoot(Object object, String elementNamespaceUri, String elementLocalName, String elementPrefix, boolean forceWrap, boolean isNamespaceAware, XMLUnmarshaller xmlUnmarshaller) { if (forceWrap || shouldWrapObject(object, elementNamespaceUri, elementLocalName, elementPrefix, isNamespaceAware)) { // if the DOMRecord element != descriptor's default // root element, create an Root, populate and return it Root xmlRoot = new XMLRoot(); xmlRoot.setLocalName(elementLocalName); xmlRoot.setNamespaceURI(elementNamespaceUri); xmlRoot.setObject(object); setDeclaredTypeOnXMLRoot(xmlRoot, elementNamespaceUri, elementLocalName, isNamespaceAware, xmlUnmarshaller); return xmlRoot; } return object; } /** * INTERNAL: * @return */ public Object wrapObjectInXMLRoot(Object object, String elementNamespaceUri, String elementLocalName, String elementPrefix, String encoding, String version, boolean forceWrap, boolean isNamespaceAware, XMLUnmarshaller unmarshaller) { if (forceWrap || shouldWrapObject(object, elementNamespaceUri, elementLocalName, elementPrefix, isNamespaceAware)) { // if the DOMRecord element != descriptor's default // root element, create an XMLRoot, populate and return it Root xmlRoot = new XMLRoot(); xmlRoot.setLocalName(elementLocalName); xmlRoot.setNamespaceURI(elementNamespaceUri); xmlRoot.setObject(object); xmlRoot.setEncoding(encoding); xmlRoot.setVersion(version); setDeclaredTypeOnXMLRoot(xmlRoot, elementNamespaceUri, elementLocalName, isNamespaceAware, unmarshaller); return xmlRoot; } return object; } private void setDeclaredTypeOnXMLRoot(Root xmlRoot, String elementNamespaceUri, String elementLocalName, boolean isNamespaceAware, Unmarshaller unmarshaller){ XPathQName xpathQName = new XPathQName(elementNamespaceUri, elementLocalName, isNamespaceAware); Descriptor desc = unmarshaller.getContext().getDescriptor(xpathQName); if(desc != null){ xmlRoot.setDeclaredType(desc.getJavaClass()); } } /** * INTERNAL: * @return */ public boolean shouldWrapObject(Object object, String elementNamespaceUri, String elementLocalName, String elementPrefix, boolean isNamespaceAware) { if(resultAlwaysXMLRoot){ return true; } XMLField defaultRootField = getDefaultRootElementField(); // if the descriptor's default root element is null, we want to // create/return an XMLRoot - otherwise, we need to compare the // default root element vs. the root element in the instance doc. if (defaultRootField != null) { // resolve namespace prefix if one exists String defaultRootName = defaultRootField.getXPathFragment().getLocalName(); String defaultRootNamespaceUri = defaultRootField.getXPathFragment().getNamespaceURI(); // if the DOMRecord element == descriptor's default // root element, return the object as per usual if(isNamespaceAware){ if ((((defaultRootNamespaceUri == null) && (elementNamespaceUri == null)) || ((defaultRootNamespaceUri == null) && (elementNamespaceUri.length() == 0)) || ((elementNamespaceUri == null) && (defaultRootNamespaceUri.length() == 0)) || (((defaultRootNamespaceUri != null) && (elementNamespaceUri != null)) && (defaultRootNamespaceUri .equals(elementNamespaceUri)))) && (defaultRootName.equals(elementLocalName))) { return false; } }else{ if (defaultRootName.equals(elementLocalName)) { return false; } } } return true; } public XMLField getDefaultRootElementField() { return defaultRootElementField; } /** * @return true if a new default root element field was created, else false. */ private boolean setDefaultRootElementField(String newDefaultRootElement) { if (null == newDefaultRootElement || 0 == newDefaultRootElement.length()) { setDefaultRootElementField((XMLField) null); return false; } if(getDefaultRootElementField() != null && newDefaultRootElement.equals(getDefaultRootElementField().getName())){ return false; } // create the root element xml field based on default root element name setDefaultRootElementField(new XMLField(newDefaultRootElement)); return true; } public void setDefaultRootElementField(XMLField xmlField) { defaultRootElementField = xmlField; } public QName getDefaultRootElementType() { if (defaultRootElementField != null) { return defaultRootElementField.getLeafElementType(); } return null; } /** * The default root element type string will be stored until * initialization - a QName will be created and stored on the * default root element field during initialize. * * @param type */ public void setDefaultRootElementType(QName type) { if (defaultRootElementField != null) { defaultRootElementField.setLeafElementType(type); } } /** * INTERNAL: * <p>Indicates if the Object mapped by this descriptor is a sequenced data object * and should be marshalled accordingly. */ public boolean isSequencedObject() { return sequencedObject; } public void setSequencedObject(boolean isSequenced) { this.sequencedObject = isSequenced; } public boolean isWrapper() { return isWrapper; } public void setIsWrapper(boolean value) { this.isWrapper = value; } public boolean isResultAlwaysXMLRoot() { return resultAlwaysXMLRoot; } public void setResultAlwaysXMLRoot(boolean resultAlwaysXMLRoot) { this.resultAlwaysXMLRoot = resultAlwaysXMLRoot; } /** * INTERNAL: * Returns true if any of the mappings on this descriptor are key-based reference * mappings. */ public boolean hasReferenceMappings() { return this.hasReferenceMappings; } @Override public DatabaseField getTypedField(DatabaseField field) { XMLField foundField = (XMLField) super.getTypedField(field); if(null != foundField) { return foundField; } StringTokenizer stringTokenizer = new StringTokenizer(field.getName(), String.valueOf(XPATH_FRAGMENT_SEPARATOR)); DatabaseField typedField = getTypedField(stringTokenizer); if(null == typedField) { DatabaseMapping selfMapping = objectBuilder.getMappingForField(new XMLField(".")); if(null != selfMapping) { return selfMapping.getReferenceDescriptor().getTypedField(field); } } return typedField; } protected DatabaseField getTypedField(StringTokenizer stringTokenizer) { StringBuilder xPath = new StringBuilder(); XMLField xmlField = new XMLField(); xmlField.setNamespaceResolver(namespaceResolver); while(stringTokenizer.hasMoreElements()) { String nextToken = stringTokenizer.nextToken(); xmlField.setXPath(xPath.toString() + nextToken); xmlField.initialize(); DatabaseMapping mapping = objectBuilder.getMappingForField(xmlField); if(null == mapping) { XPathFragment xPathFragment = new XPathFragment(nextToken); if(xPathFragment.getIndexValue() > 0) { xmlField.setXPath(xPath.toString() + nextToken.substring(0, nextToken.indexOf('['))); xmlField.initialize(); mapping = objectBuilder.getMappingForField(xmlField); if(null != mapping) { if(mapping.isCollectionMapping()) { if(mapping.getContainerPolicy().isListPolicy()) { if(stringTokenizer.hasMoreElements()) { return ((XMLDescriptor) mapping.getReferenceDescriptor()).getTypedField(stringTokenizer); } else { return mapping.getField(); } } } } } } else { if(stringTokenizer.hasMoreElements()) { return ((XMLDescriptor) mapping.getReferenceDescriptor()).getTypedField(stringTokenizer); } else { return mapping.getField(); } } xPath = xPath.append(nextToken).append(XPATH_FRAGMENT_SEPARATOR); } return null; } /** * INTERNAL: * Returns this Descriptor's location accessor, if one is defined. */ public AttributeAccessor getLocationAccessor() { return locationAccessor; } /** * INTERNAL: * Set this Descriptor's location accessor. */ public void setLocationAccessor(AttributeAccessor value) { this.locationAccessor = value; } /** * INTERNAL: * Convert all the class-name-based settings in this Descriptor to actual class-based * settings. This method is used when converting a project that has been built * with class names to a project with classes. * @param classLoader */ @Override public void convertClassNamesToClasses(ClassLoader classLoader){ super.convertClassNamesToClasses(classLoader); if(this.attributeGroups != null) { for(AttributeGroup next:attributeGroups.values()) { next.convertClassNamesToClasses(classLoader); } } } }