| /* |
| * Copyright (c) 1998, 2019 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 |
| package org.eclipse.persistence.oxm.mappings; |
| |
| import java.util.Enumeration; |
| import java.util.Vector; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.DescriptorException; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.oxm.mappings.FragmentCollectionMapping; |
| import org.eclipse.persistence.internal.oxm.mappings.XMLContainerMapping; |
| import org.eclipse.persistence.internal.queries.ContainerPolicy; |
| import org.eclipse.persistence.internal.queries.JoinedAttributeManager; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.AttributeAccessor; |
| import org.eclipse.persistence.mappings.foundation.AbstractCompositeDirectCollectionMapping; |
| import org.eclipse.persistence.oxm.XMLField; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; |
| import org.eclipse.persistence.oxm.record.DOMRecord; |
| import org.eclipse.persistence.oxm.record.XMLRecord; |
| import org.eclipse.persistence.platform.xml.XMLPlatformFactory; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.sessions.Session; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Text; |
| |
| /** |
| * <p><b>Purpose: </b>This mapping provides a means to keep a part of the xml tree as a collection |
| * of DOM elements. |
| * |
| * <p><b>Setting the XPath</b>: TopLink XML mappings make use of XPath statements to find the relevant |
| * data in an XML document. The XPath statement is relative to the context node specified in the descriptor. |
| * The XPath may contain path and positional information; the last node in the XPath forms the local |
| * root node for the fragment. The XPath is specified on the mapping using the <code>setXPath</code> |
| * method. |
| * |
| * <table border="1"> |
| * <caption>XPath statements</caption> |
| * <tr> |
| * <th id="c1">XPath</th> |
| * <th id="c2">Description</th> |
| * </tr> |
| * <tr> |
| * <td headers="c1">phone-number</td> |
| * <td headers="c2">The phone-number information is stored in the phone-number element.</td> |
| * </tr> |
| * <tr> |
| * <td headers="c1" style="nowrap">contact-info/phone-number</td> |
| * <td headers="c2">The XPath statement may be used to specify any valid path.</td> |
| * </tr> |
| * </table> |
| * <p><b>Sample Configuration:</b> |
| * <pre><code> |
| * XMLFragmentCollectionMapping mapping = new XMLFragmentCollectionMapping(); |
| * mapping.setAttributeName("phoneNumbers"); |
| * mapping.setXPath("contact-info/phone-number"); |
| * </code> |
| * </pre> |
| */ |
| public class XMLFragmentCollectionMapping extends AbstractCompositeDirectCollectionMapping implements FragmentCollectionMapping<AbstractSession, AttributeAccessor, ContainerPolicy, ClassDescriptor, DatabaseField, Session, XMLRecord>, XMLMapping { |
| private boolean defaultEmptyContainer = XMLContainerMapping.EMPTY_CONTAINER_DEFAULT; |
| private boolean isWriteOnly; |
| private boolean reuseContainer; |
| private AbstractNullPolicy wrapperNullPolicy; |
| |
| public XMLFragmentCollectionMapping() { |
| super(); |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public boolean isXMLMapping() { |
| return true; |
| } |
| |
| /** |
| * INTERNAL: |
| * Initialize the mapping. |
| */ |
| @Override |
| public void initialize(AbstractSession session) throws DescriptorException { |
| super.initialize(session); |
| } |
| |
| /** |
| * Set the Mapping field name attribute to the given XPath String |
| * @param xpathString String |
| */ |
| public void setXPath(String xpathString) { |
| setField(new XMLField(xpathString)); |
| } |
| |
| /** |
| * Get the XPath String |
| * @return String the XPath String associated with this Mapping |
| */ |
| public String getXPath() { |
| return getFieldName(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Build the nested collection from the database row. |
| */ |
| @Override |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery sourceQuery, CacheKey cacheKey, AbstractSession executionSession, boolean isTargetProtected, Boolean[] wasCacheUsed) throws DatabaseException { |
| ContainerPolicy cp = this.getContainerPolicy(); |
| |
| Object fieldValue = ((DOMRecord)row).getValuesIndicatingNoEntry(this.getField(), true); |
| |
| Vector nestedRows = null; |
| if (fieldValue instanceof Vector) { |
| nestedRows = (Vector)fieldValue; |
| } |
| if ((nestedRows == null) || nestedRows.isEmpty()) { |
| if (reuseContainer) { |
| Object currentObject = ((XMLRecord) row).getCurrentObject(); |
| Object container = getAttributeAccessor().getAttributeValueFromObject(currentObject); |
| return container != null ? container : cp.containerInstance(); |
| } else { |
| return cp.containerInstance(); |
| } |
| } |
| |
| Object result = null; |
| if (reuseContainer) { |
| Object currentObject = ((XMLRecord) row).getCurrentObject(); |
| Object container = getAttributeAccessor().getAttributeValueFromObject(currentObject); |
| result = container != null ? container : cp.containerInstance(); |
| } else { |
| result = cp.containerInstance(); |
| } |
| |
| for (Enumeration stream = nestedRows.elements(); stream.hasMoreElements();) { |
| Object next = stream.nextElement(); |
| if (next instanceof Element) { |
| XMLPlatformFactory.getInstance().getXMLPlatform().namespaceQualifyFragment((Element)next); |
| } |
| cp.addInto(next, result, executionSession); |
| } |
| return result; |
| } |
| |
| /** |
| * INTERNAL: |
| */ |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| if (this.isReadOnly()) { |
| return; |
| } |
| |
| Object attributeValue = this.getAttributeValueFromObject(object); |
| if (attributeValue == null) { |
| row.put(this.getField(), null); |
| return; |
| } |
| |
| ContainerPolicy cp = this.getContainerPolicy(); |
| |
| Vector elements = new Vector(cp.sizeFor(attributeValue)); |
| for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter);) { |
| Object element = cp.next(iter, session); |
| if (element != null) { |
| elements.addElement(element); |
| } |
| } |
| |
| Object fieldValue = null; |
| if (!elements.isEmpty()) { |
| fieldValue = this.getDescriptor().buildFieldValueFromDirectValues(elements, elementDataTypeName, session); |
| } |
| row.put(this.getField(), fieldValue); |
| } |
| |
| @Override |
| public boolean isAbstractCompositeDirectCollectionMapping() { |
| return false; |
| } |
| |
| @Override |
| public void writeSingleValue(Object attributeValue, Object parent, XMLRecord row, AbstractSession session) { |
| if (((XMLField)this.getField()).getLastXPathFragment().isAttribute()) { |
| if (attributeValue instanceof Attr) { |
| attributeValue = ((Attr)attributeValue).getValue(); |
| } |
| } else if (((XMLField)this.getField()).getLastXPathFragment().nameIsText()) { |
| if (attributeValue instanceof Text) { |
| attributeValue = ((Text)attributeValue).getNodeValue(); |
| } |
| } |
| row.put(getField(), attributeValue); |
| } |
| |
| @Override |
| public boolean isWriteOnly() { |
| return this.isWriteOnly; |
| } |
| |
| @Override |
| public void setIsWriteOnly(boolean b) { |
| this.isWriteOnly = b; |
| } |
| |
| @Override |
| public void preInitialize(AbstractSession session) throws DescriptorException { |
| getAttributeAccessor().setIsWriteOnly(this.isWriteOnly()); |
| getAttributeAccessor().setIsReadOnly(this.isReadOnly()); |
| super.preInitialize(session); |
| } |
| |
| @Override |
| public void setAttributeValueInObject(Object object, Object value) throws DescriptorException { |
| if(isWriteOnly()) { |
| return; |
| } |
| super.setAttributeValueInObject(object, value); |
| } |
| |
| /** |
| * Return true if the original container on the object should be used if |
| * present. If it is not present then the container policy will be used to |
| * create the container. |
| */ |
| @Override |
| public boolean getReuseContainer() { |
| return reuseContainer; |
| } |
| |
| /** |
| * Specify whether the original container on the object should be used if |
| * present. If it is not present then the container policy will be used to |
| * create the container. |
| */ |
| @Override |
| public void setReuseContainer(boolean reuseContainer) { |
| this.reuseContainer = reuseContainer; |
| } |
| |
| /** |
| * INTERNAL |
| * Return true if an empty container should be set on the object if there |
| * is no presence of the collection in the XML document. |
| * @since EclipseLink 2.3.3 |
| */ |
| @Override |
| public boolean isDefaultEmptyContainer() { |
| return defaultEmptyContainer; |
| } |
| |
| /** |
| * INTERNAL |
| * Indicate whether by default an empty container should be set on the |
| * field/property if the collection is not present in the XML document. |
| * @since EclipseLink 2.3.3 |
| */ |
| @Override |
| public void setDefaultEmptyContainer(boolean defaultEmptyContainer) { |
| this.defaultEmptyContainer = defaultEmptyContainer; |
| } |
| |
| @Override |
| public AbstractNullPolicy getWrapperNullPolicy() { |
| return this.wrapperNullPolicy; |
| } |
| |
| @Override |
| public void setWrapperNullPolicy(AbstractNullPolicy policy) { |
| this.wrapperNullPolicy = policy; |
| } |
| |
| } |