| /* |
| * 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.mappings; |
| |
| import java.util.Enumeration; |
| import java.util.Vector; |
| |
| import jakarta.activation.DataHandler; |
| |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.XMLMarshalException; |
| import org.eclipse.persistence.internal.helper.ClassConstants; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.identitymaps.CacheKey; |
| import org.eclipse.persistence.internal.oxm.XMLBinaryDataHelper; |
| import org.eclipse.persistence.internal.oxm.XMLConversionManager; |
| import org.eclipse.persistence.internal.oxm.mappings.BinaryDataCollectionMapping; |
| 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.converters.Converter; |
| import org.eclipse.persistence.oxm.NamespaceResolver; |
| import org.eclipse.persistence.oxm.XMLConstants; |
| import org.eclipse.persistence.oxm.XMLDescriptor; |
| import org.eclipse.persistence.oxm.XMLField; |
| import org.eclipse.persistence.oxm.XMLMarshaller; |
| import org.eclipse.persistence.oxm.XMLUnmarshaller; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.AbstractNullPolicy; |
| import org.eclipse.persistence.oxm.mappings.nullpolicy.XMLNullRepresentationType; |
| import org.eclipse.persistence.oxm.record.DOMRecord; |
| import org.eclipse.persistence.oxm.record.XMLRecord; |
| import org.eclipse.persistence.queries.ObjectBuildingQuery; |
| import org.eclipse.persistence.sessions.Session; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| |
| /** |
| * <p><b>Purpose:</b>Provide a mapping for a collection of binary data values that can be treated |
| * as either inline binary values or as an attachment. |
| * <p><b>Responsibilities:</b><ul> |
| * <li>Handle converting binary types (byte[], Image etc) to base64</li> |
| * <li>Make callbacks to AttachmentMarshaller/AttachmentUnmarshaller</li> |
| * <li>Write out approriate attachment information (xop:include) </li> |
| * </ul> |
| * <p>XMLBinaryDataCollectionMapping represents a mapping of a collection of binary data in the object model |
| * to XML. This can either be written directly as inline binary data (base64) or |
| * passed through as an MTOM or SWAREF attachment. |
| * <p>The following types are allowable to be mapped using an XMLBinaryDataMapping:<ul> |
| * <li>java.awt.Image</li> |
| * <li>byte[]</li> |
| * <li>jakarta.activation.DataHandler</li> |
| * <li>javax.xml.transform.Source</li> |
| * <li>jakarta.mail.internet.MimeMultipart</li> |
| * </ul> |
| * <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 |
| * node for the binary mapping. The XPath is specified on the mapping using the <code>setXPath</code> |
| * method. |
| * |
| * <p><b>Inline Binary Data</b>: Set this flag if you want to always inline binary data for this mapping. |
| * This will disable consideration for attachment handling for this mapping. |
| * |
| * <p><b>SwaRef</b>: Set this flag in order to specify that the target node of this mapping is of type |
| * xs:swaref |
| * |
| * @see org.eclipse.persistence.oxm.attachment.XMLAttachmentMarshaller |
| * @see org.eclipse.persistence.oxm.attachment.XMLAttachmentUnmarshaller |
| * @see org.eclipse.persistence.oxm.mappings.MimeTypePolicy |
| * @since TopLink 11.1.1.0.0g |
| */ |
| public class XMLBinaryDataCollectionMapping extends XMLCompositeDirectCollectionMapping implements BinaryDataCollectionMapping<AbstractSession, AttributeAccessor, ContainerPolicy, Converter, ClassDescriptor, DatabaseField, XMLMarshaller, MimeTypePolicy, Session, XMLUnmarshaller, XMLRecord> { |
| private boolean shouldInlineBinaryData; |
| private MimeTypePolicy mimeTypePolicy; |
| private boolean isSwaRef; |
| private Class collectionContentType; |
| private static final String INCLUDE = "Include"; |
| |
| public XMLBinaryDataCollectionMapping() { |
| collectionContentType = ClassConstants.APBYTE; |
| mimeTypePolicy = new FixedMimeTypePolicy(null); |
| } |
| |
| @Override |
| public boolean shouldInlineBinaryData() { |
| return shouldInlineBinaryData; |
| } |
| |
| @Override |
| public void setShouldInlineBinaryData(boolean b) { |
| shouldInlineBinaryData = b; |
| } |
| |
| /** |
| * INTERNAL |
| */ |
| @Override |
| public String getMimeType(Object anObject) { |
| if (mimeTypePolicy == null) { |
| return null; |
| } else { |
| return mimeTypePolicy.getMimeType(anObject); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| */ |
| @Override |
| public String getMimeType() { |
| return getMimeType(null); |
| } |
| |
| @Override |
| public MimeTypePolicy getMimeTypePolicy() { |
| return mimeTypePolicy; |
| } |
| |
| /** |
| * Allow implementer to set the MimeTypePolicy class FixedMimeTypePolicy or AttributeMimeTypePolicy (dynamic) |
| * @param mimeTypePolicy MimeTypePolicy |
| */ |
| @Override |
| public void setMimeTypePolicy(MimeTypePolicy mimeTypePolicy) { |
| this.mimeTypePolicy = mimeTypePolicy; |
| } |
| |
| /** |
| * Force mapping to set default FixedMimeTypePolicy using the MimeType string as argument |
| */ |
| public void setMimeType(String mimeTypeString) { |
| // use the following to set dynamically - mapping.setMimeTypePolicy(new FixedMimeTypePolicy(property.getMimeType())); |
| mimeTypePolicy = new FixedMimeTypePolicy(mimeTypeString); |
| } |
| |
| @Override |
| public boolean isSwaRef() { |
| return isSwaRef; |
| } |
| |
| @Override |
| public void setSwaRef(boolean swaRef) { |
| isSwaRef = swaRef; |
| } |
| |
| @Override |
| public boolean isAbstractCompositeDirectCollectionMapping() { |
| return false; |
| } |
| |
| /** |
| * Set the Mapping field name attribute to the given XPath String |
| * @param xpathString String |
| */ |
| @Override |
| public void setXPath(String xpathString) { |
| XMLField field = new XMLField(xpathString); |
| field.setSchemaType(XMLConstants.BASE_64_BINARY_QNAME); |
| setField(new XMLField(xpathString)); |
| } |
| |
| @Override |
| public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session, WriteType writeType) { |
| XMLRecord record = (XMLRecord) row; |
| Object attributeValue = getAttributeValueFromObject(object); |
| |
| ContainerPolicy cp = this.getContainerPolicy(); |
| Vector elements = new Vector(cp.sizeFor(attributeValue)); |
| XMLField field = (XMLField) getField(); |
| NamespaceResolver resolver = field.getNamespaceResolver(); |
| boolean isAttribute = field.getLastXPathFragment().isAttribute(); |
| String prefix = null; |
| XMLField includeField = null; |
| if (!isAttribute) { |
| if (record.isXOPPackage() && !isSwaRef() && !shouldInlineBinaryData()) { |
| field = (XMLField) getField(); |
| |
| // If the field's resolver is non-null and has an entry for XOP, |
| // use it - otherwise, create a new resolver, set the XOP entry, |
| // on it, and use it instead. |
| // We do this to avoid setting the XOP namespace declaration on |
| // a given field or descriptor's resolver, as it is only required |
| // on the current element |
| if (resolver != null) { |
| prefix = resolver.resolveNamespaceURI(XMLConstants.XOP_URL); |
| } |
| if (prefix == null) { |
| prefix = XMLConstants.XOP_PREFIX;//"xop"; |
| resolver = new NamespaceResolver(); |
| resolver.put(prefix, XMLConstants.XOP_URL); |
| } |
| includeField = new XMLField(prefix + XMLConstants.COLON + INCLUDE + "/@href"); |
| includeField.setNamespaceResolver(resolver); |
| } |
| } |
| XMLField textField = new XMLField(field.getXPath() + '/' + XMLConstants.TEXT); |
| textField.setNamespaceResolver(field.getNamespaceResolver()); |
| textField.setSchemaType(field.getSchemaType()); |
| //field = textField; |
| |
| boolean inline = false; |
| for (Object iter = cp.iteratorFor(attributeValue); cp.hasNext(iter);) { |
| Object element = cp.next(iter, session); |
| element = getValueToWrite(element, object, record, field, includeField, session); |
| if(element == null){ |
| AbstractNullPolicy nullPolicy = getNullPolicy(); |
| if (nullPolicy == null) { |
| elements.addElement(null); |
| } else { |
| if (nullPolicy.getMarshalNullRepresentation() == XMLNullRepresentationType.XSI_NIL) { |
| elements.addElement(XMLRecord.NIL); |
| } else if (nullPolicy.getMarshalNullRepresentation() == XMLNullRepresentationType.ABSENT_NODE) { |
| // Do nothing |
| } else { |
| elements.addElement(XMLConstants.EMPTY_STRING); |
| } |
| } |
| }else{ |
| |
| |
| if(element.getClass() == ClassConstants.ABYTE) { |
| inline = true; |
| } |
| elements.addElement(element); |
| } |
| } |
| Object fieldValue = null; |
| if (!elements.isEmpty()) { |
| fieldValue = this.getDescriptor().buildFieldValueFromDirectValues(elements, elementDataTypeName, session); |
| } |
| if(inline) { |
| row.put(textField, fieldValue); |
| } else { |
| row.put(field, fieldValue); |
| } |
| } |
| |
| public Object getValueToWrite(Object value, Object parent, XMLRecord record, XMLField field, XMLField includeField, AbstractSession session) { |
| XMLMarshaller marshaller = record.getMarshaller(); |
| Object element = convertObjectValueToDataValue(value, session, record.getMarshaller()); |
| boolean isAttribute = ((XMLField) getField()).getLastXPathFragment().isAttribute(); |
| |
| if(element == null){ |
| return null; |
| } |
| |
| if (isAttribute) { |
| if (isSwaRef() && (marshaller.getAttachmentMarshaller() != null)) { |
| //should be a DataHandler here |
| try { |
| String id = marshaller.getAttachmentMarshaller().addSwaRefAttachment((DataHandler) element); |
| element = id; |
| } catch (ClassCastException cce) { |
| throw XMLMarshalException.invalidSwaRefAttribute(getAttributeClassification().getName()); |
| } |
| } else { |
| //inline case |
| XMLBinaryDataHelper.EncodedData data = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesForBinaryValue(element, record.getMarshaller(), mimeTypePolicy.getMimeType(parent)); |
| String base64Value = ((XMLConversionManager) session.getDatasourcePlatform().getConversionManager()).buildBase64StringFromBytes(data.getData()); |
| element = base64Value; |
| } |
| } else { |
| if (record.isXOPPackage() && !isSwaRef() && !shouldInlineBinaryData()) { |
| //write as attachment |
| String c_id = XMLConstants.EMPTY_STRING; |
| byte[] bytes = null; |
| if ((getAttributeElementClass() == ClassConstants.ABYTE) || (getAttributeElementClass() == ClassConstants.APBYTE)) { |
| if (getAttributeElementClass() == ClassConstants.ABYTE) { |
| element = session.getDatasourcePlatform().getConversionManager().convertObject(element, ClassConstants.APBYTE); |
| } |
| bytes = (byte[])element; |
| c_id = marshaller.getAttachmentMarshaller().addMtomAttachment(bytes, 0, bytes.length, this.mimeTypePolicy.getMimeType(parent), field.getLastXPathFragment().getLocalName(), |
| field.getLastXPathFragment().getNamespaceURI()); |
| } else if (getAttributeElementClass() == XMLBinaryDataHelper.getXMLBinaryDataHelper().DATA_HANDLER) { |
| c_id = marshaller.getAttachmentMarshaller().addMtomAttachment((DataHandler) element, field.getLastXPathFragment().getLocalName(), field.getLastXPathFragment().getNamespaceURI()); |
| if(c_id == null) { |
| XMLBinaryDataHelper.EncodedData data = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesForBinaryValue(element, marshaller, this.mimeTypePolicy.getMimeType(parent)); |
| bytes = data.getData(); |
| } |
| } else { |
| XMLBinaryDataHelper.EncodedData data = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesForBinaryValue(element, marshaller, this.mimeTypePolicy.getMimeType(parent)); |
| bytes = data.getData(); |
| c_id = marshaller.getAttachmentMarshaller().addMtomAttachment(bytes, 0, bytes.length, data.getMimeType(), field.getLastXPathFragment().getLocalName(), field.getLastXPathFragment().getNamespaceURI()); |
| } |
| |
| if(c_id == null) { |
| element = bytes; |
| } else { |
| DOMRecord include = new DOMRecord(field.getLastXPathFragment().getLocalName()); |
| include.setSession(session); |
| include.put(includeField, c_id); |
| element = include; |
| |
| // Need to call setAttributeNS on the record, unless the xop prefix |
| // is defined on the descriptor's resolver already |
| NamespaceResolver resolver = ((XMLField) getField()).getNamespaceResolver(); |
| if (resolver == null || resolver.resolveNamespaceURI(XMLConstants.XOP_URL) == null) { |
| resolver = new NamespaceResolver(); |
| resolver.put(XMLConstants.XOP_PREFIX, XMLConstants.XOP_URL); |
| String xpath = XMLConstants.XOP_PREFIX + XMLConstants.COLON + INCLUDE; |
| XMLField incField = new XMLField(xpath); |
| incField.setNamespaceResolver(resolver); |
| Object obj = include.getIndicatingNoEntry(incField); |
| if (obj != null && obj instanceof DOMRecord) { |
| if (((DOMRecord) obj).getDOM().getNodeType() == Node.ELEMENT_NODE) { |
| ((Element) ((DOMRecord) obj).getDOM()).setAttributeNS(javax.xml.XMLConstants.XMLNS_ATTRIBUTE_NS_URI, javax.xml.XMLConstants.XMLNS_ATTRIBUTE + XMLConstants.COLON + XMLConstants.XOP_PREFIX, XMLConstants.XOP_URL); |
| } |
| } |
| } |
| } |
| } else if (isSwaRef() && (marshaller.getAttachmentMarshaller() != null)) { |
| //element should be a data-handler |
| try { |
| String c_id = marshaller.getAttachmentMarshaller().addSwaRefAttachment((DataHandler) element); |
| element = c_id; |
| } catch (Exception ex) { |
| } |
| } else { |
| //inline |
| if (!((getAttributeElementClass() == ClassConstants.ABYTE) || (getAttributeElementClass() == ClassConstants.APBYTE))) { |
| element = XMLBinaryDataHelper.getXMLBinaryDataHelper().getBytesForBinaryValue(element, marshaller, this.mimeTypePolicy.getMimeType(parent)).getData(); |
| } |
| } |
| } |
| return element; |
| } |
| |
| @Override |
| public void writeSingleValue(Object value, Object parent, XMLRecord record, AbstractSession session) { |
| XMLField field = (XMLField) getField(); |
| NamespaceResolver resolver = field.getNamespaceResolver(); |
| boolean isAttribute = field.getLastXPathFragment().isAttribute(); |
| String prefix = null; |
| XMLField includeField = null; |
| if (!isAttribute) { |
| if (record.isXOPPackage() && !isSwaRef() && !shouldInlineBinaryData()) { |
| field = (XMLField) getField(); |
| |
| // If the field's resolver is non-null and has an entry for XOP, |
| // use it - otherwise, create a new resolver, set the XOP entry, |
| // on it, and use it instead. |
| // We do this to avoid setting the XOP namespace declaration on |
| // a given field or descriptor's resolver, as it is only required |
| // on the current element |
| if (resolver != null) { |
| prefix = resolver.resolveNamespaceURI(XMLConstants.XOP_URL); |
| } |
| if (prefix == null) { |
| prefix = XMLConstants.XOP_PREFIX;//"xop"; |
| resolver = new NamespaceResolver(); |
| resolver.put(prefix, XMLConstants.XOP_URL); |
| } |
| |
| includeField = new XMLField(prefix + XMLConstants.COLON + INCLUDE + "/@href"); |
| includeField.setNamespaceResolver(resolver); |
| } |
| } |
| XMLField textField = new XMLField(field.getXPath() + '/' +XMLConstants.TEXT); |
| textField.setNamespaceResolver(field.getNamespaceResolver()); |
| textField.setSchemaType(field.getSchemaType()); |
| |
| Object valueToWrite = getValueToWrite(value, parent, record, field, includeField, session); |
| if(!isAttribute && valueToWrite.getClass() == ClassConstants.ABYTE) { |
| //if the returned value is a byte[] and not an XMLRecord, just write it inline |
| record.add(textField, valueToWrite); |
| } |
| record.add(field, valueToWrite); |
| } |
| |
| @Override |
| public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query, CacheKey cacheKey, AbstractSession executionSession, boolean isTargetProtected, Boolean[] wasCacheUsed) { |
| ContainerPolicy cp = this.getContainerPolicy(); |
| |
| Object fieldValue = row.getValues(this.getField()); |
| if (fieldValue == null) { |
| if (reuseContainer) { |
| Object currentObject = ((XMLRecord) row).getCurrentObject(); |
| Object container = getAttributeAccessor().getAttributeValueFromObject(currentObject); |
| return container != null ? container : cp.containerInstance(); |
| } else { |
| return cp.containerInstance(); |
| } |
| } |
| |
| Vector fieldValues = this.getDescriptor().buildDirectValuesFromFieldValue(fieldValue); |
| if (fieldValues == null) { |
| 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(fieldValues.size()); |
| } |
| |
| for (Enumeration stream = fieldValues.elements(); stream.hasMoreElements();) { |
| Object element = stream.nextElement(); |
| |
| // PERF: Direct variable access. |
| //Object value = row.get(field); |
| //Object fieldValue = null; |
| XMLUnmarshaller unmarshaller = ((XMLRecord) row).getUnmarshaller(); |
| if (element instanceof String) { |
| if (this.isSwaRef() && (unmarshaller.getAttachmentUnmarshaller() != null)) { |
| fieldValue = unmarshaller.getAttachmentUnmarshaller().getAttachmentAsDataHandler((String) element); |
| } else if (!this.isSwaRef()) { |
| //should be base64 |
| byte[] bytes = ((XMLConversionManager) executionSession.getDatasourcePlatform().getConversionManager()).convertSchemaBase64ToByteArray(element); |
| fieldValue = bytes; |
| } |
| } else { |
| //this was an element, so do the XOP/SWAREF/Inline binary cases for an element |
| XMLRecord record = (XMLRecord) element; |
| if (getNullPolicy().valueIsNull((Element) record.getDOM())) { |
| fieldValue = null; |
| } |
| else{ |
| record.setSession(executionSession); |
| |
| if ((unmarshaller.getAttachmentUnmarshaller() != null) && unmarshaller.getAttachmentUnmarshaller().isXOPPackage() && !this.isSwaRef() && !this.shouldInlineBinaryData()) { |
| //look for the include element: |
| String xpath = XMLConstants.EMPTY_STRING; |
| |
| // need a prefix for XOP |
| String prefix = null; |
| NamespaceResolver descriptorResolver = ((XMLDescriptor) getDescriptor()).getNamespaceResolver(); |
| if (descriptorResolver != null) { |
| prefix = descriptorResolver.resolveNamespaceURI(XMLConstants.XOP_URL); |
| } |
| if (prefix == null) { |
| prefix = XMLConstants.XOP_PREFIX; |
| } |
| NamespaceResolver tempResolver = new NamespaceResolver(); |
| tempResolver.put(prefix, XMLConstants.XOP_URL); |
| xpath = prefix + XMLConstants.COLON + INCLUDE + "/@href"; |
| XMLField field = new XMLField(xpath); |
| field.setNamespaceResolver(tempResolver); |
| String includeValue = (String) record.get(field); |
| if (element != null && includeValue != null) { |
| if ((getAttributeElementClass() == ClassConstants.ABYTE) || (getAttributeElementClass() == ClassConstants.APBYTE)) { |
| fieldValue = unmarshaller.getAttachmentUnmarshaller().getAttachmentAsByteArray(includeValue); |
| } else { |
| fieldValue = unmarshaller.getAttachmentUnmarshaller().getAttachmentAsDataHandler(includeValue); |
| } |
| } else { |
| //If we didn't find the Include element, check for inline |
| fieldValue = record.get(XMLConstants.TEXT); |
| //should be a base64 string |
| fieldValue = ((XMLConversionManager) executionSession.getDatasourcePlatform().getConversionManager()).convertSchemaBase64ToByteArray(fieldValue); |
| } |
| } else if ((unmarshaller.getAttachmentUnmarshaller() != null) && isSwaRef()) { |
| String refValue = (String) record.get(XMLConstants.TEXT); |
| if (refValue != null) { |
| fieldValue = unmarshaller.getAttachmentUnmarshaller().getAttachmentAsDataHandler(refValue); |
| } |
| } else { |
| fieldValue = record.get(XMLConstants.TEXT); |
| //should be a base64 string |
| fieldValue = ((XMLConversionManager) executionSession.getDatasourcePlatform().getConversionManager()).convertSchemaBase64ToByteArray(fieldValue); |
| } |
| } |
| } |
| Object attributeValue = convertDataValueToObjectValue(fieldValue, executionSession, unmarshaller); |
| cp.addInto(attributeValue, result, query.getSession()); |
| } |
| return result; |
| } |
| |
| public void setCollectionContentType(Class javaClass) { |
| setAttributeElementClass(javaClass); |
| } |
| |
| /* |
| * This is the same as calling getAttributeElementClass() |
| * If not set by the user than byte[].class is the default |
| */ |
| public Class getCollectionContentType() { |
| return getAttributeElementClass(); |
| } |
| |
| /** |
| * PUBLIC: |
| * Set the class each element in the object's |
| * collection should be converted to, before the collection |
| * is inserted into the object. |
| * This is optional - if left null, the elements will be added |
| * to the object's collection unconverted. |
| */ |
| @Override |
| public void setAttributeElementClass(Class attributeElementClass) { |
| super.setAttributeElementClass(attributeElementClass); |
| this.collectionContentType = attributeElementClass; |
| } |
| |
| @Override |
| public Class getAttributeElementClass() { |
| Class elementClass = super.getAttributeElementClass(); |
| if(elementClass == null) { |
| return this.collectionContentType; |
| } |
| return elementClass; |
| } |
| |
| } |