org.eclipse.persistence.oxm.XMLDescriptor Maven / Gradle / Ivy
/*
* Copyright (c) 1998, 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:
// 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{
/*
* 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
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
public NamespaceResolver getNamespaceResolver() {
return namespaceResolver;
}
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
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
*/
@Override
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.
*/
@Override
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 getPrimaryKeyFieldNames() {
if(null == primaryKeyFields) {
return new Vector<>(0);
}
return super.getPrimaryKeyFieldNames();
}
@Override
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
*/
@Override
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
*/
@Override
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.
*/
@Override
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.
*/
@Override
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<>(1);
}
super.addPrimaryKeyField(field);
}
@Override
public void setPrimaryKeyFields(List thePrimaryKeyFields) {
if(null == thePrimaryKeyFields) {
return;
}
List xmlFields = new ArrayList(thePrimaryKeyFields.size());
Iterator 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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
public Object buildFieldValueFromNestedRows(Vector nestedRows, String structureName, AbstractSession session) throws DatabaseException {
return nestedRows;
}
/**
* INTERNAL:
* A DatabaseField is built from the given field name.
*/
@Override
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.
*/
@Override
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.
*/
@Override
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
*/
@Override
public void setTables(Vector theTables) {
super.setTables(theTables);
}
/**
* INTERNAL:
* Allow the descriptor to initialize any dependencies on this session.
*/
@Override
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 = 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 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.
*/
@Override
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.
*/
@Override
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 mappingsEnum = getMappings().elements(); mappingsEnum.hasMoreElements();) {
DatabaseMapping mapping = 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.
*/
@Override
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.
*
* @return object
*/
@Override
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.
*
* @return object
*/
@Override
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:
*/
@Override
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:
*/
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;
}
@Override
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;
}
@Override
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.
*
*/
public void setDefaultRootElementType(QName type) {
if (defaultRootElementField != null) {
defaultRootElementField.setLeafElementType(type);
}
}
/**
* INTERNAL:
* Indicates if the Object mapped by this descriptor is a sequenced data object
* and should be marshalled accordingly.
*/
@Override
public boolean isSequencedObject() {
return sequencedObject;
}
public void setSequencedObject(boolean isSequenced) {
this.sequencedObject = isSequenced;
}
@Override
public boolean isWrapper() {
return isWrapper;
}
public void setIsWrapper(boolean value) {
this.isWrapper = value;
}
@Override
public boolean isResultAlwaysXMLRoot() {
return resultAlwaysXMLRoot;
}
@Override
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 + nextToken);
xmlField.initialize();
DatabaseMapping mapping = objectBuilder.getMappingForField(xmlField);
if(null == mapping) {
XPathFragment xPathFragment = new XPathFragment(nextToken);
if(xPathFragment.getIndexValue() > 0) {
xmlField.setXPath(xPath + 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.
*/
@Override
public AttributeAccessor getLocationAccessor() {
return locationAccessor;
}
/**
* INTERNAL:
* Set this Descriptor's location accessor.
*/
@Override
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.
*/
@Override
public void convertClassNamesToClasses(ClassLoader classLoader){
super.convertClassNamesToClasses(classLoader);
if(this.attributeGroups != null) {
for(AttributeGroup next:attributeGroups.values()) {
next.convertClassNamesToClasses(classLoader);
}
}
}
}