All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vmware.vcloud.xjcplugin.RestApiVersionsPlugin Maven / Gradle / Ivy

package com.vmware.vcloud.xjcplugin;

/*-
 * #%L
 * vcd-xjc-plugins :: Custom plugins for XML to Java Compilation
 * %%
 * Copyright (C) 2018 VMware, Inc.
 * %%
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * #L%
 */

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;

import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;

import com.sun.codemodel.JAnnotatable;
import com.sun.codemodel.JAnnotationUse;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JDocComment;
import com.sun.codemodel.JEnumConstant;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.tools.xjc.BadCommandLineException;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.Plugin;
import com.sun.tools.xjc.model.CCustomizations;
import com.sun.tools.xjc.model.CElementInfo;
import com.sun.tools.xjc.model.CPluginCustomization;
import com.sun.tools.xjc.outline.ClassOutline;
import com.sun.tools.xjc.outline.ElementOutline;
import com.sun.tools.xjc.outline.EnumConstantOutline;
import com.sun.tools.xjc.outline.EnumOutline;
import com.sun.tools.xjc.outline.FieldOutline;
import com.sun.tools.xjc.outline.Outline;
import com.vmware.vcloud.api.annotation.ContentType;
import com.vmware.vcloud.api.annotation.Supported;
import com.vmware.vcloud.api.rest.version.ApiVersion;

import org.apache.commons.lang3.StringUtils;
import org.jvnet.jaxb2_commons.util.CustomizationUtils;
import org.jvnet.jaxb2_commons.util.FieldAccessorUtils;

