blob: 368c08c6d301f076a9e4b8effb873d095b4747cb [file] [log] [blame]
/*
* Copyright (c) 2011, 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:
// Blaise Doughan - 2.2 - initial implementation
package org.eclipse.persistence.jaxb.dynamic.metadata;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import jakarta.xml.bind.JAXBException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.persistence.dynamic.DynamicClassLoader;
import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory;
import org.eclipse.persistence.jaxb.javamodel.JavaClass;
import org.eclipse.persistence.jaxb.javamodel.JavaModelInput;
import org.eclipse.persistence.jaxb.javamodel.xjc.XJCJavaClassImpl;
import org.eclipse.persistence.jaxb.javamodel.xjc.XJCJavaModelImpl;
import org.eclipse.persistence.jaxb.javamodel.xjc.XJCJavaModelInputImpl;
import org.eclipse.persistence.platform.xml.XMLPlatformException;
import org.eclipse.persistence.platform.xml.XMLPlatformFactory;
import org.eclipse.persistence.platform.xml.XMLTransformer;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXParseException;
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JEnumConstant;
import com.sun.codemodel.JPackage;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.api.ErrorListener;
import com.sun.tools.xjc.api.S2JJAXBModel;
import com.sun.tools.xjc.api.SchemaCompiler;
import com.sun.tools.xjc.api.XJC;
public class SchemaMetadata extends Metadata {
private static final String DEFAULT_SYSTEM_ID = "sysid";
private SchemaCompiler schemaCompiler;
private List<InputSource> externalBindings;
@SuppressWarnings("unchecked")
public SchemaMetadata(DynamicClassLoader dynamicClassLoader, Map<String, Object> properties) throws JAXBException {
super(dynamicClassLoader, properties);
try {
if (properties != null) {
Object propValue = properties.get(DynamicJAXBContextFactory.EXTERNAL_BINDINGS_KEY);
if (propValue != null) {
externalBindings = new ArrayList<>();
if (propValue instanceof List<?>) {
List<Source> xjbSources = (List<Source>) propValue;
for (Source source : xjbSources) {
externalBindings.add(createInputSourceFromSource(source));
}
} else {
Source xjbSource = (Source) propValue;
InputSource xjbInputSource = createInputSourceFromSource(xjbSource);
externalBindings.add(xjbInputSource);
}
}
}
} catch (ClassCastException cce) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.xjbNotSource());
}
}
public SchemaMetadata(DynamicClassLoader dynamicClassLoader, Map<String, Object> properties, Source metadataSource, EntityResolver resolver) throws JAXBException {
this(dynamicClassLoader, properties);
try {
InputSource schemaInputSource = createInputSourceFromSource(metadataSource);
if (schemaInputSource.getSystemId() == null) {
schemaInputSource.setSystemId(DEFAULT_SYSTEM_ID);
}
// Use XJC API to parse the schema and generate its JCodeModel
schemaCompiler = XJC.createSchemaCompiler();
schemaCompiler.setEntityResolver(resolver);
schemaCompiler.setErrorListener(new XJCErrorListener());
if (externalBindings != null) {
for (InputSource xjbSource : externalBindings) {
schemaCompiler.parseSchema(xjbSource);
}
}
schemaCompiler.parseSchema(schemaInputSource);
} catch (XMLPlatformException xpe) {
// This will occur when trying to refreshMetadata from a closed stream (non-XML Node metadata)
if (xpe.getCause() instanceof TransformerException) {
TransformerException te = (TransformerException) xpe.getCause();
if (te.getCause() instanceof IOException) {
throw org.eclipse.persistence.exceptions.JAXBException.cannotRefreshMetadata();
}
}
} catch (Exception e) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.errorCreatingDynamicJAXBContext(e));
}
}
public SchemaMetadata(DynamicClassLoader dynamicClassLoader, Map<String, Object> properties, Node node, EntityResolver resolver) throws JAXBException {
this(dynamicClassLoader, properties);
Element element;
if (node.getNodeType() == Node.DOCUMENT_NODE) {
element = ((Document) node).getDocumentElement();
} else if (node.getNodeType() == Node.ELEMENT_NODE) {
element = (Element) node;
} else {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.cannotInitializeFromNode());
}
// Use XJC API to parse the schema and generate its JCodeModel
schemaCompiler = XJC.createSchemaCompiler();
schemaCompiler.setEntityResolver(resolver);
schemaCompiler.setErrorListener(new XJCErrorListener());
schemaCompiler.parseSchema(DEFAULT_SYSTEM_ID, element);
}
@Override
public JavaModelInput getJavaModelInput() throws JAXBException {
S2JJAXBModel model = schemaCompiler.bind();
if (model == null) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.xjcBindingError());
}
JCodeModel codeModel = model.generateCode(new Plugin[0], null);
// Create EclipseLink JavaModel objects for each of XJC's JDefinedClasses
ArrayList<JDefinedClass> classesToProcess = new ArrayList<>();
Iterator<JPackage> packages = codeModel.packages();
while (packages.hasNext()) {
JPackage pkg = packages.next();
Iterator<JDefinedClass> classes = pkg.classes();
while (classes.hasNext()) {
JDefinedClass cls = classes.next();
classesToProcess.add(cls);
}
}
// Look for Inner Classes and add them
ArrayList<JDefinedClass> innerClasses = new ArrayList<>();
for (int i = 0; i < classesToProcess.size(); i++) {
innerClasses.addAll(getInnerClasses(classesToProcess.get(i)));
}
classesToProcess.addAll(innerClasses);
JavaClass[] jotClasses = createClassModelFromXJC(classesToProcess, codeModel, dynamicClassLoader);
// Use the JavaModel to setup a Generator to generate an EclipseLink project
XJCJavaModelImpl javaModel = new XJCJavaModelImpl(codeModel, dynamicClassLoader);
XJCJavaModelInputImpl javaModelInput = new XJCJavaModelInputImpl(jotClasses, javaModel);
for (JavaClass javaClass : jotClasses) {
((XJCJavaClassImpl) javaClass).setJavaModel(javaModel);
javaModel.getJavaModelClasses().put(javaClass.getQualifiedName(), javaClass);
}
return javaModelInput;
}
private HashSet<JDefinedClass> getInnerClasses(JDefinedClass xjcClass) {
// Check this xjcClass for inner classes. If one is found, search that one too.
HashSet<JDefinedClass> classesToReturn = new HashSet<>();
Iterator<JDefinedClass> it = xjcClass.classes();
while (it.hasNext()) {
JDefinedClass innerClass = it.next();
classesToReturn.add(innerClass);
classesToReturn.addAll(getInnerClasses(innerClass));
}
return classesToReturn;
}
@SuppressWarnings("unchecked")
private JavaClass[] createClassModelFromXJC(ArrayList<JDefinedClass> xjcClasses, JCodeModel jCodeModel, DynamicClassLoader dynamicClassLoader) throws JAXBException {
try {
JavaClass[] elinkClasses = new JavaClass[xjcClasses.size()];
int count = 0;
for (JDefinedClass definedClass : xjcClasses) {
XJCJavaClassImpl xjcClass = new XJCJavaClassImpl(definedClass, jCodeModel, dynamicClassLoader);
elinkClasses[count] = xjcClass;
count++;
// If this is an enum, trigger a dynamic class generation, because we won't
// be creating a descriptor for it
if (definedClass.getClassType().equals(ClassType.ENUM)) {
Map<String, JEnumConstant> enumConstants = definedClass.enumConstants();
Object[] enumValues = enumConstants.keySet().toArray();
dynamicClassLoader.addEnum(definedClass.fullName(), enumValues);
}
}
return elinkClasses;
} catch (Exception e) {
throw new JAXBException(org.eclipse.persistence.exceptions.JAXBException.errorCreatingDynamicJAXBContext(e));
}
}
private static InputSource createInputSourceFromSource(Source aSource) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
StreamResult result = new StreamResult(baos);
XMLTransformer t = XMLPlatformFactory.getInstance().getXMLPlatform().newXMLTransformer();
t.transform(aSource, result);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
InputSource inputSource = new InputSource(bais);
inputSource.setSystemId(aSource.getSystemId());
return inputSource;
}
private class XJCErrorListener implements ErrorListener {
@Override
public void error(SAXParseException arg0) {
throw org.eclipse.persistence.exceptions.JAXBException.errorCreatingDynamicJAXBContext(arg0);
}
@Override
public void fatalError(SAXParseException arg0) {
throw org.eclipse.persistence.exceptions.JAXBException.errorCreatingDynamicJAXBContext(arg0);
}
@Override
public void info(SAXParseException arg0) {
throw org.eclipse.persistence.exceptions.JAXBException.errorCreatingDynamicJAXBContext(arg0);
}
@Override
public void warning(SAXParseException arg0) {
throw org.eclipse.persistence.exceptions.JAXBException.errorCreatingDynamicJAXBContext(arg0);
}
}
}