/******************************************************************************* * 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.eis; import java.util.List; import java.util.Vector; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.descriptors.DescriptorQueryManager; import org.eclipse.persistence.descriptors.InheritancePolicy; import org.eclipse.persistence.eis.mappings.EISCompositeCollectionMapping; import org.eclipse.persistence.eis.mappings.EISCompositeDirectCollectionMapping; import org.eclipse.persistence.eis.mappings.EISCompositeObjectMapping; import org.eclipse.persistence.eis.mappings.EISDirectMapping; import org.eclipse.persistence.eis.mappings.EISOneToManyMapping; import org.eclipse.persistence.eis.mappings.EISOneToOneMapping; import org.eclipse.persistence.exceptions.DatabaseException; import org.eclipse.persistence.exceptions.DescriptorException; import org.eclipse.persistence.internal.databaseaccess.DatasourceCall; import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform; import org.eclipse.persistence.internal.expressions.SQLStatement; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.helper.DatabaseTable; import org.eclipse.persistence.internal.oxm.QNameInheritancePolicy; import org.eclipse.persistence.internal.oxm.XMLObjectBuilder; 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.CollectionMapping; import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.ObjectReferenceMapping; import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping; import org.eclipse.persistence.oxm.NamespaceResolver; import org.eclipse.persistence.oxm.XMLField; import org.eclipse.persistence.queries.DatabaseQuery; /** * * <p>An <code>EISDescriptor</code> defines the mapping from a JCA data * structure to a Java object. There are two types of EIS descriptors: * <ul> * <li>Root - indicates to the EclipseLink runtime that the EIS descriptor's * reference class is a parent class: no other class will reference it by way of * a composite object mapping or composite collection mapping. For an EIS root * descriptor, EIS interactions can be defined to invoke methods on an EIS * <li>Composite - indicates to the EclipseLink runtime that the EIS descriptor's * reference class may be referenced by a composite object mapping or composite * collection mapping * </ul> * * @see org.eclipse.persistence.eis.interactions.EISInteraction * @see org.eclipse.persistence.eis.mappings.EISMapping * * @author James * @since OracleAS TopLink 10<i>g</i> (10.0.3) */ public class EISDescriptor extends ClassDescriptor { /** Define the type of data the descriptor maps to. */ protected String dataFormat; /** Define the valid data formats that the descriptor can map to. */ public static final String MAPPED = "mapped"; public static final String INDEXED = "indexed"; public static final String XML = "xml"; /** Allow namespaces to be specified for XML type descriptors. */ protected NamespaceResolver namespaceResolver; /** * Default constructor. */ public EISDescriptor() { super(); this.shouldOrderMappings = false; this.dataFormat = XML; } @Override protected void validateMappingType(DatabaseMapping mapping) { if (!(mapping.isEISMapping())) { throw DescriptorException.invalidMappingType(mapping); } } /** * PUBLIC: * Specify the data type name for the class of objects the descriptor maps. * This may be the XML schema complex type name, or the JCA record name for the type being mapped. */ public void setDataTypeName(String dataTypeName) throws DescriptorException { this.setTableName(dataTypeName); } /** * PUBLIC: * Return the XML namespace resolver. * XML type EIS descriptor can use a namespace resolver to support XML schema namespaces. */ public NamespaceResolver getNamespaceResolver() { 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. */ @Override public InheritancePolicy getInheritancePolicy() { if (inheritancePolicy == null) { if(isXMLFormat()) { // Lazy initialize to conserve space in non-inherited classes. setInheritancePolicy(new org.eclipse.persistence.internal.oxm.QNameInheritancePolicy(this)); } else { setInheritancePolicy(new InheritancePolicy(this)); } } return inheritancePolicy; } /** * PUBLIC: * Set the XML namespace resolver. * XML type EIS descriptor can use a namespace resolver to support XML schema namespaces. */ public void setNamespaceResolver(NamespaceResolver namespaceResolver) { this.namespaceResolver = namespaceResolver; } /** * INTERNAL: * Avoid SDK initialization. */ @Override public void setQueryManager(DescriptorQueryManager queryManager) { this.queryManager = queryManager; if (queryManager != null) { queryManager.setDescriptor(this); } } /** * INTERNAL: * Configure the object builder for the correct dataFormat. */ @Override public void preInitialize(AbstractSession session) { // Must not initialize if already done. if (isInitialized(PREINITIALIZED)) { return; } if (isXMLFormat()) { setObjectBuilder(new XMLObjectBuilder(this)); if(this.hasInheritance()) { ((QNameInheritancePolicy)getInheritancePolicy()).setNamespaceResolver(this.namespaceResolver); } } // initializeQueryManager(); super.preInitialize(session); } /** * INTERNAL: * Initialize the query manager specific to the descriptor type. * Allow the platform to initialize the CRUD queries to defaults. */ @Override public void initialize(DescriptorQueryManager queryManager, AbstractSession session) { ((DatasourcePlatform)session.getDatasourcePlatform()).initializeDefaultQueries(queryManager, session); super.initialize(queryManager, session); } public boolean isXMLFormat() { return this.dataFormat.equals(XML); } public boolean isMappedFormat() { return this.dataFormat.equals(MAPPED); } public boolean isIndexedFormat() { return this.dataFormat.equals(INDEXED); } /** * PUBLIC: * Return the data format that the descriptor maps to. */ public String getDataFormat() { return dataFormat; } /** * PUBLIC: * Specify the data type name for the class of objects the descriptor maps. * This may be the XML schema complex type name, or the JCA record name for the type being mapped. */ public String getDataTypeName() throws DescriptorException { return this.getTableName(); } /** * PUBLIC: * Configure the data format that the descriptor maps to. */ public void setDataFormat(String dataFormat) { this.dataFormat = dataFormat; } /** * PUBLIC: * Configure the data format to use mapped records. */ public void useMappedRecordFormat() { setDataFormat(MAPPED); } /** * PUBLIC: * Configure the data format to use indexed records. */ public void useIndexedRecordFormat() { setDataFormat(INDEXED); } /** * PUBLIC: * Configure the data format to use xml records. */ public void useXMLRecordFormat() { setDataFormat(XML); } /** * INTERNAL: * Build the nested row. */ @Override public AbstractRecord buildNestedRowFromFieldValue(Object fieldValue) { if (fieldValue instanceof AbstractRecord) { return (AbstractRecord)fieldValue; } // BUG#2667762 if the tag was empty this could be a string of whitespace. if (!(fieldValue instanceof List)) { return getObjectBuilder().createRecord(0, null); } List nestedRows = (List)fieldValue; if (nestedRows.isEmpty()) { return getObjectBuilder().createRecord(0, 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(0, null); } return (AbstractRecord)nestedRows.get(0); } } /** * INTERNAL: * Build the nested rows. */ @Override public Vector buildNestedRowsFromFieldValue(Object fieldValue, AbstractSession session) { if (!isXMLFormat()) { if (!(fieldValue instanceof List)) { return new Vector(); } return new Vector((List)fieldValue); } // 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; } /** * 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) { 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) { return directValues; } /** * INTERNAL: * Build and return the field value from the specified nested database row. */ @Override public Object buildFieldValueFromNestedRow(AbstractRecord nestedRow, AbstractSession session) throws DatabaseException { Vector nestedRows = new Vector(1); nestedRows.add(nestedRow); return this.buildFieldValueFromNestedRows(nestedRows, "", session); } /** * 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: * XML type descriptors should use XMLFields. */ @Override public DatabaseField buildField(String fieldName) { if (isXMLFormat()) { XMLField xmlField = new XMLField(fieldName); xmlField.setNamespaceResolver(this.getNamespaceResolver()); xmlField.initialize(); return xmlField; } else { return super.buildField(fieldName); } } /** * INTERNAL: * If the field is an XMLField then set the namespace resolver from the descriptor. * This allows the resolver to only be set in the descriptor. */ @Override public DatabaseField buildField(DatabaseField field) { if (isXMLFormat()) { if(!(field instanceof XMLField)) { String xPath = field.getName(); // Moxy requires /text on elements. if ((xPath.indexOf('@') == -1) && (xPath.indexOf("/text()") == -1)) { xPath = xPath + "/text()"; } field = new XMLField(xPath); } ((XMLField)field).setNamespaceResolver(getNamespaceResolver()); ((XMLField)field).initialize(); } return super.buildField(field); } /** * Return a new direct/basic mapping for this type of descriptor. */ @Override public AbstractDirectMapping newDirectMapping() { return new EISDirectMapping(); } /** * Return a new aggregate/embedded mapping for this type of descriptor. */ @Override public AggregateMapping newAggregateMapping() { return new EISCompositeObjectMapping(); } /** * Return a new aggregate collection/element collection mapping for this type of descriptor. */ @Override public DatabaseMapping newAggregateCollectionMapping() { return new EISCompositeCollectionMapping(); } /** * Return a new direct collection/element collection mapping for this type of descriptor. */ @Override public DatabaseMapping newDirectCollectionMapping() { return new EISCompositeDirectCollectionMapping(); } /** * Return a new one to one mapping for this type of descriptor. */ @Override public ObjectReferenceMapping newOneToOneMapping() { return new EISOneToOneMapping(); } /** * Return a new many to one mapping for this type of descriptor. */ @Override public ObjectReferenceMapping newManyToOneMapping() { return new EISOneToOneMapping(); } /** * Return a new one to many mapping for this type of descriptor. */ @Override public CollectionMapping newOneToManyMapping() { return new EISOneToManyMapping(); } /** * Return a new one to many mapping for this type of descriptor. */ public CollectionMapping newUnidirectionalOneToManyMapping() { return new EISOneToManyMapping(); } /** * Return a new one to many mapping for this type of descriptor. */ @Override public CollectionMapping newManyToManyMapping() { return new EISOneToManyMapping(); } /** * 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 * instanceVariableName is the name of an instance variable of * the class which the receiver describes. * @param fieldName * fieldName is the name 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 fieldName) { EISDirectMapping mapping = new EISDirectMapping(); mapping.setAttributeName(attributeName); if (isXMLFormat()) { mapping.setXPath(fieldName); } else { mapping.setFieldName(fieldName); } 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 fieldName) { EISDirectMapping mapping = new EISDirectMapping(); mapping.setAttributeName(attributeName); mapping.setSetMethodName(setMethodName); mapping.setGetMethodName(getMethodName); if (isXMLFormat()) { mapping.setXPath(fieldName); } else { mapping.setFieldName(fieldName); } return addMapping(mapping); } /** * PUBLIC: * Specify the primary key field. * This should be called for each field that make up the primary key. * For EIS XML Descriptors use the addPrimaryKeyField(DatabaseField) API * and supply an org.eclipse.persistence.oxm.XMLField parameter instead of using this method */ @Override public void addPrimaryKeyFieldName(String fieldName) { if (isXMLFormat()) { addPrimaryKeyField(new XMLField(fieldName)); } else { super.addPrimaryKeyFieldName(fieldName); } } /** * PUBLIC: * Set the sequence number field name. * This is the field in the descriptors table that needs its value to be generated. * This is normally the primary key field of the descriptor. * For EIS XML Descriptors use the setSequenceNumberFieldName(DatabaseField) API * and supply an org.eclipse.persistence.oxm.XMLField parameter instead of using this method */ @Override public void setSequenceNumberFieldName(String fieldName) { super.setSequenceNumberFieldName(fieldName); } /** * INTERNAL: * Override this method to throw an exception. SQL should not be generated for * EIS Calls. */ @Override public DatasourceCall buildCallFromStatement(SQLStatement statement, DatabaseQuery query, AbstractSession session) { return ((EISPlatform)session.getDatasourcePlatform()).buildCallFromStatement(statement, query, session); } @Override public void initialize(AbstractSession session) throws DescriptorException { if (isXMLFormat()) { for(int x = 0, primaryKeyFieldsSize = this.primaryKeyFields.size(); x<primaryKeyFieldsSize; x++) { XMLField pkField = (XMLField) this.primaryKeyFields.get(x); pkField.setNamespaceResolver(this.namespaceResolver); pkField.initialize(); } } super.initialize(session); } /** * INTERNAL: This is needed by regular aggregate descriptors * but not by * EIS aggregate descriptors. */ @Override public void initializeAggregateInheritancePolicy(AbstractSession session) { // do nothing, since the parent descriptor was already modified during pre-initialize } /** * INTERNAL: * XML descriptors are initialized normally, since they do * not need to be cloned by ESI aggregate mappings. */ @Override public boolean requiresInitialization(AbstractSession session) { return (!isDescriptorForInterface()); } /** * Aggregates use a dummy table as default. */ @Override protected DatabaseTable extractDefaultTable() { if (this.isAggregateDescriptor()) { return new DatabaseTable(); } return super.extractDefaultTable(); } /** * INTERNAL: * Indicates if a return type is required for the field set on the * returning policy. For EIS descriptors, this should always * return false. */ @Override public boolean isReturnTypeRequiredForReturningPolicy() { return false; } /** * PUBLIC: * Return if the descriptor maps to an EIS or NoSQL datasource. */ @Override public boolean isEISDescriptor() { return true; } /** * INTERNAL: * Return if change sets are required for new objects. */ @Override public boolean shouldUseFullChangeSetsForNewObjects() { // This is currently set to allow EIS tests to pass the same as before. // TODO: It should be removed, and the test issues fixed (bug was logged). return true; } }