blob: 5f7ec6cbcdd3af8f63cb58e4ea6aa6e591fe5aea [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.jaxb.compiler;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.xml.XMLConstants;
import jakarta.xml.bind.SchemaOutputResolver;
import javax.xml.namespace.QName;
import org.eclipse.persistence.core.sessions.CoreProject;
import org.eclipse.persistence.internal.oxm.Constants;
import org.eclipse.persistence.internal.oxm.mappings.Descriptor;
import org.eclipse.persistence.internal.oxm.schema.SchemaModelProject;
import org.eclipse.persistence.internal.oxm.schema.model.Schema;
import org.eclipse.persistence.internal.oxm.schema.model.SchemaCompareByNamespace;
import org.eclipse.persistence.jaxb.TypeMappingInfo;
import org.eclipse.persistence.jaxb.javamodel.Helper;
import org.eclipse.persistence.jaxb.javamodel.JavaClass;
import org.eclipse.persistence.jaxb.javamodel.JavaModelInput;
import org.eclipse.persistence.jaxb.javamodel.reflection.JavaModelInputImpl;
import org.eclipse.persistence.jaxb.xmlmodel.XmlBindings;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLContext;
import org.eclipse.persistence.oxm.XMLMarshaller;
import org.eclipse.persistence.sessions.Project;
/**
* INTERNAL:
* <p><b>Purpose:</b>The purpose of this class is to act as an entry point into the
* TopLink JAXB 2.0 Generation framework
* <p><b>Responsibilities:</b><ul>
* <li>Run initial processing on a list of classes to create TypeInfo meta data</li>
* <li>Provide API to generate Schema Files</li>
* <li>Provide API to generate a TopLink Project</li>
* <li>Act as an integration point with WebServices</li>
* </ul>
* <p> This class acts as an entry point into JAXB 2.0 Generation. A Generator is created with a
* specific set of JAXB 2.0 Annotated classes and then performs actions on those, such as
* generating schema files, or generating TopLink Projects. Additional information is returned
* from the schema generation methods as a means of integration with WebServices.
*
* @author mmacivor
* @since Oracle TopLink 11.1.1.0.0
* @see AnnotationsProcessor
* @see MappingsGenerator
* @see SchemaGenerator
*/
public class Generator {
private AnnotationsProcessor annotationsProcessor;
private SchemaGenerator schemaGenerator;
private MappingsGenerator mappingsGenerator;
private Helper helper;
private Map<Type, TypeMappingInfo> typeToTypeMappingInfo;
/**
* This constructor creates a Helper using the JavaModelInput
* instance's JavaModel. Annotations are processed here as well.
*
*/
public Generator(JavaModelInput jModelInput) {
helper = new Helper(jModelInput.getJavaModel());
configureFacetsGeneration(jModelInput);
annotationsProcessor = new AnnotationsProcessor(helper);
schemaGenerator = new SchemaGenerator(helper);
mappingsGenerator = new MappingsGenerator(helper);
annotationsProcessor.processClassesAndProperties(jModelInput.getJavaClasses(), null);
}
/**
* This constructor will process and apply the given XmlBindings as appropriate. Classes
* declared in the bindings will be amalgamated with any classes in the JavaModelInput.
*
* If xmlBindings is null or empty, AnnotationsProcessor will be used to process
* annotations as per usual.
*
* @param xmlBindings map of XmlBindings keyed on package name
*/
public Generator(JavaModelInput jModelInput, Map<String, XmlBindings> xmlBindings, ClassLoader cLoader, String defaultTargetNamespace, boolean enableXmlAccessorFactory) {
helper = new Helper(jModelInput.getJavaModel());
configureFacetsGeneration(jModelInput);
annotationsProcessor = new AnnotationsProcessor(helper);
annotationsProcessor.setXmlAccessorFactorySupport(enableXmlAccessorFactory);
annotationsProcessor.setDefaultTargetNamespace(defaultTargetNamespace);
schemaGenerator = new SchemaGenerator(helper);
mappingsGenerator = new MappingsGenerator(helper);
if (xmlBindings != null && !xmlBindings.isEmpty()) {
new XMLProcessor(xmlBindings).processXML(annotationsProcessor, jModelInput, null, null);
} else {
annotationsProcessor.processClassesAndProperties(jModelInput.getJavaClasses(), null);
}
}
/**
* This constructor creates a Helper using the JavaModelInput
* instance's JavaModel and a map of javaclasses that were generated from Type objects.
* Annotations are processed here as well.
*
*/
public Generator(JavaModelInput jModelInput, TypeMappingInfo[] typeMappingInfos, JavaClass[] javaClasses, Map<Type, TypeMappingInfo> typeToTypeMappingInfo, String defaultTargetNamespace) {
helper = new Helper(jModelInput.getJavaModel());
configureFacetsGeneration(jModelInput);
annotationsProcessor = new AnnotationsProcessor(helper);
annotationsProcessor.setDefaultTargetNamespace(defaultTargetNamespace);
schemaGenerator = new SchemaGenerator(helper);
mappingsGenerator = new MappingsGenerator(helper);
this.typeToTypeMappingInfo = typeToTypeMappingInfo;
annotationsProcessor.processClassesAndProperties(javaClasses, typeMappingInfos);
}
/**
* This constructor will process and apply the given XmlBindings as appropriate. Classes
* declared in the bindings will be amalgamated with any classes in the JavaModelInput.
*
* If xmlBindings is null or empty, AnnotationsProcessor will be used to process
* annotations as per usual.
*
* @param xmlBindings map of XmlBindings keyed on package name
*/
public Generator(JavaModelInput jModelInput, TypeMappingInfo[] typeMappingInfos, JavaClass[] javaClasses, Map<Type, TypeMappingInfo> typeToTypeMappingInfo, Map<String, XmlBindings> xmlBindings, ClassLoader cLoader, String defaultTargetNamespace, boolean enableXmlAccessorFactory) {
helper = new Helper(jModelInput.getJavaModel());
configureFacetsGeneration(jModelInput);
annotationsProcessor = new AnnotationsProcessor(helper);
annotationsProcessor.setXmlAccessorFactorySupport(enableXmlAccessorFactory);
annotationsProcessor.setDefaultTargetNamespace(defaultTargetNamespace);
schemaGenerator = new SchemaGenerator(helper);
mappingsGenerator = new MappingsGenerator(helper);
this.typeToTypeMappingInfo = typeToTypeMappingInfo;
if (xmlBindings != null && !xmlBindings.isEmpty()) {
new XMLProcessor(xmlBindings).processXML(annotationsProcessor, jModelInput, typeMappingInfos, javaClasses);
} else {
annotationsProcessor.processClassesAndProperties(javaClasses, typeMappingInfos);
}
}
/**
* This event is called when mappings generation is completed,
* and provides a chance to deference anything that is no longer
* needed (to reduce the memory footprint of this object).
*/
public void postInitialize() {
mappingsGenerator = null;
annotationsProcessor.postInitialize();
schemaGenerator = null;
}
/**
*
*/
public boolean hasMarshalCallbacks() {
return getMarshalCallbacks()!=null && !getMarshalCallbacks().isEmpty();
}
public boolean hasUnmarshalCallbacks() {
return getUnmarshalCallbacks()!=null && !getUnmarshalCallbacks().isEmpty();
}
public CoreProject generateProject() throws Exception {
mappingsGenerator.getClassToGeneratedClasses().putAll(annotationsProcessor.getArrayClassesToGeneratedClasses());
CoreProject p = mappingsGenerator.generateProject(annotationsProcessor.getTypeInfoClasses(), annotationsProcessor.getTypeInfos(), annotationsProcessor.getUserDefinedSchemaTypes(), annotationsProcessor.getPackageToPackageInfoMappings(), annotationsProcessor.getGlobalElements(), annotationsProcessor.getLocalElements(), annotationsProcessor.getTypeMappingInfosToGeneratedClasses(), annotationsProcessor.getTypeMappingInfoToAdapterClasses(),annotationsProcessor.isDefaultNamespaceAllowed());
annotationsProcessor.getArrayClassesToGeneratedClasses().putAll(mappingsGenerator.getClassToGeneratedClasses());
return p;
}
public java.util.Collection<Schema> generateSchema() {
schemaGenerator.generateSchema(annotationsProcessor.getTypeInfoClasses(), annotationsProcessor.getTypeInfos(), annotationsProcessor.getUserDefinedSchemaTypes(), annotationsProcessor.getPackageToPackageInfoMappings(), null, annotationsProcessor.getArrayClassesToGeneratedClasses());
return schemaGenerator.getAllSchemas();
}
public Map<String, SchemaTypeInfo> generateSchemaFiles(String schemaPath, Map<QName, Type> additionalGlobalElements) throws FileNotFoundException {
// process any additional global elements
processAdditionalElements(additionalGlobalElements, annotationsProcessor);
schemaGenerator.generateSchema(annotationsProcessor.getTypeInfoClasses(), annotationsProcessor.getTypeInfos(), annotationsProcessor.getUserDefinedSchemaTypes(), annotationsProcessor.getPackageToPackageInfoMappings(), annotationsProcessor.getGlobalElements(), annotationsProcessor.getArrayClassesToGeneratedClasses());
Project proj = new SchemaModelProject();
XMLContext context = new XMLContext(proj);
XMLMarshaller marshaller = context.createMarshaller();
Descriptor schemaDescriptor = (Descriptor)proj.getDescriptor(Schema.class);
java.util.Collection<Schema> schemas = schemaGenerator.getAllSchemas();
for(Schema schema : schemas) {
File file = new File(schemaPath + "/" + schema.getName());
NamespaceResolver schemaNamespaces = schema.getNamespaceResolver();
schemaNamespaces.put(Constants.SCHEMA_PREFIX, XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaDescriptor.setNamespaceResolver(schemaNamespaces);
marshaller.marshal(schema, new FileOutputStream(file));
}
return schemaGenerator.getSchemaTypeInfo();
}
public Map<String, SchemaTypeInfo> generateSchemaFiles(SchemaOutputResolver outputResolver, Map<QName, Type> additionalGlobalElements) {
// process any additional global elements
processAdditionalElements(additionalGlobalElements, annotationsProcessor);
schemaGenerator.generateSchema(annotationsProcessor.getTypeInfoClasses(), annotationsProcessor.getTypeInfos(), annotationsProcessor.getUserDefinedSchemaTypes(), annotationsProcessor.getPackageToPackageInfoMappings(), annotationsProcessor.getGlobalElements(), annotationsProcessor.getArrayClassesToGeneratedClasses(), outputResolver);
Project proj = new SchemaModelProject();
XMLContext context = new XMLContext(proj);
XMLMarshaller marshaller = context.createMarshaller();
Descriptor schemaDescriptor = (Descriptor)proj.getDescriptor(Schema.class);
java.util.Collection<Schema> schemas = schemaGenerator.getAllSchemas();
// make sure that schemas will be passed to the output in specified order
if (schemas instanceof List) {
((List<Schema>)schemas).sort(new SchemaCompareByNamespace());
}
for(Schema schema : schemas) {
try {
NamespaceResolver schemaNamespaces = schema.getNamespaceResolver();
schemaNamespaces.put(Constants.SCHEMA_PREFIX, XMLConstants.W3C_XML_SCHEMA_NS_URI);
schemaDescriptor.setNamespaceResolver(schemaNamespaces);
// make sure we don't call into the provided output resolver more than once
javax.xml.transform.Result target;
if (schema.hasResult()) {
target = schema.getResult();
} else {
target = outputResolver.createOutput(schema.getTargetNamespace(), schema.getName());
}
marshaller.marshal(schema, target);
} catch (IOException ex) {
ex.printStackTrace();
}
}
return schemaGenerator.getSchemaTypeInfo();
}
/**
* Convenience method that processes a given map of QName-Type entries. For each an ElementDeclaration
* is created and added to the given AnnotationsProcessor instance's map of global elements.
*
* It is assumed that the map of QName-Type entries contains Type instances that are either a Class or
* a ParameterizedType.
*
*/
private void processAdditionalElements(Map<QName, Type> additionalGlobalElements, AnnotationsProcessor annotationsProcessor) {
if (additionalGlobalElements != null) {
ElementDeclaration declaration;
for(Entry<QName, Type> entry : additionalGlobalElements.entrySet()) {
QName key = entry.getKey();
Type type = entry.getValue();
TypeMappingInfo tmi = null;
if(this.typeToTypeMappingInfo != null) {
tmi = this.typeToTypeMappingInfo.get(type);
}
if(tmi != null) {
if(annotationsProcessor.getTypeMappingInfosToGeneratedClasses().get(tmi) != null) {
type = annotationsProcessor.getTypeMappingInfosToGeneratedClasses().get(tmi);
}
}
JavaClass jClass = null;
if (type instanceof Class) {
Class<?> tClass = (Class<?>) type;
jClass = helper.getJavaClass(tClass);
}
// if no type is available don't do anything
if (jClass != null) {
declaration = new ElementDeclaration(key, jClass, jClass.getQualifiedName(), false);
annotationsProcessor.getGlobalElements().put(key, declaration);
}
}
}
}
public Map<String, UnmarshalCallback> getUnmarshalCallbacks() {
return annotationsProcessor.getUnmarshalCallbacks();
}
public Map<String, MarshalCallback> getMarshalCallbacks() {
return annotationsProcessor.getMarshalCallbacks();
}
public MappingsGenerator getMappingsGenerator() {
return this.mappingsGenerator;
}
public AnnotationsProcessor getAnnotationsProcessor() {
return annotationsProcessor;
}
public void setTypeToTypeMappingInfo(Map<Type, TypeMappingInfo> typesToTypeMapping) {
this.typeToTypeMappingInfo = typesToTypeMapping;
}
public Map<Type, TypeMappingInfo> getTypeToTypeMappingInfo() {
return this.typeToTypeMappingInfo;
}
private void configureFacetsGeneration(JavaModelInput jModelInput) {
if (jModelInput instanceof JavaModelInputImpl) {
helper.setFacets(((JavaModelInputImpl) jModelInput).isFacets());
} else {
String msg = "MOXy BV: Facets generation could not be configured. EclipseLink''s JavaModelInputImpl was " +
"not detected, instead JavaModelInput is of class: {0}";
AbstractSessionLog.getLog().log(SessionLog.WARNING, SessionLog.MOXY, msg, new Object[] {jModelInput.getClass()}, false);
}
}
}