blob: b02d422affc204ac189b727507e23820c352db6f [file] [log] [blame]
/*
* 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.internal.jaxb;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.util.HashMap;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.namespace.QName;
import org.eclipse.persistence.core.mappings.converters.CoreConverter;
import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.JAXBException;
import org.eclipse.persistence.internal.oxm.XMLBinaryDataHelper;
import org.eclipse.persistence.internal.oxm.XMLConversionManager;
import org.eclipse.persistence.internal.oxm.mappings.BinaryDataMapping;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor;
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.jaxb.JAXBMarshaller;
import org.eclipse.persistence.jaxb.JAXBUnmarshaller;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.converters.ObjectTypeConverter;
import org.eclipse.persistence.oxm.XMLMarshaller;
import org.eclipse.persistence.oxm.XMLUnmarshaller;
import org.eclipse.persistence.sessions.Session;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Converter that wraps an XmlAdapter.
*
* @see jakarta.xml.bind.annotation.adapters.XmlAdapter
*/
public class XMLJavaTypeConverter extends org.eclipse.persistence.oxm.mappings.converters.XMLConverterAdapter {
protected Class<Object> boundType = Object.class;
protected Class<Object> valueType = Object.class;
protected Class<?> xmlAdapterClass;
protected String xmlAdapterClassName;
protected XmlAdapter xmlAdapter;
protected QName schemaType;
protected DatabaseMapping mapping;
protected CoreConverter nestedConverter;
/**
* The default constructor. This constructor should be used
* in conjunction with the setXmlAdapterClass method or
* setXmlAdapterClassName method.
*/
public XMLJavaTypeConverter() {
}
/**
* This constructor takes the XmlAdapter class to be used with this
* converter.
*
*/
public XMLJavaTypeConverter(Class xmlAdapterClass) {
setXmlAdapterClass(xmlAdapterClass);
}
/**
* This constructor takes an adapter class name. The adapter
* class will be loaded during initialization.
*
*/
public XMLJavaTypeConverter(String xmlAdapterClassName) {
this.xmlAdapterClassName = xmlAdapterClassName;
}
/**
* This constructor takes the XmlAdapter class to be used with this
* converter, as well as a schema type to be used during the conversion
* operation. During unmarshal, the value type will be converted to
* the schema type, then from that type to the bound type. The opposite
* will occur during marshal.
*
*/
public XMLJavaTypeConverter(Class xmlAdapterClass, QName schemaType) {
setSchemaType(schemaType);
setXmlAdapterClass(xmlAdapterClass);
}
/**
* This constructor takes the XmlAdapter class name to be used with this
* converter (loaded during initialization), as well as a schema type to
* be used during the conversion operation. During unmarshal, the value
* type will be converted to the schema type, then from that type to the
* bound type. The opposite will occur during marshal.
*
*/
public XMLJavaTypeConverter(String xmlAdapterClassName, QName schemaType) {
setSchemaType(schemaType);
setXmlAdapterClassName(xmlAdapterClassName);
}
/**
* Wraps the XmlAdapter unmarshal method.
*/
@Override
public Object convertDataValueToObjectValue(Object dataValue, Session session, XMLUnmarshaller unmarshaller) {
try {
XmlAdapter adapter = this.xmlAdapter;
if (unmarshaller != null) {
HashMap adapters = (HashMap) unmarshaller.getProperty(JAXBUnmarshaller.XML_JAVATYPE_ADAPTERS);
if (adapters != null) {
XmlAdapter runtimeAdapter = (XmlAdapter) adapters.get(this.xmlAdapterClass);
if (runtimeAdapter != null) {
adapter = runtimeAdapter;
}
}
}
Object toConvert = dataValue;
//apply nested converter first
if(nestedConverter != null) {
toConvert = nestedConverter.convertDataValueToObjectValue(toConvert, session);
} else {
if ((dataValue != null) && !(dataValue.getClass() == this.valueType)) {
if (this.mapping instanceof BinaryDataMapping) {
toConvert = XMLBinaryDataHelper.getXMLBinaryDataHelper().convertObject(dataValue, valueType, (AbstractSession) session, this.mapping.getContainerPolicy());
} else {
if (getSchemaType() != null) {
toConvert = ((XMLConversionManager) session.getDatasourcePlatform().getConversionManager()).convertObject(dataValue, valueType, getSchemaType());
} else {
toConvert = session.getDatasourcePlatform().getConversionManager().convertObject(dataValue, valueType);
}
}
}
}
return adapter.unmarshal(toConvert);
} catch (Exception ex) {
if(unmarshaller == null || unmarshaller.getErrorHandler() == null){
throw ConversionException.couldNotBeConverted(dataValue, boundType, ex);
}
try {
unmarshaller.getErrorHandler().warning(new SAXParseException(null, null, ex));
return null;
} catch (SAXException e) {
throw ConversionException.couldNotBeConverted(dataValue, boundType, ex);
}
}
}
/**
* Wraps the XmlAdapter marshal method.
*/
@Override
public Object convertObjectValueToDataValue(Object objectValue, Session session, XMLMarshaller marshaller) {
try {
XmlAdapter adapter = this.xmlAdapter;
if (marshaller != null) {
HashMap adapters = (HashMap) marshaller.getProperty(JAXBMarshaller.XML_JAVATYPE_ADAPTERS);
if (adapters != null) {
XmlAdapter runtimeAdapter = (XmlAdapter) adapters.get(this.xmlAdapterClass);
if (runtimeAdapter != null) {
adapter = runtimeAdapter;
}
}
}
Object dataValue = adapter.marshal(objectValue);
if(nestedConverter != null) {
dataValue = nestedConverter.convertObjectValueToDataValue(dataValue, session);
}
return dataValue;
} catch (Exception ex) {
if(marshaller == null || marshaller.getErrorHandler() == null){
throw ConversionException.couldNotBeConverted(objectValue, valueType, ex);
}
try {
marshaller.getErrorHandler().warning(new SAXParseException(null, null, ex));
return null;
} catch (SAXException e) {
throw ConversionException.couldNotBeConverted(objectValue, valueType, ex);
}
}
}
/**
* Get the schema type to be used during conversion.
*/
public QName getSchemaType() {
return schemaType;
}
/**
* Return the XmlAdapter class for this converter.
*
* @return xmlAdapterClass
*/
public Class getXmlAdapterClass() {
return xmlAdapterClass;
}
/**
* Return the XmlAdapter class name for this converter.
* If null, the name will be set to "".
*
* @return xmlAdapterClassName
*/
public String getXmlAdapterClassName() {
if (xmlAdapterClassName == null) {
xmlAdapterClassName = "";
}
return xmlAdapterClassName;
}
/**
* Figure out the BoundType and ValueType for the XmlAdapter class, then
* either create an instance of the XmlAdapter, or if an instance is set
* on the marshaller, use it.
*
*/
@Override
public void initialize(DatabaseMapping mapping, Session session) {
// if the adapter class is null, try the adapter class name
ClassLoader loader = session.getDatasourceLogin().getDatasourcePlatform().getConversionManager().getLoader();
if (xmlAdapterClass == null) {
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
xmlAdapterClass = AccessController.doPrivileged(new PrivilegedClassForName<>(getXmlAdapterClassName(), true, loader));
} else {
xmlAdapterClass = PrivilegedAccessHelper.getClassForName(getXmlAdapterClassName(), true, loader);
}
} catch (Exception e) {
throw JAXBException.adapterClassNotLoaded(getXmlAdapterClassName(), e);
}
}
// validate adapter class extends jakarta.xml.bind.annotation.adapters.XmlAdapter
if (!XmlAdapter.class.isAssignableFrom(xmlAdapterClass)) {
throw JAXBException.invalidAdapterClass(getXmlAdapterClassName());
}
setBoundTypeAndValueTypeInCaseOfGenericXmlAdapter();
try {
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
xmlAdapter = (XmlAdapter) AccessController.doPrivileged(new PrivilegedNewInstanceFromClass<>(getXmlAdapterClass()));
} else {
xmlAdapter = (XmlAdapter) PrivilegedAccessHelper.newInstanceFromClass(getXmlAdapterClass());
}
} catch (IllegalAccessException e) {
Constructor ctor = null;
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
ctor = AccessController.doPrivileged(new PrivilegedGetConstructorFor<>(xmlAdapterClass, new Class[0], true));
} else {
ctor = PrivilegedAccessHelper.getDeclaredConstructorFor(xmlAdapterClass, new Class[0], true);
}
xmlAdapter = (XmlAdapter) PrivilegedAccessHelper.invokeConstructor(ctor, new Object[0]);
}
} catch (Exception ex) {
throw JAXBException.adapterClassCouldNotBeInstantiated(getXmlAdapterClassName(), ex);
}
if(nestedConverter != null) {
if(nestedConverter instanceof ObjectTypeConverter) {
((ObjectTypeConverter)nestedConverter).convertClassNamesToClasses(loader);
}
nestedConverter.initialize(mapping, session);
}
}
private void setBoundTypeAndValueTypeInCaseOfGenericXmlAdapter() {
Type[] parameterizedTypeArguments = GenericsClassHelper.getParameterizedTypeArguments(xmlAdapterClass, XmlAdapter.class);
if (null != parameterizedTypeArguments) {
Class valueTypeClass = GenericsClassHelper.getClassOfType(parameterizedTypeArguments[0]);
if (null != valueTypeClass) {
valueType = valueTypeClass;
}
if (valueType.isInterface()) {
valueType = Object.class; // during unmarshalling we'll need to instantiate this, so -> no interfaces
}
Class boundTypeClass = GenericsClassHelper.getClassOfType(parameterizedTypeArguments[1]);
if (null != boundTypeClass) {
boundType = boundTypeClass;
}
}
}
/**
* Satisfy the interface.
*/
@Override
public boolean isMutable() {
return false;
}
/**
* Set the schema type to be used during conversion - if one is
* required.
*/
public void setSchemaType(QName qname) {
schemaType = qname;
}
/**
* Set the XmlAdapter class to be used with this converter.
*
*/
public void setXmlAdapterClass(Class xmlAdapterClass) {
this.xmlAdapterClass = xmlAdapterClass;
}
/**
* Set the XmlAdapter class to be used with this converter.
*
*/
public void setXmlAdapterClassName(String xmlAdapterClassName) {
this.xmlAdapterClassName = xmlAdapterClassName;
}
/**
* Get the nested converter to that is used in conjunction with the adapter.
*/
public CoreConverter getNestedConverter() {
return nestedConverter;
}
/**
* Set a nested converter to be used in conjunction with the adapter. On marshal,
* the nested converter is invoked after the adapter. On umarshal it is invoked before.
* Primarily used to support enumerations with adapters.
*/
public void setNestedConverter(CoreConverter nestedConverter) {
this.nestedConverter = nestedConverter;
}
}