| /* |
| * 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 |
| package org.eclipse.persistence.oxm.record; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import java.util.Stack; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.eclipse.persistence.exceptions.EclipseLinkException; |
| import org.eclipse.persistence.exceptions.XMLMarshalException; |
| import org.eclipse.persistence.internal.core.helper.CoreClassConstants; |
| import org.eclipse.persistence.internal.core.helper.CoreField; |
| import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession; |
| import org.eclipse.persistence.internal.oxm.ConversionManager; |
| import org.eclipse.persistence.internal.oxm.Constants; |
| import org.eclipse.persistence.internal.oxm.Marshaller; |
| import org.eclipse.persistence.internal.oxm.Namespace; |
| import org.eclipse.persistence.internal.oxm.NamespaceResolver; |
| import org.eclipse.persistence.internal.oxm.ObjectBuilder; |
| import org.eclipse.persistence.internal.oxm.Root; |
| import org.eclipse.persistence.internal.oxm.XMLBinaryDataHelper; |
| import org.eclipse.persistence.internal.oxm.XPathPredicate; |
| import org.eclipse.persistence.internal.oxm.XPathFragment; |
| import org.eclipse.persistence.internal.oxm.XPathNode; |
| import org.eclipse.persistence.internal.oxm.mappings.Descriptor; |
| import org.eclipse.persistence.internal.oxm.mappings.Field; |
| import org.eclipse.persistence.internal.oxm.mappings.Login; |
| import org.eclipse.persistence.internal.oxm.record.AbstractMarshalRecordImpl; |
| import org.eclipse.persistence.oxm.record.ValidatingMarshalRecord.MarshalSAXParseException; |
| import org.eclipse.persistence.core.queries.CoreAttributeGroup; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Node; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.SAXException; |
| |
| /** |
| * <p>A MarshalRecord encapsulates the marshal target.</p> |
| * |
| * <p>MarshalRecords are stateful and state changes are triggered by different |
| * event notifications, therefore this class is not thread safe.</p> |
| * |
| * <p>XML document creation will differ depending on the subclass of MarshalRecord |
| * used. For example when NodeRecord is used a child element is created on the |
| * openStartElement event, and when the ContentHandlerRecord is used a child |
| * element is not created until the closeStartMethod event.</p> |
| * |
| * @see org.eclipse.persistence.oxm.XMLMarshaller |
| */ |
| public abstract class MarshalRecord<MARSHALLER extends Marshaller> extends AbstractMarshalRecordImpl<CoreAbstractSession, CoreField, MARSHALLER, NamespaceResolver> implements org.eclipse.persistence.internal.oxm.record.MarshalRecord<CoreAbstractSession, CoreField, MARSHALLER, NamespaceResolver> { |
| private ArrayList<XPathNode> groupingElements; |
| private HashMap positionalNodes; |
| |
| protected XPathFragment textWrapperFragment; |
| |
| private CycleDetectionStack<Object> cycleDetectionStack = new CycleDetectionStack<>(); |
| |
| private Stack<CoreAttributeGroup> attributeGroupStack; |
| |
| protected static final String COLON_W_SCHEMA_NIL_ATTRIBUTE = Constants.COLON + Constants.SCHEMA_NIL_ATTRIBUTE; |
| protected static final String TRUE = "true"; |
| |
| |
| protected MarshalRecord() { |
| super(null); |
| namespaceResolver = new NamespaceResolver(); |
| } |
| |
| @Override |
| public void forceValueWrapper(){} |
| |
| public HashMap getPositionalNodes() { |
| if (positionalNodes == null) { |
| positionalNodes = new HashMap(); |
| } |
| return positionalNodes; |
| } |
| |
| public String getLocalName() { |
| throw XMLMarshalException.operationNotSupported("getLocalName"); |
| } |
| |
| public String getNamespaceURI() { |
| throw XMLMarshalException.operationNotSupported("getNamespaceURI"); |
| } |
| |
| public void clear() { |
| throw XMLMarshalException.operationNotSupported("clear"); |
| } |
| |
| public Document getDocument() { |
| throw XMLMarshalException.operationNotSupported("getDocument"); |
| } |
| |
| @Override |
| public Node getDOM() { |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * If an XPathNode does not have an associated NodeValue then add it to the |
| * MarshalRecord as a grouping element. |
| */ |
| @Override |
| public void addGroupingElement(XPathNode xPathNode) { |
| if (null == groupingElements) { |
| groupingElements = new ArrayList(2); |
| } |
| groupingElements.add(xPathNode); |
| } |
| |
| /** |
| * |
| * INTERNAL: |
| */ |
| @Override |
| public void removeGroupingElement(XPathNode xPathNode) { |
| if (null != groupingElements) { |
| groupingElements.remove(xPathNode); |
| } |
| } |
| |
| public String transformToXML() { |
| return null; |
| } |
| |
| @Override |
| public void setSession(CoreAbstractSession session) { |
| super.setSession(session); |
| if (session != null && session.getDatasourceLogin() instanceof Login) { |
| this.equalNamespaceResolvers = ((Login) session.getDatasourceLogin()).hasEqualNamespaceResolvers(); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the field-value pair to the document. |
| */ |
| @Override |
| public void add(CoreField key, Object value) { |
| if (null == value) { |
| return; |
| } |
| Field xmlField = convertToXMLField(key); |
| XPathFragment lastFragment = xmlField.getLastXPathFragment(); |
| if (lastFragment.nameIsText()) { |
| characters(xmlField.getSchemaType(), value, null, xmlField.isCDATA()); |
| |
| } else if (lastFragment.isAttribute()) { |
| attribute(lastFragment, xmlField.getNamespaceResolver(), value, xmlField.getSchemaType()); |
| } else { |
| element(lastFragment); |
| } |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the field-value pair to the document. |
| */ |
| @Override |
| public Object put(CoreField key, Object value) { |
| add(key, value); |
| return null; |
| } |
| |
| /** |
| * INTERNAL: |
| * Add the namespace declarations to the XML document. |
| * @param namespaceResolver The NamespaceResolver contains the namespace |
| * prefix and URI pairings that need to be declared. |
| */ |
| public void namespaceDeclarations(NamespaceResolver namespaceResolver) { |
| if (namespaceResolver == null) { |
| return; |
| } |
| String namespaceURI = namespaceResolver.getDefaultNamespaceURI(); |
| if(null != namespaceURI) { |
| defaultNamespaceDeclaration(namespaceURI); |
| } |
| if(namespaceResolver.hasPrefixesToNamespaces()) { |
| for(Entry<String, String> entry: namespaceResolver.getPrefixesToNamespaces().entrySet()) { |
| String namespacePrefix = entry.getKey(); |
| namespaceDeclaration(namespacePrefix, entry.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * Handle marshal of an empty collection. |
| * @param openGrouping if grouping elements should be marshalled for empty collections |
| */ |
| @Override |
| public boolean emptyCollection(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, boolean openGrouping) { |
| if (openGrouping) { |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| closeStartGroupingElements(groupingFragment); |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Add the specified namespace declaration |
| */ |
| @Override |
| public void namespaceDeclaration(String prefix, String namespaceURI){ |
| attribute(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, prefix, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + Constants.COLON + prefix, namespaceURI); |
| } |
| |
| /** |
| * Add the defaultNamespace declaration |
| */ |
| public void defaultNamespaceDeclaration(String defaultNamespace){ |
| attribute(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE, javax.xml.XMLConstants.XMLNS_ATTRIBUTE, defaultNamespace); |
| } |
| |
| /** |
| * Receive notification that a document is being started. |
| * @param encoding The XML document will be encoded using this encoding. |
| * @param version This specifies the version of XML. |
| */ |
| public abstract void startDocument(String encoding, String version); |
| |
| /** |
| * INTERNAL |
| * Writes the header, if appropriate. |
| */ |
| public void writeHeader() { |
| } |
| |
| /** |
| * Recieve notification that a document is being ended. |
| */ |
| public abstract void endDocument(); |
| |
| /** |
| * INTERNAL |
| */ |
| public void marshalWithoutRootElement(ObjectBuilder treeObjectBuilder, Object object, Descriptor descriptor, Root root, boolean isXMLRoot){ |
| } |
| |
| /** |
| * Receive notification that a namespace has been declared. |
| * @param prefix The namespace prefix. |
| * @param namespaceURI The namespace URI. |
| */ |
| @Override |
| public void startPrefixMapping(String prefix, String namespaceURI) { |
| } |
| |
| public void startPrefixMappings(NamespaceResolver namespaceResolver) { |
| if (namespaceResolver != null && namespaceResolver.hasPrefixesToNamespaces()) { |
| for(Entry<String, String> entry: namespaceResolver.getPrefixesToNamespaces().entrySet()) { |
| startPrefixMapping(entry.getKey(), entry.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * Receive notification that the scope of this namespace declaration has |
| * ended. |
| * @param prefix The namespace prefix. |
| */ |
| @Override |
| public void endPrefixMapping(String prefix) { |
| } |
| |
| public void endPrefixMappings(NamespaceResolver namespaceResolver) { |
| } |
| |
| /** |
| * Receive notification that an element is being started. |
| * @param xPathFragment The XPathFragment contains the name and prefix |
| * information about the XML element being ended. |
| * @param namespaceResolver The NamespaceResolver can be used to resolve the |
| * namespace URI for the namespace prefix held by the XPathFragment (if |
| * required). |
| */ |
| @Override |
| public void openStartElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) { |
| this.addPositionalNodes(xPathFragment, namespaceResolver); |
| } |
| |
| /** |
| * Receive notification of an element. |
| * @param frag The XPathFragment of the element |
| */ |
| public abstract void element(XPathFragment frag); |
| |
| /** |
| * Receive notification of an attribute. |
| * @param xPathFragment The XPathFragment contains the name and prefix |
| * information about the XML element being ended. |
| * @param namespaceResolver The NamespaceResolver can be used to resolve the |
| * namespace URI for the namespace prefix held by the XPathFragment (if |
| * required). |
| * @param value This is the complete value for the attribute. |
| */ |
| @Override |
| public abstract void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, String value); |
| |
| /** |
| * Receive notification of an attribute. |
| * @param namespaceURI The namespace URI, if the attribute is not namespace |
| * qualified the value of this parameter wil be null. |
| * @param localName The local name of the attribute. |
| * @param qName The qualified name of the attribute. |
| * @param value This is the complete value for the attribute. |
| */ |
| @Override |
| public abstract void attribute(String namespaceURI, String localName, String qName, String value); |
| |
| |
| /** |
| * Receive notification that all of the attribute events have occurred for |
| * the most recent element that has been started. |
| */ |
| @Override |
| public abstract void closeStartElement(); |
| |
| /** |
| * Receive notification that an element is being ended. |
| * @param xPathFragment The XPathFragment contains the name and prefix |
| * information about the XML element being ended. |
| * @param namespaceResolver The NamespaceResolver can be used to resolve the |
| * namespace URI for the namespace prefix held by the XPathFragment (if |
| * required). |
| */ |
| @Override |
| public abstract void endElement(XPathFragment xPathFragment, NamespaceResolver namespaceResolver); |
| |
| /** |
| * Receive notification of character data. |
| * @param value This is the entire value of the text node. |
| */ |
| @Override |
| public abstract void characters(String value); |
| |
| /** |
| * Convert the value if necessary and write out the attribute and converted value. |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void attribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver, Object value, QName schemaType){ |
| if(schemaType != null && Constants.QNAME_QNAME.equals(schemaType)){ |
| String convertedValue = getStringForQName((QName)value); |
| attribute(xPathFragment, namespaceResolver, convertedValue); |
| } else{ |
| String convertedValue = ((ConversionManager) session.getDatasourcePlatform().getConversionManager()).convertObject(value, CoreClassConstants.STRING, schemaType); |
| attribute(xPathFragment, namespaceResolver, convertedValue); |
| } |
| } |
| |
| /** |
| * Convert the value if necessary and write out the converted value. |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void characters(QName schemaType, Object value, String mimeType, boolean isCDATA){ |
| if(mimeType != null) { |
| if(value instanceof List){ |
| value = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesListForBinaryValues(// |
| (List)value, marshaller, mimeType); |
| }else{ |
| value = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesForBinaryValue(// |
| value, marshaller, mimeType).getData(); |
| } |
| } |
| if(schemaType != null && Constants.QNAME_QNAME.equals(schemaType)){ |
| String convertedValue = getStringForQName((QName)value); |
| characters(convertedValue); |
| }else{ |
| String convertedValue = ((ConversionManager) session.getDatasourcePlatform().getConversionManager()).convertObject(value, CoreClassConstants.STRING, schemaType); |
| if(isCDATA){ |
| cdata(convertedValue); |
| }else{ |
| characters(convertedValue); |
| } |
| } |
| } |
| |
| @Override |
| public String getValueToWrite(QName schemaType, Object value, ConversionManager conversionManager) { |
| if(value == null){ |
| return null; |
| } |
| if(schemaType != null && Constants.QNAME_QNAME.equals(schemaType)){ |
| return getStringForQName((QName)value); |
| }else if(value.getClass() == String.class){ |
| return (String) value; |
| } |
| return conversionManager.convertObject(value, CoreClassConstants.STRING, schemaType); |
| } |
| |
| protected String getStringForQName(QName qName){ |
| if(null == qName) { |
| return null; |
| } |
| String namespaceURI = qName.getNamespaceURI(); |
| if(null == namespaceURI || 0 == namespaceURI.length()) { |
| if(getNamespaceResolver() != null && getNamespaceResolver().getDefaultNamespaceURI() != null) { |
| //need to add a default namespace declaration. |
| defaultNamespaceDeclaration(namespaceURI); |
| } |
| return qName.getLocalPart(); |
| } else { |
| NamespaceResolver namespaceResolver = getNamespaceResolver(); |
| if(namespaceResolver == null){ |
| throw XMLMarshalException.namespaceResolverNotSpecified(namespaceURI); |
| } |
| if(namespaceURI.equals(namespaceResolver.getDefaultNamespaceURI())) { |
| return qName.getLocalPart(); |
| } |
| String prefix = namespaceResolver.resolveNamespaceURI(namespaceURI); |
| if(null == prefix) { |
| if(namespaceURI.equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)){ |
| prefix = namespaceResolver.generatePrefix(Constants.SCHEMA_PREFIX); |
| }else{ |
| prefix = namespaceResolver.generatePrefix(); |
| } |
| namespaceDeclaration(prefix, namespaceURI); |
| } |
| if(Constants.EMPTY_STRING.equals(prefix)){ |
| return qName.getLocalPart(); |
| } |
| return prefix + Constants.COLON + qName.getLocalPart(); |
| } |
| } |
| |
| |
| /** |
| * Receive notification of character data to be wrapped in a CDATA node. |
| * @param value This is the value of the text to be wrapped |
| */ |
| @Override |
| public abstract void cdata(String value); |
| |
| /** |
| * Receive notification of a node. |
| * @param node The Node to be added to the document |
| * @param resolver The NamespaceResolver can be used to resolve the |
| * namespace URI/prefix of the node |
| */ |
| @Override |
| public void node(Node node, NamespaceResolver resolver ){ |
| node(node, resolver, null, null); |
| } |
| |
| /** |
| * Receive notification of a node. |
| * @param node The Node to be added to the document |
| * @param resolver The NamespaceResolver can be used to resolve the |
| * @param qualifiedName replacement root name for the node |
| * @param rootUri replacement root namespace for the node |
| * namespace URI/prefix of the node |
| */ |
| public abstract void node(Node node, NamespaceResolver resolver, String qualifiedName, String rootUri); |
| |
| /** |
| * INTERNAL: |
| * Trigger that the grouping elements should be written. This is normally |
| * done when something like a mapping has a non-null value that is |
| * marshalled. |
| * @param namespaceResolver The NamespaceResolver can be used to resolve the |
| * namespace URI for the namespace prefix held by the XPathFragment (if |
| * required). |
| */ |
| @Override |
| public XPathFragment openStartGroupingElements(NamespaceResolver namespaceResolver) { |
| if (null == groupingElements) { |
| return null; |
| } |
| XPathFragment xPathFragment = null; |
| for (int x = 0, groupingElementsSize = groupingElements.size(); x < groupingElementsSize; x++) { |
| XPathNode xPathNode = groupingElements.get(x); |
| xPathFragment = xPathNode.getXPathFragment(); |
| openStartElement(xPathFragment, namespaceResolver); |
| |
| predicateAttribute(xPathFragment, namespaceResolver); |
| |
| if (x != (groupingElementsSize - 1)) { |
| closeStartElement(); |
| } |
| } |
| groupingElements = null; |
| return xPathFragment; |
| } |
| |
| @Override |
| public void closeStartGroupingElements(XPathFragment groupingFragment) { |
| if (null != groupingFragment) { |
| this.closeStartElement(); |
| } |
| } |
| |
| protected void addPositionalNodes(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) { |
| if (xPathFragment.containsIndex()) { |
| String shortName = xPathFragment.getShortName(); |
| Integer index = (Integer)getPositionalNodes().get(shortName); |
| int start; |
| if (null == index) { |
| start = 1; |
| } else { |
| start = index; |
| } |
| for (int x = start; x < xPathFragment.getIndexValue(); x++) { |
| element(xPathFragment); |
| } |
| getPositionalNodes().put(shortName, xPathFragment.getIndexValue() + 1); |
| } |
| } |
| |
| @Override |
| public void beforeContainmentMarshal(Object child) { |
| if(null != marshaller) { |
| Marshaller.Listener marshalListener = marshaller.getMarshalListener(); |
| if(null != marshalListener) { |
| try { |
| marshalListener.beforeMarshal(child); |
| } catch(EclipseLinkException e) { |
| ErrorHandler errorHandler = marshaller.getErrorHandler(); |
| if(null == errorHandler) { |
| throw e; |
| } else { |
| try { |
| MarshalSAXParseException saxParseException = new MarshalSAXParseException(null, null, null, -1, -1, e, child); |
| errorHandler.error(saxParseException); |
| } catch(SAXException saxParseException) { |
| throw e; |
| } |
| } |
| } |
| } |
| } |
| setOwningObject(child); |
| } |
| |
| @Override |
| public void afterContainmentMarshal(Object parent, Object child) { |
| if(null != marshaller) { |
| Marshaller.Listener marshalListener = marshaller.getMarshalListener(); |
| if(null != marshalListener) { |
| try { |
| marshalListener.afterMarshal(child); |
| } catch(EclipseLinkException e) { |
| ErrorHandler errorHandler = marshaller.getErrorHandler(); |
| if(null == errorHandler) { |
| throw e; |
| } else { |
| try { |
| MarshalSAXParseException saxParseException = new MarshalSAXParseException(null, null, null, -1, -1, e, child); |
| errorHandler.error(saxParseException); |
| } catch(SAXException saxParseException) { |
| throw e; |
| } |
| } |
| } |
| } |
| } |
| setOwningObject(parent); |
| } |
| |
| /** |
| * INTERNAL: |
| * Returns the list of grouping elements currently stored on the MarshalRecord |
| */ |
| @Override |
| public ArrayList<XPathNode> getGroupingElements() { |
| return this.groupingElements; |
| } |
| |
| /** |
| * INTERNAL: |
| * Sets the list of grouping elements to be marshalled on this record. |
| */ |
| @Override |
| public void setGroupingElement(ArrayList<XPathNode> elements) { |
| this.groupingElements = elements; |
| } |
| |
| /** |
| * Marshal the attribute for the predicate if one was specified. |
| */ |
| @Override |
| public void predicateAttribute(XPathFragment xPathFragment, NamespaceResolver namespaceResolver) { |
| if(null != xPathFragment) { |
| XPathPredicate predicate = xPathFragment.getPredicate(); |
| if(null != predicate) { |
| XPathFragment predicateXPathFragment = predicate.getXPathFragment(); |
| if(predicateXPathFragment.isAttribute()) { |
| attribute(predicateXPathFragment, namespaceResolver, predicate.getValue()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method is used to inform the MarshalRecord that the element events |
| * it is about to receive are part of a collection. |
| * @since EclipseLink 2.4 |
| * @see #endCollection |
| */ |
| @Override |
| public void startCollection() { |
| } |
| |
| /** |
| * Used when an nil attribute should be written |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void emptyAttribute(XPathFragment xPathFragment,NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| // We mutate the null into an empty string |
| attribute(xPathFragment, namespaceResolver, Constants.EMPTY_STRING); |
| closeStartGroupingElements(groupingFragment); |
| } |
| |
| |
| /** |
| * Used when an nil attribute should be written |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void emptyComplex(XPathFragment xPathFragment, NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| closeStartGroupingElements(groupingFragment); |
| openStartElement(xPathFragment, namespaceResolver); |
| closeStartElement(); |
| endElement(xPathFragment, namespaceResolver); |
| } |
| |
| /** |
| * Used when an nil attribute should be written |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void emptySimple(NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| closeStartGroupingElements(groupingFragment); |
| } |
| |
| /** |
| * Used when an nil attribute should be written |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void nilSimple(NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| String xsiPrefix = processNamespaceResolverForXSIPrefix(namespaceResolver); |
| StringBuilder qName = new StringBuilder(Constants.ATTRIBUTE); // Unsynchronized |
| qName.append(xsiPrefix).append(COLON_W_SCHEMA_NIL_ATTRIBUTE); |
| XPathFragment nilFragment = new XPathFragment(qName.toString()); |
| nilFragment.setNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); |
| attribute(nilFragment, namespaceResolver, TRUE); |
| closeStartGroupingElements(groupingFragment); |
| } |
| |
| /** |
| * Used when an nil attribute should be written |
| * @since EclipseLink 2.4 |
| */ |
| @Override |
| public void nilComplex(XPathFragment xPathFragment, NamespaceResolver namespaceResolver){ |
| XPathFragment groupingFragment = openStartGroupingElements(namespaceResolver); |
| closeStartGroupingElements(groupingFragment); |
| openStartElement(xPathFragment, namespaceResolver); |
| String xsiPrefix = processNamespaceResolverForXSIPrefix(namespaceResolver); |
| XPathFragment nilFragment = new XPathFragment(Constants.ATTRIBUTE + xsiPrefix + COLON_W_SCHEMA_NIL_ATTRIBUTE); |
| nilFragment.setNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); |
| attribute(nilFragment, namespaceResolver, TRUE); |
| closeStartElement(); |
| endElement(xPathFragment, namespaceResolver); |
| } |
| |
| |
| /** |
| * This method is used to inform the MarshalRecord that it is done receiving |
| * element events that are part of a collection. |
| * @since EclipseLink 2.4 |
| * @see #startCollection |
| */ |
| @Override |
| public void endCollection() { |
| } |
| |
| |
| /** |
| * INTERNAL: |
| * Private function to process or create an entry in the NamespaceResolver for the xsi prefix. |
| * @return xsi prefix |
| * @since EclipseLink 2.4 |
| |
| */ |
| protected String processNamespaceResolverForXSIPrefix(NamespaceResolver namespaceResolver) { |
| String xsiPrefix; |
| if (null == namespaceResolver) { |
| // add new xsi entry into the properties map |
| xsiPrefix = Constants.SCHEMA_INSTANCE_PREFIX; |
| namespaceResolver = new NamespaceResolver(); |
| namespaceResolver.put(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); |
| namespaceDeclaration(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); |
| } else { |
| // find an existing xsi entry in the map |
| xsiPrefix = namespaceResolver.resolveNamespaceURI(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); |
| if (null == xsiPrefix) { |
| xsiPrefix = namespaceResolver.generatePrefix(Constants.SCHEMA_INSTANCE_PREFIX); |
| namespaceDeclaration(xsiPrefix, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI); |
| } |
| } |
| return xsiPrefix; |
| } |
| |
| /** |
| * INTERNAL: |
| * The optional fragment used to wrap the text() mappings |
| * @since 2.4 |
| */ |
| @Override |
| public XPathFragment getTextWrapperFragment() { |
| //return null as this is not supported by default |
| //subclass records can return the fragment if supported. |
| return null; |
| } |
| |
| protected String getNameForFragment(XPathFragment xPathFragment) { |
| if(!this.hasCustomNamespaceMapper()) { |
| return xPathFragment.getShortName(); |
| } |
| if(xPathFragment.getNamespaceURI() != null && xPathFragment.getNamespaceURI().length() > 0) { |
| String prefix = this.getPrefixForFragment(xPathFragment); |
| if(prefix != null && prefix.length() > 0) { |
| return prefix + Constants.COLON + xPathFragment.getLocalName(); |
| } |
| } |
| return xPathFragment.getLocalName(); |
| } |
| |
| protected byte[] getPrefixBytes(XPathFragment xPathFragment) { |
| if(!this.hasCustomNamespaceMapper()) { |
| return xPathFragment.getPrefixBytes(); |
| } |
| String prefix = this.getPrefixForFragment(xPathFragment); |
| if(null == prefix || prefix.isEmpty()) { |
| return null; |
| } |
| return prefix.getBytes(Constants.DEFAULT_CHARSET); |
| } |
| |
| protected String getPrefixForFragment(XPathFragment xPathFragment) { |
| if(!hasCustomNamespaceMapper) { |
| return xPathFragment.getPrefix(); |
| } |
| String uri = xPathFragment.getNamespaceURI(); |
| if(uri == null || uri.length() == 0) { |
| return Constants.EMPTY_STRING; |
| } |
| |
| String defaultNamespace = getNamespaceResolver().getDefaultNamespaceURI(); |
| |
| if(defaultNamespace != null && defaultNamespace.equals(uri)) { |
| return Constants.EMPTY_STRING; |
| } |
| String prefix = this.getNamespaceResolver().resolveNamespaceURI(uri); |
| |
| if(prefix != null) { |
| return prefix; |
| } |
| for(Object next:getNamespaceResolver().getNamespaces()) { |
| Namespace ns = (Namespace)next; |
| uri = ns.getNamespaceURI(); |
| prefix = ns.getPrefix(); |
| } |
| return xPathFragment.getPrefix(); |
| } |
| |
| /** |
| * INTERNAL |
| */ |
| @Override |
| public CycleDetectionStack<Object> getCycleDetectionStack() { |
| return this.cycleDetectionStack; |
| } |
| |
| private Field convertToXMLField(CoreField field) { |
| return (Field) field; |
| } |
| |
| @Override |
| public boolean isWrapperAsCollectionName() { |
| return false; |
| } |
| |
| @Override |
| public CoreAttributeGroup getCurrentAttributeGroup() { |
| if(this.attributeGroupStack == null || this.attributeGroupStack.isEmpty()) { |
| return DEFAULT_ATTRIBUTE_GROUP; |
| } |
| return attributeGroupStack.peek(); |
| } |
| |
| @Override |
| public void pushAttributeGroup(CoreAttributeGroup group) { |
| if(group == DEFAULT_ATTRIBUTE_GROUP && this.attributeGroupStack == null) { |
| return; |
| } |
| if(this.attributeGroupStack == null) { |
| this.attributeGroupStack = new Stack<>(); |
| } |
| this.attributeGroupStack.push(group); |
| } |
| |
| @Override |
| public void popAttributeGroup() { |
| if(attributeGroupStack != null) { |
| attributeGroupStack.pop(); |
| } |
| } |
| |
| @Override |
| public void flush() { |
| } |
| |
| /** |
| * INTERNAL: |
| * Convenience method for splitting CDATA content to parts |
| * so that {@code "]]>"} occurrences are eliminated. |
| * <p> |
| * For example {@code splitCData("a]]>b")} returns list of two elements, |
| * {@code "a]]"} and {@code ">b"}. |
| * <p> |
| * This method is intended for use in {@link #cdata(String)} |
| * implementations where appropriate. |
| * |
| * @param value for CDATA section possibly containing {@code "]]>"} |
| * @return list of CDATA-valid chunks of the input |
| */ |
| static List<String> splitCData(String value) { |
| if (value == null) { |
| return Collections.emptyList(); |
| } |
| List<String> parts = new LinkedList<>(); |
| int index = -1; |
| String remainder = value; |
| while (0 <= (index = remainder.indexOf("]]>"))) { |
| String part = remainder.substring(0, index + 2); |
| parts.add(part); |
| remainder = remainder.substring(index + 2); |
| } |
| parts.add(remainder); |
| return parts; |
| } |
| } |