/** * Copyright © 2006-2016 Web Cohesion (info@webcohesion.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.webcohesion.enunciate.modules.jaxb; import com.webcohesion.enunciate.EnunciateContext; import com.webcohesion.enunciate.api.ApiRegistry; import com.webcohesion.enunciate.javac.decorations.type.DecoratedTypeMirror; import com.webcohesion.enunciate.metadata.Ignore; import com.webcohesion.enunciate.module.*; import com.webcohesion.enunciate.modules.jaxb.model.Registry; import org.reflections.adapters.MetadataAdapter; import javax.lang.model.element.*; import javax.lang.model.type.TypeMirror; import javax.xml.bind.annotation.XmlRegistry; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * @author Ryan Heaton */ @SuppressWarnings ( "unchecked" ) public class JaxbModule extends BasicProviderModule implements TypeDetectingModule, MediaTypeDefinitionModule, ApiRegistryProviderModule, ApiFeatureProviderModule { private DataTypeDetectionStrategy defaultDataTypeDetectionStrategy; private EnunciateJaxbContext jaxbContext; static final String NAME = "jaxb"; @Override public String getName() { return NAME; } public EnunciateJaxbContext getJaxbContext() { return jaxbContext; } public DataTypeDetectionStrategy getDataTypeDetectionStrategy() { String dataTypeDetection = this.config.getString("[@datatype-detection]", null); if (dataTypeDetection != null) { try { return DataTypeDetectionStrategy.valueOf(dataTypeDetection); } catch (IllegalArgumentException e) { //fall through... } } return this.defaultDataTypeDetectionStrategy == null ? DataTypeDetectionStrategy.local : this.defaultDataTypeDetectionStrategy; } public boolean isDisableExamples() { return this.config.getBoolean("[@disableExamples]", false); } @Override public void setDefaultDataTypeDetectionStrategy(DataTypeDetectionStrategy strategy) { this.defaultDataTypeDetectionStrategy = strategy; } @Override public ApiRegistry getApiRegistry() { return new JaxbApiRegistry(this.jaxbContext); } @Override public void addDataTypeDefinitions(TypeMirror type, Set<String> declaredMediaTypes, LinkedList<Element> contextStack) { boolean jaxbApplies = false; for (String mediaType : declaredMediaTypes) { if ("*/*".equals(mediaType) || "text/*".equals(mediaType) || "application/*".equals(mediaType) || "text/xml".equals(mediaType) || "application/xml".equals(mediaType) || mediaType.endsWith("+xml")) { jaxbApplies = true; break; } } if (jaxbApplies) { this.jaxbContext.addReferencedTypeDefinitions(type, contextStack); } else { debug("Element %s is NOT to be added as a JAXB data type because %s doesn't seem to include XML.", type, declaredMediaTypes); } } @Override public void call(EnunciateContext context) { this.jaxbContext = new EnunciateJaxbContext(context, isDisableExamples()); DataTypeDetectionStrategy detectionStrategy = getDataTypeDetectionStrategy(); switch (detectionStrategy) { case aggressive: for (Element declaration : context.getApiElements()) { addPotentialJaxbElement(declaration, new LinkedList<Element>()); } break; case local: for (Element declaration : context.getLocalApiElements()) { addPotentialJaxbElement(declaration, new LinkedList<Element>()); } //no break, add explicit includes: default: if (context.hasExplicitIncludes()) { //if we're not aggressive, we only want to add the api elements if they've been explicitly included for (Element declaration : context.getApiElements()) { addPotentialJaxbElement(declaration, new LinkedList<Element>()); } } } this.enunciate.addArtifact(new JaxbContextClassListArtifact(this.jaxbContext)); this.enunciate.addArtifact(new NamespacePropertiesArtifact(this.jaxbContext)); } public void addPotentialJaxbElement(Element declaration, LinkedList<Element> contextStack) { if (declaration instanceof TypeElement) { XmlRegistry registryMetadata = declaration.getAnnotation(XmlRegistry.class); if (registryMetadata != null) { Registry registry = new Registry((TypeElement) declaration, jaxbContext); this.jaxbContext.add(registry); } else if (!this.jaxbContext.isKnownTypeDefinition((TypeElement) declaration) && isExplicitTypeDefinition(declaration)) { this.jaxbContext.add(this.jaxbContext.createTypeDefinition((TypeElement) declaration), contextStack); } } } protected boolean isExplicitTypeDefinition(Element declaration) { if (declaration.getKind() != ElementKind.CLASS && declaration.getKind() != ElementKind.ENUM) { debug("%s isn't a potential JAXB type because it's not a class or an enum.", declaration); return false; } PackageElement pckg = this.context.getProcessingEnvironment().getElementUtils().getPackageOf(declaration); if ((pckg != null) && (pckg.getAnnotation(Ignore.class) != null)) { debug("%s isn't a potential JAXB type because its package is annotated as to be ignored.", declaration); return false; } if (isThrowable(declaration)) { debug("%s isn't a potential JAXB type because it's an instance of java.lang.Throwable.", declaration); return false; } List<? extends AnnotationMirror> annotationMirrors = declaration.getAnnotationMirrors(); boolean explicitXMLTypeOrElement = false; for (AnnotationMirror mirror : annotationMirrors) { Element annotationDeclaration = mirror.getAnnotationType().asElement(); if (annotationDeclaration != null) { String fqn = annotationDeclaration instanceof TypeElement ? ((TypeElement)annotationDeclaration).getQualifiedName().toString() : ""; //exclude all XmlTransient types and all jaxws types. if (XmlTransient.class.getName().equals(fqn) || fqn.startsWith("javax.xml.ws") || fqn.startsWith("javax.ws.rs") || fqn.startsWith("javax.jws")) { debug("%s isn't a potential JAXB type because of annotation %s.", declaration, fqn); return false; } else { explicitXMLTypeOrElement = (XmlType.class.getName().equals(fqn)) || (XmlRootElement.class.getName().equals(fqn)); } } if (explicitXMLTypeOrElement) { break; } } return explicitXMLTypeOrElement; } /** * Whether the specified declaration is throwable. * * @param declaration The declaration to determine whether it is throwable. * @return Whether the specified declaration is throwable. */ protected boolean isThrowable(Element declaration) { return declaration.getKind() == ElementKind.CLASS && ((DecoratedTypeMirror) declaration.asType()).isInstanceOf(Throwable.class); } @Override public boolean typeDetected(Object type, MetadataAdapter metadata) { List<String> classAnnotations = metadata.getClassAnnotationNames(type); if (classAnnotations != null) { for (String classAnnotation : classAnnotations) { if ((XmlType.class.getName().equals(classAnnotation)) || (XmlRootElement.class.getName().equals(classAnnotation))) { return true; } } } return false; } }