blob: 03b24be43f1b4fd1ed47dc5b0a56f93c88bc9fda [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.util.HashMap;
import javax.xml.namespace.QName;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
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.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<?> boundType = Object.class;
protected Class<?> valueType = Object.class;
protected Class<? extends XmlAdapter<?,?>> xmlAdapterClass;
protected String xmlAdapterClassName;
protected XmlAdapter<?,?> xmlAdapter;
protected QName schemaType;
protected DatabaseMapping mapping;
protected CoreConverter<DatabaseMapping, Session> 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.
*
*/
@SuppressWarnings("unchecked")
public XMLJavaTypeConverter(Class<?> xmlAdapterClass) {
setXmlAdapterClass((Class<XmlAdapter<?,?>>) 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.
*
*/
@SuppressWarnings("unchecked")
public XMLJavaTypeConverter(Class<?> xmlAdapterClass, QName schemaType) {
setSchemaType(schemaType);
setXmlAdapterClass((Class<XmlAdapter<?,?>>) 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) {
@SuppressWarnings("unchecked")
HashMap<Class<XmlAdapter<?,?>>, XmlAdapter<?, ?>> adapters
= (HashMap<Class<XmlAdapter<?,?>>, XmlAdapter<?, ?>>) unmarshaller.getProperty(JAXBUnmarshaller.XML_JAVATYPE_ADAPTERS);
if (adapters != null) {
XmlAdapter<?, ?> runtimeAdapter = 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);
}
}
}
}
//noinspection unchecked
return ((XmlAdapter<Object, ?>)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) {
@SuppressWarnings("unchecked")
HashMap<Class<XmlAdapter<?,?>>, XmlAdapter<?,?>> adapters
= (HashMap<Class<XmlAdapter<?,?>>, XmlAdapter<?,?>>) marshaller.getProperty(JAXBMarshaller.XML_JAVATYPE_ADAPTERS);
if (adapters != null) {
XmlAdapter<?,?> runtimeAdapter = adapters.get(this.xmlAdapterClass);
if (runtimeAdapter != null) {
adapter = runtimeAdapter;
}
}
}
@SuppressWarnings("unchecked")
Object dataValue = ((XmlAdapter<?, Object>) 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<? extends XmlAdapter<?,?>> 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) {
xmlAdapterClass = PrivilegedAccessHelper.callDoPrivilegedWithException(
() -> PrivilegedAccessHelper.getClassForName(getXmlAdapterClassName(), true, loader),
(ex) -> JAXBException.adapterClassNotLoaded(getXmlAdapterClassName(), ex)
);
}
// validate adapter class extends jakarta.xml.bind.annotation.adapters.XmlAdapter
if (!XmlAdapter.class.isAssignableFrom(xmlAdapterClass)) {
throw JAXBException.invalidAdapterClass(getXmlAdapterClassName());
}
setBoundTypeAndValueTypeInCaseOfGenericXmlAdapter();
try {
try {
xmlAdapter = PrivilegedAccessHelper.callDoPrivilegedWithException(
() -> PrivilegedAccessHelper.newInstanceFromClass(getXmlAdapterClass())
);
} catch (IllegalAccessException e) {
Constructor<? extends XmlAdapter<?,?>> ctor = PrivilegedAccessHelper.callDoPrivilegedWithException(
() -> PrivilegedAccessHelper.getDeclaredConstructorFor(xmlAdapterClass, new Class<?>[0], true)
);
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<? extends XmlAdapter<?,?>> 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<DatabaseMapping, Session> 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<DatabaseMapping, Session> nestedConverter) {
this.nestedConverter = nestedConverter;
}
}