/* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the "License"). You may not use this file except * in compliance with the License. * * You can obtain a copy of the license at * glassfish/bootstrap/legal/CDDLv1.0.txt or * https://glassfish.dev.java.net/public/CDDLv1.0.html. * See the License for the specific language governing * permissions and limitations under the License. * * When distributing Covered Code, include this CDDL * HEADER in each file and include the License file at * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable, * add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your * own identifying information: Portions Copyright [yyyy] * [name of copyright owner] */ // Copyright (c) 1998, 2006, Oracle. All rights reserved. package oracle.toplink.essentials.internal.ejb.cmp3.xml; import java.lang.reflect.Method; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import oracle.toplink.essentials.ejb.cmp3.EJBQuery; import oracle.toplink.essentials.exceptions.ValidationException; import oracle.toplink.essentials.internal.ejb.cmp3.EJBQueryImpl; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataAccessor; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataDefaultListener; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataDescriptor; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataHelper; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataJoinColumn; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataLogger; import oracle.toplink.essentials.internal.ejb.cmp3.metadata.MetadataProcessor; import oracle.toplink.essentials.internal.ejb.cmp3.xml.sequencing.Id; import oracle.toplink.essentials.internal.ejb.cmp3.xml.sequencing.SequenceGenerator; import oracle.toplink.essentials.internal.ejb.cmp3.xml.sequencing.SequencingProcessor; import oracle.toplink.essentials.internal.ejb.cmp3.xml.sequencing.TableGenerator; import oracle.toplink.essentials.internal.helper.DatabaseField; import oracle.toplink.essentials.internal.helper.DatabaseTable; import oracle.toplink.essentials.internal.security.PrivilegedAccessController; import oracle.toplink.essentials.internal.sessions.AbstractSession; import oracle.toplink.essentials.mappings.AggregateObjectMapping; import oracle.toplink.essentials.mappings.CollectionMapping; import oracle.toplink.essentials.mappings.DatabaseMapping; import oracle.toplink.essentials.mappings.DirectToFieldMapping; import oracle.toplink.essentials.mappings.ForeignReferenceMapping; import oracle.toplink.essentials.mappings.ManyToManyMapping; import oracle.toplink.essentials.queryframework.ColumnResult; import oracle.toplink.essentials.queryframework.EJBQLPlaceHolderQuery; import oracle.toplink.essentials.queryframework.EntityResult; import oracle.toplink.essentials.queryframework.FieldResult; import oracle.toplink.essentials.sessions.Project; import oracle.toplink.essentials.sessions.Session; import oracle.toplink.essentials.queryframework.SQLResultSetMapping; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * INTERNAL: * * Process entity-mappings instance documents, with the end result being a TopLink project. The * project is handed to this class via TopLink session, and could possibly be partially/fully populated * from project XML. After processing, the project may/may not be fully complete; if not, the * EJBAnnotationsProcessor will complete the process. It is assumed here that an entity declaration * does not have to be complete. Mappings, however, must be fully defined. * */ public class EntityMappingsXMLProcessor extends MetadataProcessor { // persistence unit default values, set on persistence-unit-default element private String m_defaultAccess; private String m_defaultCatalog; private String m_defaultSchema; // override values for persistence unit defaults - applies to a given instance document // package, catalog and schema can be overridden in entity-mappings element, and // access can be overridden in entity-mappings and entity elements private String m_access; private String m_catalog; private String m_package; private String m_schema; // data members private Document m_document; private HashMap m_namedQueries; private HashMap m_namedNativeQueries; private HashMap m_entityListeners; private SequencingProcessor m_sequencingProcessor; private Project m_project; private XPathEngine m_xPathEngine; private XMLHelper m_xmlHelper; /** * The default constructor. */ public EntityMappingsXMLProcessor() { this(true); } /** * The default constructor. */ public EntityMappingsXMLProcessor(boolean enableLazyForOneToOne) { m_enableLazyForOneToOne = enableLazyForOneToOne; m_namedQueries = new HashMap(); m_namedNativeQueries = new HashMap(); m_entityListeners = new HashMap(); m_processingContext = ORM_XML; } /** * INTERNAL: * Add EntityListeners to the descriptors of a given TopLink session. * * EntityListeners are processed in EntityManagerSetupImpl baseDeploy method, * which is called after the project has been written out, enhanced and read * back in. */ public void addEntityListeners(Session session) { for (Iterator it = m_entityListeners.keySet().iterator(); it.hasNext();) { String entityClassName = (String) it.next(); HashMap listenerMap = (HashMap) m_entityListeners.get(entityClassName); Class entityClass = MetadataHelper.getClassForName(entityClassName, m_loader); XMLDescriptorMetadata dmd = new XMLDescriptorMetadata(MetadataHelper.findDescriptor(session.getProject(), entityClass), entityClass); XMLCBListener cbListener; Iterator listenIt; // handle entity listeners ArrayList listeners = (ArrayList) listenerMap.get(XMLConstants.ENTITY_LISTENERS); for (listenIt = listeners.iterator(); listenIt.hasNext(); ) { cbListener = (XMLCBListener) listenIt.next(); cbListener.initializeCallbackMethods(m_loader); dmd.addEntityListenerEventListener(cbListener); } // handle callback methods listeners = (ArrayList) listenerMap.get(XMLConstants.CALLBACK_METHODS); for (listenIt = listeners.iterator(); listenIt.hasNext(); ) { cbListener = (XMLCBListener) listenIt.next(); cbListener.initializeCallbackMethods(m_loader); dmd.addEntityEventListener(cbListener); } } } /** * INTERNAL: * If node contains a child tag with name matching eventTag, the associated * method name is stored in the listener for later processing. * * @param cbListener * @param node * @param eventTag */ private void addEventMethodNameToListener(XMLCBListener cbListener, Node node, String eventTag) { Node result = m_xPathEngine.selectSingleNode(node, new String[] {eventTag}); if (result != null) { // process @method-name (required) cbListener.addEventMethodName(eventTag, m_xPathEngine.selectSingleNode(result, new String[] {XMLConstants.ATT_METHOD_NAME}).getNodeValue()); } } /** * INTERNAL: * If node contains a child tag with name matching eventTag, the associated * method name is stored in the map for later processing. * * @param methodMap * @param node * @param eventTag */ private void addEventMethodNameToMap(HashMap methodMap, Node node, String eventTag) { Node result = m_xPathEngine.selectSingleNode(node, new String[] {eventTag}); if (result != null) { // process @method-name (required) methodMap.put(eventTag, m_xPathEngine.selectSingleNode(result, new String[] {XMLConstants.ATT_METHOD_NAME}).getNodeValue()); } } /** * INTERNAL: * Add queries to a given session. The XMLDescriptorMetadata objects for any * entities containing named-query or named-native-query definitions have been * flagged and will be processed. Queries at the entity-mapping level will * be processed here as well. * * @param session the session to which the processed queries will be added */ public void addNamedQueriesToSession(Session session) { HashMap props, hints; String queryString; // for each named-query for (Iterator keys = m_namedQueries.keySet().iterator(); keys.hasNext();) { String queryName = (String) keys.next(); if (session.getQuery(queryName) != null) { getLogger().logWarningMessage(MetadataLogger.IGNORE_QUERY, queryName); continue; } try { props = (HashMap) m_namedQueries.get(queryName); queryString = (String) props.get(XMLConstants.QUERY); hints = (HashMap) props.get(XMLConstants.QUERY_HINT); // add a new query to the TopLink Session session.addEjbqlPlaceHolderQuery(new EJBQLPlaceHolderQuery(queryName, queryString, hints)); } catch (Exception exception) { throw ValidationException.errorProcessingNamedQueryElement(queryName, exception); } } // for each named-native-query for (Iterator keys = m_namedNativeQueries.keySet().iterator(); keys.hasNext();) { String queryName = (String) keys.next(); if (session.getQuery(queryName) != null) { getLogger().logWarningMessage(MetadataLogger.IGNORE_QUERY, queryName); continue; } try { props = (HashMap) m_namedNativeQueries.get(queryName); queryString = (String) props.get(XMLConstants.QUERY); hints = (HashMap) props.get(XMLConstants.QUERY_HINT); // result-class (takes precidence) if (props.containsKey(XMLConstants.ATT_RESULT_CLASS)) { Class resultClass = MetadataHelper.getClassForName(((String) props.get(XMLConstants.ATT_RESULT_CLASS)), m_loader); if (resultClass != void.class) { // add a new query to the TopLink Session session.addQuery(queryName, EJBQueryImpl.buildSQLDatabaseQuery(resultClass, queryString, hints)); continue; } } // result-set-mapping if (props.containsKey(XMLConstants.ATT_RESULT_SET_MAPPING)) { // add a new query to the TopLink Session session.addQuery(queryName, EJBQueryImpl.buildSQLDatabaseQuery((String) props.get(XMLConstants.ATT_RESULT_SET_MAPPING), queryString, hints)); continue; } // at this point we need to set default query values on the session session.addQuery(queryName, EJBQueryImpl.buildSQLDatabaseQuery(queryString, hints)); } catch (Exception exception) { throw ValidationException.errorProcessingNamedQueryElement(queryName, exception); } } } /** * INTERNAL: * The class name of each node in the node list will be added to the provided collection. * * @param xmlDoc * @param xPath * @param defaultPkg */ private static Set buildClassSetForNodeList(Document xmlDoc, String xPath, String defaultPkg) { HashSet classNameSet = new HashSet(); XPathEngine xPathEngine = XPathEngine.getInstance(); NodeList nodes = xPathEngine.selectNodes(xmlDoc, new String[] {XMLConstants.ENTITY_MAPPINGS, xPath}); for (int i=0; i 0) { // for each listener... for (int i=0; i0; } /** * INTERNAL: * Locate a node in the DOM tree for a given class. * * @param className the name of the class to search the DOM for * @param searchString the node name to search for, i.e. ENTITY or MAPPED_SUPERCLASS * @return */ public Node locateNode(Class cls, String searchString) { return m_xmlHelper.locateNode(cls, searchString); } /** * INTERNAL: * Process the attributes of an entity or embeddable. * * @param dmd */ protected void processAccessors(MetadataDescriptor dmd) { processAttributes((XMLDescriptorMetadata) dmd, m_xmlHelper.locateNode(dmd.getJavaClass())); } /** * INTERNAL: * Satisfy the abstract method declaration in MetadataProcessor. */ protected void processAssociationOverrides(MetadataAccessor accessor, AggregateObjectMapping mapping) { return; } /** * INTERNAL: * Process association-override info. * * @param dmd * @param entityNode */ protected void processAssociationOverrides(XMLDescriptorMetadata dmd, Node entityNode) { NodeList nodes = m_xPathEngine.selectNodes(entityNode, new String[] {XMLConstants.ASSOCIATION_OVERRIDE}); if (nodes != null) { for (int i=0; i0) { dmd.addAssociationOverride(name, new Object[] {jCols}); } } } } /** * INTERNAL: * Process the attributes of an entity, embeddable or mapped-superclass. * * @param dmd */ protected void processAccessors(Class cls, XMLDescriptorMetadata dmd) { processAttributes(dmd, m_xmlHelper.locateNode(cls)); } /** * INTERNAL: * @param accessor * @param mapping */ protected void processAttributeOverrides(MetadataAccessor metadataAccessor, AggregateObjectMapping mapping) { XMLAccessor accessor = (XMLAccessor) metadataAccessor; if (accessor.hasAttributeOverrides()) { NodeList overrides = accessor.getAttributeOverrideNodes(); for (int i=0; i elements // specified should equal the number of primary key fields if (joinColumns == null || joinColumns.getLength() != referenceDmd.getPrimaryKeyFields().size()) { // TODO: create a non-annotation specific message to throw here throw ValidationException.incompleteJoinColumnsSpecified(attributeName, dmd.getJavaClass()); } for (int i=0; i1) { // TODO: create a non-annotation specific message to throw here throw ValidationException.excessiveJoinColumnsSpecified(attributeName, dmd.getJavaClass()); } allJoinColumns.add(processJoinColumn(joinColumns.item(0), accessor, targetTableName, referenceDmd)); } else { addJoinColumnDefault(allJoinColumns, sourceTableName, targetTableName, dmd); } } return allJoinColumns; } /** * INTERNAL: * Process join-table information. * * @param dmd * @param attributeNode * @param mapping * @return */ protected void processJoinTable(MetadataAccessor metadataAccessor, ManyToManyMapping mapping) { XMLAccessor accessor = (XMLAccessor) metadataAccessor; DatabaseTable relationTable = getRelationTable(accessor); mapping.setRelationTable(relationTable); String relationTableName = relationTable.getQualifiedName(); // process join-column and inverse-join-column ArrayList sourceKeys = new ArrayList(); ArrayList targetKeys = new ArrayList(); Node joinTableNode = accessor.getJoinTableNode(); if (joinTableNode != null) { // process join-columns NodeList jCols = m_xPathEngine.selectNodes(joinTableNode, new String[] {XMLConstants.JOIN_COLUMN}); if (jCols != null) { for (int i=0; i 0) { for (int i=0; i 1) { throw ValidationException.excessivePrimaryKeyJoinColumnsSpecified(entityClass); } } // process primary-key-join-column(s) if (primaryKeyJoinColumns != null) { for (int i=0; i