org.eclipse.persistence.oxm.XMLDescriptor Maven / Gradle / Ivy
Show all versions of eclipselink Show documentation
/*
* Copyright (c) 1998, 2024 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 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.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;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.Vector;
/**
* 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 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 = new ArrayList<>(3);
this.mappings = new ArrayList<>();
this.primaryKeyFields = null;
this.fields = new ArrayList<>();
this.allFields = new ArrayList<>();
this.constraintDependencies = Collections.emptyList();
this.multipleTableForeignKeys = Collections.emptyMap();
this.queryKeys = Collections.emptyMap();
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.emptyMap();
this.additionalWritableMapKeyFields = Collections.emptyList();
// 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().get(0).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 List getPrimaryKeyFieldNames() {
if(null == primaryKeyFields) {
return new ArrayList<>(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 nestedRows)) {
return getObjectBuilder().createRecord(null);
}
if (nestedRows.isEmpty()) {
return getObjectBuilder().createRecord(null);
} else {
// BUG#2667762 - If the tag was empty this could be a string of whitespace.
if (!(nestedRows.get(0) instanceof AbstractRecord)) {
return getObjectBuilder().createRecord(null);
}
return (XMLRecord) nestedRows.get(0);
}
}
/**
* 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 List 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 List