/**
 * RestApiVersionsPlugin is a plug-in for the JAXB's XJC code
 * generator. It adds {@link Supported} annotations to the generated JAXB
 * classes, fields, elements, enums, and enum constants.
 * 

* To activate it, add the following parameters to the XJC command line:
* *

 *   -enable -Xrest-api [default-version]
 * 
* * If default-version is not present "1.5" will be assumed. *

* To specify annotation values for specific element, you need to add the * following to the XSD: * *

 * <xs:appinfo>
 *     <meta:version added-in="1.5" removed-in="2.0"/>
 * </xs:appinfo>
 * 
* * If {@code added-in} is missing, the {@code default-version} will be used.
* If {@code removed-in} is present, the usages will also be marked as deprecated.
* If {@code removed-in} is missing, it won't be included in the generated * annotation either. *

* You also have to add the following boilerplate to the beginning of the XSD: * *

 *   xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
 *   xmlns:meta="http://www.vmware.com/vcloud/meta"
 *   jaxb:verion="2.0"
 *   jaxb:extensionBindingPrefixes="meta"
 * 
*

* The plug-in also supports the content-type annotation: *

 * <xs:appinfo>
 *     <meta:content-type>application/vnd.vmware.vcloud.vApp</meta:content-type>
 * </xs:appinfo>
 * 
* which adds the {@link ContentType} annotation to the generated java class. *

* Lastly, the plugin will try to include the name of the source {@code .xsd} file * and the approximate line and column numbers in the class javadoc */ public class RestApiVersionsPlugin extends Plugin { private static final String X_ANNOTATION_ADDED_IN = "added-in"; private static final String X_ANNOTATION_REMOVED_IN = "removed-in"; private static final String J_ANNOTATION_ADDED_IN = "addedIn"; private static final String J_ANNOTATION_REMOVED_IN = "removedIn"; private static final String NAMESPACE_URI = "http://www.vmware.com/vcloud/meta"; private static final String ELEMENT_VERSION = "version"; private static final String ELEMENT_CONTENT_TYPE = "content-type"; private static final String PLUGIN_OPTION = "Xrest-api"; private static final String CONTENT_TYPE_CONST = "CONTENT_TYPE"; private String version = "1.5"; @Override public String getOptionName() { return PLUGIN_OPTION; } @Override public int parseArgument(Options opt, String[] args, int i) throws BadCommandLineException, IOException { if (!args[i].equals("-Xrest-api")) { return super.parseArgument(opt, args, i); } final String rxVersion = "\\w+(\\.\\d+)*"; final int j = i + 1; if (j < args.length && args[j].matches(rxVersion)) { version = args[j]; return 2; } return 1; } @Override public List getCustomizationURIs() { ArrayList res = new ArrayList(1); res.add(NAMESPACE_URI); return res; } @Override public boolean isCustomizationTagName(String nsUri, String localName) { return NAMESPACE_URI.equals(nsUri) && (ELEMENT_VERSION.equals(localName) || ELEMENT_CONTENT_TYPE.equals(localName)); } @Override public String getUsage() { return " -" + PLUGIN_OPTION + " [default-version]" + ": enables the plugin and sets the default version to be used as \"added-in\" elements.\n"; } @Override public boolean run(Outline outline, Options options, ErrorHandler errorHandler) { for (CElementInfo elementInfo : outline.getModel().getAllElements()) { ElementOutline elementOutline = outline.getElement(elementInfo); if (elementOutline != null) { processElementOutline(elementOutline, errorHandler); } } for (ClassOutline classOutline : outline.getClasses()) { processClassOutline(classOutline, errorHandler); } for (EnumOutline enumOutline : outline.getEnums()) { processEnumOutline(enumOutline, errorHandler); } return true; } /** * Annotates {@link ElementOutline}. * * @param elementOutline * @param errorHandler */ private void processElementOutline(ElementOutline elementOutline, ErrorHandler errorHandler) { CCustomizations customizations = CustomizationUtils.getCustomizations(elementOutline); addSupportedAnnotation(elementOutline.implClass, customizations); addSourceLocationComment(elementOutline.implClass, customizations); } /** * Annotates {@link ClassOutline}. * * @param classOutline * @param errorHandler */ private void processClassOutline(ClassOutline classOutline, ErrorHandler errorHandler) { CCustomizations customizations = CustomizationUtils.getCustomizations(classOutline); JDefinedClass implClass = classOutline.implClass; addSupportedAnnotation(implClass, customizations); addContentTypeAnnotation(implClass, customizations); addSourceLocationComment(implClass, customizations); for (FieldOutline fieldOutline : classOutline.getDeclaredFields()) { processFieldOutline(fieldOutline, errorHandler); } } /** * Annotates {@link FieldOutline}. * * @param fieldOutline * @param errorHandler */ private void processFieldOutline(FieldOutline fieldOutline, ErrorHandler errorHandler) { CCustomizations customizations = CustomizationUtils.getCustomizations(fieldOutline); addSupportedAnnotation(FieldAccessorUtils.field(fieldOutline), customizations); addSupportedAnnotation(FieldAccessorUtils.getter(fieldOutline), customizations); addSupportedAnnotation(FieldAccessorUtils.setter(fieldOutline), customizations); } /** * Annotates {@link EnumOutline}. * * @param enumOutline * @param errorHandler */ private void processEnumOutline(EnumOutline enumOutline, ErrorHandler errorHandler) { CCustomizations customizations = CustomizationUtils.getCustomizations(enumOutline); addSupportedAnnotation(enumOutline.clazz, customizations); addSourceLocationComment(enumOutline.clazz, customizations); for (EnumConstantOutline enumConstantOutline : enumOutline.constants) { processEnumConstantOutline(enumConstantOutline, errorHandler); } } /** * Annotates {@link EnumConstantOutline}. * * @param enumConstantOutline * @param errorHandler */ private void processEnumConstantOutline(EnumConstantOutline enumConstantOutline, ErrorHandler errorHandler) { CCustomizations customizations = CustomizationUtils.getCustomizations(enumConstantOutline); addSupportedAnnotation(enumConstantOutline.constRef, customizations); } /** * This method adds {@link Supported} annotation to the given element. It reads the "added-in" * and "removed-in" values from the customizations if present. Otherwise, it uses the default * {@link #version} for "added-in" and leaves "removed-in" empty. *

* If "removed-in" is present, @{@link Deprecated} annotation and the {@code @deprecated} doclet * are also added. * * @param annotatable * the element which will be annotated * @param customizations * schema customizations for this element */ private void addSupportedAnnotation(JAnnotatable annotatable, CCustomizations customizations) { if (annotatable == null) { return; } String addedIn = version; String removedIn = null; CPluginCustomization customization = customizations.find(NAMESPACE_URI, ELEMENT_VERSION); if (customization != null) { customization.markAsAcknowledged(); if (customization.element.hasAttribute(X_ANNOTATION_ADDED_IN)) { addedIn = customization.element.getAttribute(X_ANNOTATION_ADDED_IN); } if (customization.element.hasAttribute(X_ANNOTATION_REMOVED_IN)) { removedIn = customization.element.getAttribute(X_ANNOTATION_REMOVED_IN); } } // Let's validate the API version strings first try { ApiVersion.fromValue(addedIn); if (removedIn != null) { ApiVersion.fromValue(removedIn); } } catch (IllegalArgumentException e) { throw new RuntimeException("Double check API version string on " + customization.locator.getSystemId() + ":" + customization.locator.getLineNumber() + "", e); } JAnnotationUse annotation = annotatable.annotate(Supported.class); annotation.param(J_ANNOTATION_ADDED_IN, addedIn); if (removedIn == null) { return; } annotation.param(J_ANNOTATION_REMOVED_IN, removedIn); annotatable.annotate(Deprecated.class); final JDocComment javadoc; final String elementType; if (annotatable instanceof JMethod) { javadoc = ((JMethod) annotatable).javadoc(); } else if (annotatable instanceof JFieldVar) { javadoc = ((JFieldVar) annotatable).javadoc(); } else if (annotatable instanceof JDefinedClass) { javadoc = ((JDefinedClass) annotatable).javadoc(); } else if (annotatable instanceof JEnumConstant) { javadoc = ((JMethod) annotatable).javadoc(); } else { return; } final String deprecatedCommentMessage = "Removed since REST version " + removedIn; javadoc.addDeprecated().append(deprecatedCommentMessage); } /** * This method adds {@link ContentType} annotation and a CONTENT TYPE * constant of type "public static final String" to the JAXB generated Java * class. It reads the "meta:content-type" annotation element in the schema * and adds a corresponding Java annotation if not empty. * * @param implClass the element which will be annotated * @param customizations schema customizations for this element */ private void addContentTypeAnnotation(JDefinedClass implClass, CCustomizations customizations) { if (implClass == null) { return; } CPluginCustomization customization = customizations.find(NAMESPACE_URI, ELEMENT_CONTENT_TYPE); if (customization == null) { return; } customization.markAsAcknowledged(); String contentType = customization.element.getTextContent(); if (contentType != null) { JAnnotationUse annotation = implClass.annotate(ContentType.class); annotation.param("value", contentType.trim()); final JCodeModel codeModel = implClass.owner(); JExpression contentTypeConst = JExpr.lit(contentType); @SuppressWarnings("unused") JFieldVar contentTypeField = implClass.field(JMod.PUBLIC | JMod.STATIC | JMod.FINAL, codeModel.ref(String.class), CONTENT_TYPE_CONST, contentTypeConst); } } /** * This method appends the source file and the approximate line and column numbers to the class * javadoc * * @param implClass * the class whose comment will be appended. * @param customizations * schema customizations for this element @ */ private void addSourceLocationComment(JDefinedClass implClass, CCustomizations customizations) { if (implClass == null) { return; } CPluginCustomization customization = customizations.find(NAMESPACE_URI, ELEMENT_CONTENT_TYPE); if (customization == null) { return; } customization.markAsAcknowledged(); final Locator locator = customization.locator; final String source = StringUtils.defaultString(locator.getSystemId()); final String filename = StringUtils.substringAfterLast(source, "/"); final int lineNumber = locator.getLineNumber(); final int columnNumber = locator.getColumnNumber(); implClass.javadoc() .append(System.lineSeparator()) .append("

") .append(System.lineSeparator()) .append(MessageFormat.format("Schema file: {0}
{1}Approximate line and column {2,number,#}:{3,number,#}", filename, System.lineSeparator(), lineNumber, columnNumber)); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy