/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.cxf.tools.wadlto.jaxrs; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.net.URI; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.HEAD; import javax.ws.rs.HeaderParam; import javax.ws.rs.MatrixParam; import javax.ws.rs.OPTIONS; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.Suspended; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.PathSegment; import javax.ws.rs.core.Response; import javax.xml.namespace.QName; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.xml.sax.ErrorHandler; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.apache.cxf.Bus; import org.apache.cxf.catalog.OASISCatalogManager; import org.apache.cxf.catalog.OASISCatalogManagerHelper; import org.apache.cxf.common.jaxb.JAXBUtils; import org.apache.cxf.common.jaxb.JAXBUtils.JCodeModel; import org.apache.cxf.common.jaxb.JAXBUtils.S2JJAXBModel; import org.apache.cxf.common.jaxb.JAXBUtils.SchemaCompiler; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.PackageUtils; import org.apache.cxf.common.util.ReflectionInvokationHandler; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.common.util.SystemPropertyAction; import org.apache.cxf.common.xmlschema.SchemaCollection; import org.apache.cxf.helpers.CastUtils; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.helpers.JavaUtils; import org.apache.cxf.jaxrs.ext.multipart.Multipart; import org.apache.cxf.jaxrs.ext.multipart.MultipartBody; import org.apache.cxf.jaxrs.model.wadl.WadlGenerator; import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.jaxrs.utils.ResourceUtils; import org.apache.cxf.service.model.SchemaInfo; import org.apache.cxf.staxutils.StaxUtils; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.constants.Constants; public class SourceGenerator { public static final String CODE_TYPE_GRAMMAR = "grammar"; public static final String CODE_TYPE_PROXY = "proxy"; public static final String CODE_TYPE_WEB = "web"; public static final String LINE_SEP_PROPERTY = "line.separator"; public static final String FILE_SEP_PROPERTY = "file.separator"; private static final Logger LOG = LogUtils.getL7dLogger(SourceGenerator.class); private static final String DEFAULT_PACKAGE_NAME = "application"; private static final String DEFAULT_RESOURCE_NAME = "Resource"; private static final String TAB = " "; private static final List<String> HTTP_OK_STATUSES = Arrays.asList(new String[] {"200", "201", "202", "203", "204"}); private static final Set<Class<?>> OPTIONAL_PARAMS = new HashSet<Class<?>>(Arrays.<Class<?>>asList(QueryParam.class, HeaderParam.class, MatrixParam.class, FormParam.class)); private static final Map<String, Class<?>> HTTP_METHOD_ANNOTATIONS; private static final Map<String, Class<?>> PARAM_ANNOTATIONS; private static final String PLAIN_PARAM_STYLE = "plain"; private static final String BEAN_VALID_SIMPLE_NAME = "Valid"; private static final String BEAN_VALID_FULL_NAME = "javax.validation." + BEAN_VALID_SIMPLE_NAME; private static final Set<String> RESOURCE_LEVEL_PARAMS; private static final Map<String, String> AUTOBOXED_PRIMITIVES_MAP; private static final Map<String, String> XSD_SPECIFIC_TYPE_MAP; static { HTTP_METHOD_ANNOTATIONS = new HashMap<>(); HTTP_METHOD_ANNOTATIONS.put("get", GET.class); HTTP_METHOD_ANNOTATIONS.put("put", PUT.class); HTTP_METHOD_ANNOTATIONS.put("post", POST.class); HTTP_METHOD_ANNOTATIONS.put("delete", DELETE.class); HTTP_METHOD_ANNOTATIONS.put("head", HEAD.class); HTTP_METHOD_ANNOTATIONS.put("options", OPTIONS.class); PARAM_ANNOTATIONS = new HashMap<>(); PARAM_ANNOTATIONS.put("template", PathParam.class); PARAM_ANNOTATIONS.put("header", HeaderParam.class); PARAM_ANNOTATIONS.put("query", QueryParam.class); PARAM_ANNOTATIONS.put("matrix", MatrixParam.class); RESOURCE_LEVEL_PARAMS = new HashSet<>(); RESOURCE_LEVEL_PARAMS.add("template"); RESOURCE_LEVEL_PARAMS.add("matrix"); AUTOBOXED_PRIMITIVES_MAP = new HashMap<>(); AUTOBOXED_PRIMITIVES_MAP.put(byte.class.getSimpleName(), Byte.class.getSimpleName()); AUTOBOXED_PRIMITIVES_MAP.put(short.class.getSimpleName(), Short.class.getSimpleName()); AUTOBOXED_PRIMITIVES_MAP.put(int.class.getSimpleName(), Integer.class.getSimpleName()); AUTOBOXED_PRIMITIVES_MAP.put(long.class.getSimpleName(), Long.class.getSimpleName()); AUTOBOXED_PRIMITIVES_MAP.put(float.class.getSimpleName(), Float.class.getSimpleName()); AUTOBOXED_PRIMITIVES_MAP.put(double.class.getSimpleName(), Double.class.getSimpleName()); AUTOBOXED_PRIMITIVES_MAP.put(boolean.class.getSimpleName(), Boolean.class.getSimpleName()); XSD_SPECIFIC_TYPE_MAP = new HashMap<>(); XSD_SPECIFIC_TYPE_MAP.put("string", "String"); XSD_SPECIFIC_TYPE_MAP.put("integer", "long"); XSD_SPECIFIC_TYPE_MAP.put("float", "float"); XSD_SPECIFIC_TYPE_MAP.put("double", "double"); XSD_SPECIFIC_TYPE_MAP.put("int", "int"); XSD_SPECIFIC_TYPE_MAP.put("long", "long"); XSD_SPECIFIC_TYPE_MAP.put("byte", "byte"); XSD_SPECIFIC_TYPE_MAP.put("boolean", "boolean"); XSD_SPECIFIC_TYPE_MAP.put("unsignedInt", "long"); XSD_SPECIFIC_TYPE_MAP.put("unsignedShort", "int"); XSD_SPECIFIC_TYPE_MAP.put("unsignedByte", "short"); XSD_SPECIFIC_TYPE_MAP.put("unsignedLong", "java.math.BigInteger"); XSD_SPECIFIC_TYPE_MAP.put("decimal", "java.math.BigInteger"); XSD_SPECIFIC_TYPE_MAP.put("positiveInteger", "java.math.BigInteger"); XSD_SPECIFIC_TYPE_MAP.put("QName", "javax.xml.namespace.QName"); XSD_SPECIFIC_TYPE_MAP.put("duration", "javax.xml.datatype.Duration"); XSD_SPECIFIC_TYPE_MAP.put("date", "java.util.Date"); XSD_SPECIFIC_TYPE_MAP.put("dateTime", "java.util.Date"); XSD_SPECIFIC_TYPE_MAP.put("time", "java.util.Date"); XSD_SPECIFIC_TYPE_MAP.put("anyType", "String"); XSD_SPECIFIC_TYPE_MAP.put("anyURI", "java.net.URI"); } private Comparator<String> importsComparator; private boolean generateInterfaces = true; private boolean generateImpl; private String resourcePackageName; private String resourceName; private String wadlPath; private String wadlNamespace = WadlGenerator.WADL_NS; private boolean generateEnums; private boolean skipSchemaGeneration; private boolean inheritResourceParams; private boolean inheritResourceParamsFirst; private boolean useVoidForEmptyResponses = true; private boolean generateResponseIfHeadersSet; private Map<String, String> properties; private List<String> generatedServiceClasses = new ArrayList<>(); private List<String> generatedTypeClasses = new ArrayList<>(); private List<InputSource> bindingFiles = Collections.emptyList(); private List<InputSource> schemaPackageFiles = Collections.emptyList(); private List<String> compilerArgs = new ArrayList<>(); private Set<String> suspendedAsyncMethods = Collections.emptySet(); private Set<String> responseMethods = Collections.emptySet(); private Map<String, String> schemaPackageMap = Collections.emptyMap(); private Map<String, String> javaTypeMap = Collections.emptyMap(); private Map<String, String> schemaTypeMap = Collections.emptyMap(); private Map<String, String> mediaTypesMap = Collections.emptyMap(); private Bus bus; private boolean supportMultipleRepsWithElements; private boolean supportBeanValidation; private boolean validateWadl; private SchemaCollection schemaCollection = new SchemaCollection(); private String encoding; private boolean createJavaDocs; public SourceGenerator() { this(Collections.<String, String>emptyMap()); } public SourceGenerator(Map<String, String> properties) { this.properties = properties; } public void setSupportMultipleXmlReps(boolean support) { supportMultipleRepsWithElements = support; } public void setWadlNamespace(String ns) { this.wadlNamespace = ns; } public void setUseVoidForEmptyResponses(boolean use) { this.useVoidForEmptyResponses = use; } public void setGenerateResponseIfHeadersSet(boolean set) { this.generateResponseIfHeadersSet = true; } public String getWadlNamespace() { return wadlNamespace; } public void setGenerateEnums(boolean generate) { this.generateEnums = generate; } public void setSkipSchemaGeneration(boolean skip) { this.skipSchemaGeneration = skip; } public void setSuspendedAsyncMethods(Set<String> asyncMethods) { this.suspendedAsyncMethods = asyncMethods; } public void setResponseMethods(Set<String> responseMethods) { this.responseMethods = responseMethods; } private String getClassPackageName(String wadlPackageName) { if (resourcePackageName != null) { return resourcePackageName; } else if (wadlPackageName != null && wadlPackageName.length() > 0) { return wadlPackageName; } else { return DEFAULT_PACKAGE_NAME; } } private String getLineSep() { String value = properties.get(LINE_SEP_PROPERTY); return value == null ? SystemPropertyAction.getProperty(LINE_SEP_PROPERTY) : value; } private String getFileSep() { String value = properties.get(FILE_SEP_PROPERTY); return value == null ? SystemPropertyAction.getProperty(FILE_SEP_PROPERTY) : value; } public void generateSource(String wadl, File srcDir, String codeType) { Application app = readWadl(wadl, wadlPath); Set<String> typeClassNames = new HashSet<>(); GrammarInfo gInfo = generateSchemaCodeAndInfo(app, typeClassNames, srcDir); if (!CODE_TYPE_GRAMMAR.equals(codeType)) { generateResourceClasses(app, gInfo, typeClassNames, srcDir); } } private GrammarInfo generateSchemaCodeAndInfo(Application app, Set<String> typeClassNames, File srcDir) { List<SchemaInfo> schemaElements = getSchemaElements(app); if (!skipSchemaGeneration && schemaElements != null && !schemaElements.isEmpty()) { // generate classes from schema JCodeModel codeModel = createCodeModel(schemaElements, typeClassNames); if (codeModel != null) { generateClassesFromSchema(codeModel, srcDir); } } return getGrammarInfo(app, schemaElements); } private void generateResourceClasses(Application app, GrammarInfo gInfo, Set<String> typeClassNames, File src) { Element appElement = app.getAppElement(); List<Element> resourcesEls = getWadlElements(appElement, "resources"); if (resourcesEls.size() != 1) { throw new IllegalStateException("Single WADL resources element is expected"); } List<Element> resourceEls = getWadlElements(resourcesEls.get(0), "resource"); if (resourceEls.size() == 0) { throw new IllegalStateException("WADL has no resource elements"); } for (int i = 0; i < resourceEls.size(); i++) { Element resource = getResourceElement(app, resourceEls.get(i), gInfo, typeClassNames, resourceEls.get(i).getAttribute("type"), src); writeResourceClass(resource, new ContextInfo(app, src, typeClassNames, gInfo, generateInterfaces), true); if (generateInterfaces && generateImpl) { writeResourceClass(resource, new ContextInfo(app, src, typeClassNames, gInfo, false), true); } if (resourceName != null) { break; } } generateMainClass(resourcesEls.get(0), src); } private Element getResourceElement(Application app, Element resElement, GrammarInfo gInfo, Set<String> typeClassNames, String type, File srcDir) { if (type.length() > 0) { if (type.startsWith("#")) { Element resourceType = resolveLocalReference(app.getAppElement(), "resource_type", type); if (resourceType != null) { Element realElement = (Element)resourceType.cloneNode(true); DOMUtils.setAttribute(realElement, "id", resElement.getAttribute("id")); DOMUtils.setAttribute(realElement, "path", resElement.getAttribute("path")); return realElement; } } else { URI wadlRef = URI.create(type); String wadlRefPath = app.getWadlPath() != null ? getBaseWadlPath(app.getWadlPath()) + wadlRef.getPath() : wadlRef.getPath(); Application refApp = new Application(readIncludedDocument(wadlRefPath), wadlRefPath); GrammarInfo gInfoBase = generateSchemaCodeAndInfo(refApp, typeClassNames, srcDir); if (gInfoBase != null) { gInfo.getElementTypeMap().putAll(gInfoBase.getElementTypeMap()); gInfo.getNsMap().putAll(gInfoBase.getNsMap()); } return getResourceElement(refApp, resElement, gInfo, typeClassNames, "#" + wadlRef.getFragment(), srcDir); } } return resElement; } private Element getWadlElement(Element wadlEl) { String href = wadlEl.getAttribute("href"); if (href.length() > 0 && href.startsWith("#")) { return resolveLocalReference(wadlEl.getOwnerDocument().getDocumentElement(), wadlEl.getLocalName(), href); } else { return wadlEl; } } private Element resolveLocalReference(Element appEl, String elementName, String localRef) { String refId = localRef.substring(1); List<Element> resourceTypes = getWadlElements(appEl, elementName); for (Element resourceType : resourceTypes) { if (refId.equals(resourceType.getAttribute("id"))) { return resourceType; } } return null; } private GrammarInfo getGrammarInfo(Application app, List<SchemaInfo> schemaElements) { if (schemaElements == null || schemaElements.isEmpty()) { return new GrammarInfo(); } Map<String, String> nsMap = new HashMap<>(); NamedNodeMap attrMap = app.getAppElement().getAttributes(); for (int i = 0; i < attrMap.getLength(); i++) { Node node = attrMap.item(i); String nodeName = node.getNodeName(); if (nodeName.startsWith("xmlns:")) { String nsValue = node.getNodeValue(); nsMap.put(nodeName.substring(6), nsValue); } } Map<String, String> elementTypeMap = new HashMap<>(); for (SchemaInfo schemaEl : schemaElements) { populateElementTypeMap(app, schemaEl.getElement(), schemaEl.getSystemId(), elementTypeMap); } boolean noTargetNamespace = schemaElements.size() == 1 && schemaElements.get(0).getNamespaceURI().isEmpty(); return new GrammarInfo(nsMap, elementTypeMap, noTargetNamespace); } private void populateElementTypeMap(Application app, Element schemaEl, String systemId, Map<String, String> elementTypeMap) { List<Element> elementEls = DOMUtils.getChildrenWithName(schemaEl, Constants.URI_2001_SCHEMA_XSD, "element"); for (Element el : elementEls) { String type = el.getAttribute("type"); if (type.length() > 0) { elementTypeMap.put(el.getAttribute("name"), type); } } Element includeEl = DOMUtils.getFirstChildWithName(schemaEl, Constants.URI_2001_SCHEMA_XSD, "include"); if (includeEl != null) { int ind = systemId.lastIndexOf("/"); if (ind != -1) { String schemaURI = systemId.substring(0, ind + 1) + includeEl.getAttribute("schemaLocation"); populateElementTypeMap(app, readIncludedDocument(schemaURI), schemaURI, elementTypeMap); } } } public void generateMainClass(Element resourcesEl, File src) { } private void writeResourceClass(Element rElement, ContextInfo info, boolean isRoot) { String resourceId = resourceName != null ? resourceName : rElement.getAttribute("id"); if (resourceId.length() == 0) { String path = rElement.getAttribute("path"); if (path.length() > 0) { path = path.replaceAll("[\\{\\}_]*", ""); String[] split = path.split("/"); StringBuilder builder = new StringBuilder(resourceId); for (int i = 0; i < split.length; i++) { if (split[i].length() > 0) { builder.append(split[i].toUpperCase().charAt(0) + split[i].substring(1)); } } resourceId = builder.toString(); } resourceId += DEFAULT_RESOURCE_NAME; } boolean expandedQName = resourceId.startsWith("{") ? true : false; QName qname = convertToQName(resourceId, expandedQName); String namespaceURI = possiblyConvertNamespaceURI(qname.getNamespaceURI(), expandedQName); if (getSchemaClassName(namespaceURI, info.getGrammarInfo(), qname.getLocalPart(), info.getTypeClassNames()) != null) { return; } final String className = getClassName(qname.getLocalPart(), info.isInterfaceGenerated(), info.getTypeClassNames()); if (info.getResourceClassNames().contains(className)) { return; } info.getResourceClassNames().add(className); final String classPackage = getClassPackageName(namespaceURI); StringBuilder sbImports = new StringBuilder(); StringBuilder sbCode = new StringBuilder(); Set<String> imports = createImports(); sbImports.append(getClassComment()).append(getLineSep()); sbImports.append("package " + classPackage) .append(";").append(getLineSep()).append(getLineSep()); boolean doCreateJavaDocs = isJavaDocNeeded(info); if (doCreateJavaDocs) { writeClassDocs(rElement, sbCode); } if (isRoot && writeAnnotations(info.isInterfaceGenerated())) { String path = rElement.getAttribute("path"); writeAnnotation(sbCode, imports, Path.class, path, true, false); } sbCode.append("public " + getClassType(info.interfaceIsGenerated) + " " + className); writeImplementsInterface(sbCode, qname.getLocalPart(), info.isInterfaceGenerated()); sbCode.append(" {" + getLineSep() + getLineSep()); Map<String, Integer> methodNameMap = new HashMap<>(); writeMethods(rElement, classPackage, imports, sbCode, info, resourceId, isRoot, "", methodNameMap); sbCode.append("}"); writeImports(sbImports, imports, classPackage); createJavaSourceFile(info.getSrcDir(), new QName(classPackage, className), sbCode, sbImports, true); writeSubresourceClasses(rElement, info, isRoot, resourceId); } private void writeSubresourceClasses(Element rElement, ContextInfo info, boolean isRoot, String resourceId) { List<Element> childEls = getWadlElements(rElement, "resource"); for (Element subEl : childEls) { String id = subEl.getAttribute("id"); if (id.length() > 0 && !resourceId.equals(id) && !id.startsWith("{java") && !id.startsWith("java")) { Element subElement = getResourceElement(info.getApp(), subEl, info.getGrammarInfo(), info.getTypeClassNames(), subEl.getAttribute("type"), info.getSrcDir()); writeResourceClass(subElement, info, false); } writeSubresourceClasses(subEl, info, false, id); } } private QName convertToQName(String resourceId, boolean expandedQName) { QName qname = null; if (expandedQName) { qname = JAXRSUtils.convertStringToQName(resourceId); } else { int lastIndex = resourceId.lastIndexOf("."); qname = lastIndex == -1 ? new QName(resourceId) : new QName(resourceId.substring(0, lastIndex), resourceId.substring(lastIndex + 1)); } return qname; } private String getClassType(boolean interfaceIsGenerated) { return interfaceIsGenerated ? "interface" : "class"; } private String getClassName(String clsName, boolean interfaceIsGenerated, Set<String> typeClassNames) { String name = null; if (interfaceIsGenerated) { name = clsName; } else { name = generateInterfaces ? clsName + "Impl" : clsName; } name = firstCharToUpperCase(name); for (String typeName : typeClassNames) { String localName = typeName.contains(".") ? typeName.substring(typeName.lastIndexOf('.') + 1) : typeName; if (name.equalsIgnoreCase(localName)) { name += "Resource"; } } return name; } private String firstCharToUpperCase(String name) { if (name.length() > 0 && Character.isLowerCase(name.charAt(0))) { return StringUtils.capitalize(name); } else { return name; } } private String firstCharToLowerCase(String name) { if (name.length() > 0 && Character.isUpperCase(name.charAt(0))) { return StringUtils.uncapitalize(name); } else { return name; } } private boolean writeAnnotations(boolean interfaceIsGenerated) { if (interfaceIsGenerated) { return true; } else { return !generateInterfaces && generateImpl; } } private void writeImplementsInterface(StringBuilder sb, String clsName, boolean interfaceIsGenerated) { if (generateInterfaces && !interfaceIsGenerated) { sb.append(" implements " + StringUtils.capitalize(clsName)); } } private String getClassComment() { return "/**" + getLineSep() + " * Created by Apache CXF WadlToJava code generator" + getLineSep() + "**/"; } //CHECKSTYLE:OFF private void writeMethods(Element rElement, String classPackage, Set<String> imports, StringBuilder sbCode, ContextInfo info, String resourceId, boolean isRoot, String currentPath, Map<String, Integer> methodNameMap) { //CHECKSTYLE:ON List<Element> methodEls = getWadlElements(rElement, "method"); List<Element> currentInheritedParams = inheritResourceParams ? new LinkedList<Element>(info.getInheritedParams()) : Collections.<Element>emptyList(); for (Element methodEl : methodEls) { writeResourceMethod(methodEl, classPackage, imports, sbCode, info, isRoot, currentPath, methodNameMap); } if (inheritResourceParams && methodEls.isEmpty()) { info.getInheritedParams().addAll(getWadlElements(rElement, "param")); } List<Element> childEls = getWadlElements(rElement, "resource"); for (Element childEl : childEls) { String path = childEl.getAttribute("path"); if (!path.startsWith("/")) { path = "/" + path; } String newPath = currentPath + path.replace("//", "/"); String id = childEl.getAttribute("id"); if (id.length() == 0) { writeMethods(childEl, classPackage, imports, sbCode, info, id, false, newPath, methodNameMap); } else { writeResourceMethod(childEl, classPackage, imports, sbCode, info, false, newPath, methodNameMap); } } info.getInheritedParams().clear(); info.getInheritedParams().addAll(currentInheritedParams); } private void writeAnnotation(StringBuilder sbCode, Set<String> imports, Class<?> cls, String value, boolean nextLine, boolean addTab) { if (value != null && value.length() == 0) { return; } addImport(imports, cls.getName()); sbCode.append("@").append(cls.getSimpleName()); if (value != null) { sbCode.append("(\"" + value + "\")"); } if (nextLine) { sbCode.append(getLineSep()); if (addTab) { sbCode.append(TAB); } } } private void addImport(Set<String> imports, String clsName) { if (imports == null || clsName.startsWith("java.lang") || !clsName.contains(".")) { return; } if (!imports.contains(clsName)) { imports.add(clsName); } } private void writeImports(StringBuilder sbImports, Set<String> imports, String classPackage) { for (String clsName : imports) { int index = clsName.lastIndexOf("."); if (index != -1 && clsName.substring(0, index).equals(classPackage)) { continue; } sbImports.append("import " + clsName).append(";").append(getLineSep()); } } //CHECKSTYLE:OFF private void writeResourceMethod(Element methodEl, String classPackage, Set<String> imports, StringBuilder sbCode, ContextInfo info, boolean isRoot, String currentPath, Map<String, Integer> methodNameMap) { //CHECKSTYLE:ON StringBuilder sbMethodCode = sbCode; StringBuilder sbMethodDocs = null; StringBuilder sbMethodRespDocs = null; boolean doCreateJavaDocs = isJavaDocNeeded(info); if (doCreateJavaDocs) { sbMethodCode = new StringBuilder(); sbMethodDocs = startMethodDocs(methodEl); sbMethodRespDocs = new StringBuilder(); } boolean isResourceElement = "resource".equals(methodEl.getLocalName()); Element resourceEl = isResourceElement ? methodEl : (Element)methodEl.getParentNode(); List<Element> responseEls = getWadlElements(methodEl, "response"); List<Element> requestEls = getWadlElements(methodEl, "request"); Element firstRequestEl = requestEls.size() >= 1 ? requestEls.get(0) : null; List<Element> allRequestReps = getWadlElements(firstRequestEl, "representation"); List<Element> requestRepsWithElements = new LinkedList<Element>(); boolean duplicatesAvailable = getRepsWithElements(allRequestReps, requestRepsWithElements, info.getGrammarInfo()); String methodName = methodEl.getAttribute("name"); final String methodNameLowerCase = methodName.toLowerCase(); String id = getMethodId(methodEl, methodNameLowerCase); final boolean responseRequired = isMethodMatched(responseMethods, methodNameLowerCase, id); final boolean suspendedAsync = responseRequired ? false : isMethodMatched(suspendedAsyncMethods, methodNameLowerCase, id); boolean jaxpSourceRequired = requestRepsWithElements.size() > 1 && !supportMultipleRepsWithElements; int numOfMethods = jaxpSourceRequired ? 1 : requestRepsWithElements.size(); for (int i = 0; i < numOfMethods; i++) { List<Element> requestReps = allRequestReps; Element requestRepWithElement = requestRepsWithElements.get(i); String suffixName = ""; if (supportMultipleRepsWithElements && requestRepWithElement != null && requestRepsWithElements.size() > 1) { String elementRef = requestRepWithElement.getAttribute("element"); int index = elementRef.indexOf(":"); suffixName = elementRef.substring(index + 1).replace("-", ""); if (duplicatesAvailable) { String mediaType = requestRepWithElement.getAttribute("mediaType"); if (!StringUtils.isEmpty(mediaType)) { String subType = MediaType.valueOf(mediaType).getSubtype(); String[] parts = StringUtils.split(subType, "\\+"); if (parts.length == 2) { suffixName += StringUtils.capitalize(parts[1]); } else { suffixName += StringUtils.capitalize(parts[0].replaceAll("[\\.-]", "")); } } } requestReps = Collections.singletonList(requestRepWithElement); } if (writeAnnotations(info.isInterfaceGenerated())) { sbMethodCode.append(TAB); if (methodNameLowerCase.length() > 0) { if (HTTP_METHOD_ANNOTATIONS.containsKey(methodNameLowerCase)) { writeAnnotation(sbMethodCode, imports, HTTP_METHOD_ANNOTATIONS.get(methodNameLowerCase), null, true, true); } else { writeCustomHttpMethod(info, classPackage, methodName, sbMethodCode, imports); } writeFormatAnnotations(requestReps, sbMethodCode, imports, true, null); List<Element> responseReps = getWadlElements(getOKResponse(responseEls), "representation"); writeFormatAnnotations(responseReps, sbMethodCode, imports, false, requestRepWithElement); if (supportBeanValidation && !responseRequired && isRepWithElementAvailable(responseReps, info.getGrammarInfo())) { addImport(imports, BEAN_VALID_FULL_NAME); sbMethodCode.append("@").append(BEAN_VALID_SIMPLE_NAME).append(getLineSep()).append(TAB); } } if (!isRoot && !"/".equals(currentPath)) { writeAnnotation(sbMethodCode, imports, Path.class, currentPath, true, true); } } else { sbMethodCode.append(getLineSep()).append(TAB); } if (!info.isInterfaceGenerated()) { sbMethodCode.append("public "); } boolean responseTypeAvailable = true; if (methodNameLowerCase.length() > 0) { responseTypeAvailable = writeResponseType(responseEls, requestRepWithElement, sbMethodCode, sbMethodRespDocs, imports, info, responseRequired, suspendedAsync); String genMethodName = id + suffixName; if (methodNameLowerCase.equals(genMethodName)) { List<PathSegment> segments = JAXRSUtils.getPathSegments(currentPath, true, true); StringBuilder sb = new StringBuilder(); for (PathSegment ps : segments) { String pathSeg = ps.getPath().replaceAll("\\{", "").replaceAll("\\}", ""); int index = pathSeg.indexOf(":"); if (index > 0) { pathSeg = pathSeg.substring(0, index); } sb.append(pathSeg); } genMethodName += firstCharToUpperCase(sb.toString()); } genMethodName = genMethodName.replace("-", ""); Integer value = methodNameMap.get(genMethodName); if (value == null) { value = 0; } methodNameMap.put(genMethodName, ++value); if (value > 1) { genMethodName = genMethodName + value.toString(); } sbMethodCode.append(genMethodName); } else { writeSubresourceMethod(resourceEl, imports, sbMethodCode, info, id, suffixName); } sbMethodCode.append("("); List<Element> inParamElements = getParameters(resourceEl, info.getInheritedParams(), !isRoot && !isResourceElement && resourceEl.getAttribute("id").length() > 0); Element repElement = getActualRepElement(allRequestReps, requestRepWithElement); writeRequestTypes(firstRequestEl, classPackage, repElement, inParamElements, jaxpSourceRequired, sbMethodCode, sbMethodDocs, imports, info, suspendedAsync); sbMethodCode.append(")"); if (info.isInterfaceGenerated()) { sbMethodCode.append(";"); } else { generateEmptyMethodBody(sbMethodCode, responseTypeAvailable); } sbMethodCode.append(getLineSep()).append(getLineSep()); } finalizeMethodDocs(doCreateJavaDocs, sbCode, sbMethodDocs, sbMethodRespDocs, sbMethodCode); } private String getMethodId(Element methodEl, String methodNameLowerCase) { String id = methodEl.getAttribute("id"); if (id.length() == 0) { id = methodNameLowerCase; } return id; } private void finalizeMethodDocs(boolean doCreateJavaDocs, StringBuilder sbCode, StringBuilder sbJavaDocs, StringBuilder sbRespDocs, StringBuilder sbMethodCode) { if (doCreateJavaDocs) { sbJavaDocs.append(sbRespDocs); if (sbJavaDocs.length() > 0) { openJavaDocs(sbCode, true); sbCode.append(sbJavaDocs); closeJavaDocs(sbCode); } sbCode.append(sbMethodCode); } } private boolean isJavaDocNeeded(ContextInfo info) { return createJavaDocs && (generateInterfaces && !generateImpl || !generateInterfaces && generateImpl || generateImpl && info.isInterfaceGenerated()); } private void openJavaDocs(StringBuilder sbDoc, boolean tab) { if (tab) { sbDoc.append(TAB); } sbDoc.append("/**").append(getLineSep()); if (tab) { sbDoc.append(TAB); } } private void closeJavaDocs(StringBuilder sbDoc) { sbDoc.append(" */").append(getLineSep()); } private void writeClassDocs(Element resourceEl, StringBuilder sbDoc) { String text = getDocText(resourceEl); if (text != null) { openJavaDocs(sbDoc, false); sbDoc.append(" * ").append(text).append(getLineSep()); closeJavaDocs(sbDoc); } } private StringBuilder startMethodDocs(Element methodEl) { StringBuilder sbDoc = new StringBuilder(); String text = getDocText(methodEl); if (text != null) { sbDoc.append(" * ").append(text).append(getLineSep()).append(TAB); } return sbDoc; } private void writeMethodParamDocs(Element paramEl, String name, StringBuilder sbDoc) { String text = getDocText(paramEl); if (text != null) { sbDoc.append(" * @param ").append(name).append(" ").append(text) .append(getLineSep()).append(TAB); } } private void writeMethodResponseDocs(Element responseEl, StringBuilder sbDoc) { String text = getDocText(responseEl); if (text != null) { sbDoc.append(" * @return ").append(text).append(getLineSep()).append(TAB); } } private String getDocText(Element el) { Element doc = DOMUtils.getFirstChildWithName(el, getWadlNamespace(), "doc"); if (doc != null) { return DOMUtils.getContent(doc); } else { return null; } } private void writeCustomHttpMethod(ContextInfo info, String classPackage, String methodName, StringBuilder mainCode, Set<String> mainImports) { mainCode.append("@").append(methodName); mainCode.append(getLineSep()); mainCode.append(TAB); final String className = methodName; if (info.getResourceClassNames().contains(className)) { return; } info.getResourceClassNames().add(className); StringBuilder sbMethodClassImports = new StringBuilder(); sbMethodClassImports.append(getClassComment()).append(getLineSep()); sbMethodClassImports.append("package " + classPackage) .append(";").append(getLineSep()).append(getLineSep()); sbMethodClassImports.append("import java.lang.annotation.ElementType;").append(getLineSep()); sbMethodClassImports.append("import java.lang.annotation.Retention;").append(getLineSep()); sbMethodClassImports.append("import java.lang.annotation.RetentionPolicy;").append(getLineSep()); sbMethodClassImports.append("import java.lang.annotation.Target;").append(getLineSep()); sbMethodClassImports.append("import javax.ws.rs.HttpMethod;").append(getLineSep()); StringBuilder sbMethodClassCode = new StringBuilder(); sbMethodClassCode.append("@Target({ElementType.METHOD })").append(getLineSep()); sbMethodClassCode.append("@Retention(RetentionPolicy.RUNTIME)").append(getLineSep()); sbMethodClassCode.append("@HttpMethod(\"" + methodName + "\")").append(getLineSep()); sbMethodClassCode.append("public @interface " + methodName); sbMethodClassCode.append(" {" + getLineSep() + getLineSep()); sbMethodClassCode.append("}"); createJavaSourceFile(info.getSrcDir(), new QName(classPackage, className), sbMethodClassCode, sbMethodClassImports, true); } private void writeSubresourceMethod(Element resourceEl, Set<String> imports, StringBuilder sbCode, ContextInfo info, String id, String suffixName) { boolean expandedQName = id.startsWith("{"); QName qname = convertToQName(id, expandedQName); String packageName = possiblyConvertNamespaceURI(qname.getNamespaceURI(), expandedQName); String clsFullName = getSchemaClassName(packageName, info.getGrammarInfo(), qname.getLocalPart(), info.getTypeClassNames()); int lastDotIndex = clsFullName == null ? -1 : clsFullName.lastIndexOf("."); String localName = clsFullName == null ? getClassName(qname.getLocalPart(), true, info.getTypeClassNames()) : clsFullName.substring(lastDotIndex + 1); String subResponseNs = clsFullName == null ? getClassPackageName(packageName) : clsFullName.substring(0, lastDotIndex); Object parentNode = resourceEl.getParentNode(); String parentId = parentNode instanceof Element ? ((Element)parentNode).getAttribute("id") : ""; writeSubResponseType(id.equals(parentId), subResponseNs, localName, sbCode, imports); sbCode.append("get" + localName + suffixName); } private static boolean isMethodMatched(Set<String> methodNames, String methodNameLowerCase, String id) { if (methodNames.isEmpty()) { return false; } return methodNames.contains(methodNameLowerCase) || methodNameLowerCase != id && methodNames.contains(id.toLowerCase()) || methodNames.size() == 1 && "*".equals(methodNames.iterator().next()); } private boolean getRepsWithElements(List<Element> repElements, List<Element> requestRepsWithElements, GrammarInfo gInfo) { int duplicatesCount = 0; Set<String> elementRefs = new HashSet<>(); for (Element el : repElements) { String value = el.getAttribute("element"); if (value.length() > 0 && (value.contains(":") || gInfo.isSchemaWithoutTargetNamespace())) { requestRepsWithElements.add(el); if (!elementRefs.add(value)) { duplicatesCount++; } } } if (requestRepsWithElements.isEmpty()) { requestRepsWithElements.add(null); } return duplicatesCount > 0; } private boolean isRepWithElementAvailable(List<Element> repElements, GrammarInfo gInfo) { for (Element el : repElements) { String value = el.getAttribute("element"); if (value.length() > 0 && (value.contains(":") || gInfo.isSchemaWithoutTargetNamespace())) { return true; } } return false; } private List<Element> getParameters(Element resourceEl, List<Element> inheritedParams, boolean isSubresourceMethod) { List<Element> inParamElements = new LinkedList<Element>(); List<Element> allParamElements = getWadlElements(resourceEl, "param"); List<Element> newInheritedParams = inheritResourceParams ? new LinkedList<Element>() : Collections.<Element>emptyList(); for (Element el : allParamElements) { boolean isResourceLevelParam = RESOURCE_LEVEL_PARAMS.contains(el.getAttribute("style")); if (isSubresourceMethod && isResourceLevelParam) { continue; } if (inheritResourceParams && isResourceLevelParam) { newInheritedParams.add(el); } inParamElements.add(el); } int inheritedCount = 0; for (Element inherited : inheritedParams) { boolean duplicate = false; for (Element in : inParamElements) { if (in.getAttribute("name").equals(inherited.getAttribute("name"))) { duplicate = true; break; } } if (!duplicate) { if (inheritResourceParamsFirst && inheritedCount < inParamElements.size()) { inParamElements.add(inheritedCount, inherited); } else { inParamElements.add(inherited); } inheritedCount++; } } if (inheritResourceParamsFirst) { inheritedParams.addAll(newInheritedParams); } else { inheritedParams.addAll(0, newInheritedParams); } return inParamElements; } private String possiblyConvertNamespaceURI(String nsURI, boolean expandedQName) { return expandedQName ? getPackageFromNamespace(nsURI) : nsURI; } private String getPackageFromNamespace(String nsURI) { return schemaPackageMap.containsKey(nsURI) ? schemaPackageMap.get(nsURI) : PackageUtils.getPackageNameByNameSpaceURI(nsURI); } private void generateEmptyMethodBody(StringBuilder sbCode, boolean responseTypeAvailable) { sbCode.append(" {"); sbCode.append(getLineSep()).append(TAB).append(TAB); sbCode.append("//TODO: implement").append(getLineSep()).append(TAB); if (responseTypeAvailable) { sbCode.append(TAB).append("return null;").append(getLineSep()).append(TAB); } sbCode.append("}"); } private boolean addFormParameters(List<Element> inParamElements, Element requestEl, List<Element> repElements) { if (repElements.size() == 1) { String mediaType = repElements.get(0).getAttribute("mediaType"); if (MediaType.APPLICATION_FORM_URLENCODED.equals(mediaType) || mediaType.startsWith("multipart/")) { if (!mediaTypesMap.containsKey(mediaType)) { inParamElements.addAll(getWadlElements(repElements.get(0), "param")); } return true; } } return false; } private Element getActualRepElement(List<Element> repElements, Element xmlElement) { if (xmlElement != null) { return xmlElement; } for (Element el : repElements) { Element param = DOMUtils.getFirstChildWithName(el, getWadlNamespace(), "param"); if (param != null) { return el; } } return repElements.isEmpty() ? null : repElements.get(0); } //CHECKSTYLE:OFF private boolean writeResponseType(List<Element> responseEls, Element requestRepWithElement, StringBuilder sbCode, StringBuilder sbRespDocs, Set<String> imports, ContextInfo info, boolean responseRequired, boolean suspendedAsync) { //CHECKSTYLE:ON Element okResponse = !suspendedAsync ? getOKResponse(responseEls) : null; List<Element> repElements = null; if (okResponse != null) { if (sbRespDocs != null) { writeMethodResponseDocs(okResponse, sbRespDocs); } repElements = getWadlElements(okResponse, "representation"); } else { repElements = CastUtils.cast(Collections.emptyList(), Element.class); } if (!suspendedAsync && !responseRequired && responseEls.size() == 1 && generateResponseIfHeadersSet) { List<Element> outResponseParamElements = getParameters(responseEls.get(0), Collections.<Element>emptyList(), false); if (!outResponseParamElements.isEmpty()) { writeJaxrResponse(sbCode, imports); return true; } } if (repElements.size() == 0) { if (useVoidForEmptyResponses && !responseRequired || suspendedAsync) { sbCode.append("void "); return false; } else { writeJaxrResponse(sbCode, imports); return true; } } String elementType = null; if (!responseRequired) { List<Element> responseRepWithElements = new LinkedList<Element>(); getRepsWithElements(repElements, responseRepWithElements, info.getGrammarInfo()); Element responseRepWithElement = null; if (responseRepWithElements.size() == 1) { responseRepWithElement = responseRepWithElements.get(0); } else if (requestRepWithElement != null && supportMultipleRepsWithElements && responseRepWithElements.size() > 1) { String mediaType = requestRepWithElement.getAttribute("mediaType"); for (Element el : responseRepWithElements) { if (el.getAttribute("mediaType").equals(mediaType)) { responseRepWithElement = el; break; } } if (responseRepWithElement == null) { responseRepWithElement = responseRepWithElements.get(0); } } elementType = getElementRefName( getActualRepElement(repElements, responseRepWithElement), info, imports, true); } if (elementType != null) { sbCode.append(elementType + " "); } else { writeJaxrResponse(sbCode, imports); } return true; } private void writeJaxrResponse(StringBuilder sbCode, Set<String> imports) { addImport(imports, Response.class.getName()); sbCode.append(Response.class.getSimpleName()).append(" "); } private Element getOKResponse(List<Element> responseEls) { for (int i = 0; i < responseEls.size(); i++) { String statusValue = responseEls.get(i).getAttribute("status"); if (statusValue.length() == 0) { return responseEls.get(i); } String[] statuses = statusValue.split("\\s"); for (String status : statuses) { if (HTTP_OK_STATUSES.contains(status)) { return responseEls.get(i); } } } return null; } private void writeSubResponseType(boolean recursive, String ns, String localName, StringBuilder sbCode, Set<String> imports) { if (!recursive && ns.length() > 0) { addImport(imports, ns + "." + localName); } sbCode.append(localName).append(" "); } //CHECKSTYLE:OFF private void writeRequestTypes(Element requestEl, String classPackage, Element repElement, List<Element> inParamEls, boolean jaxpRequired, StringBuilder sbCode, StringBuilder sbMethodDocs, Set<String> imports, ContextInfo info, boolean suspendedAsync) { //CHECKSTYLE:ON boolean form = false; boolean multipart = false; boolean formOrMultipartParamsAvailable = false; String requestMediaType = null; int currentSize = 0; if (requestEl != null) { inParamEls.addAll(getWadlElements(requestEl, "param")); currentSize = inParamEls.size(); List<Element> repElements = getWadlElements(requestEl, "representation"); form = addFormParameters(inParamEls, requestEl, repElements); if (form) { formOrMultipartParamsAvailable = currentSize < inParamEls.size(); requestMediaType = repElements.get(0).getAttribute("mediaType"); multipart = requestMediaType.startsWith("multipart/"); } } boolean writeAnnotations = writeAnnotations(info.isInterfaceGenerated()); for (int i = 0; i < inParamEls.size(); i++) { Element paramEl = inParamEls.get(i); Class<?> paramAnn = getParamAnnotation(paramEl.getAttribute("style")); if (i >= currentSize && paramAnn == QueryParam.class && formOrMultipartParamsAvailable) { paramAnn = !multipart ? FormParam.class : Multipart.class; } String name = paramEl.getAttribute("name"); boolean enumCreated = false; if (generateEnums) { List<Element> options = DOMUtils.findAllElementsByTagNameNS(paramEl, getWadlNamespace(), "option"); if (!options.isEmpty()) { generateEnumClass(getTypicalClassName(name), options, info.getSrcDir(), classPackage); enumCreated = true; } } if (writeAnnotations) { String required = paramEl.getAttribute("required"); if (Multipart.class.equals(paramAnn) && "false".equals(required)) { writeAnnotation(sbCode, imports, paramAnn, null, false, false); sbCode.append("(value = \"").append(name).append("\", required = false").append(')'); } else { writeAnnotation(sbCode, imports, paramAnn, name, false, false); } sbCode.append(" "); String defaultVal = paramEl.getAttribute("default"); if (defaultVal.length() > 0) { writeAnnotation(sbCode, imports, DefaultValue.class, defaultVal, false, false); sbCode.append(" "); } } boolean isRepeating = isRepeatingParam(paramEl); String type = enumCreated ? getTypicalClassName(name) : getPrimitiveType(paramEl, info, imports); if (OPTIONAL_PARAMS.contains(paramAnn) && (isRepeating || !Boolean.valueOf(paramEl.getAttribute("required"))) && AUTOBOXED_PRIMITIVES_MAP.containsKey(type)) { type = AUTOBOXED_PRIMITIVES_MAP.get(type); } type = addListIfRepeating(type, isRepeating, imports); String paramName; if (JavaUtils.isJavaKeyword(name)) { paramName = name.concat("_arg"); } else { paramName = name.replaceAll("[:\\.\\-]", "_"); } String javaParamName = firstCharToLowerCase(paramName); sbCode.append(type).append(" ").append(javaParamName); if (i + 1 < inParamEls.size()) { sbCode.append(", "); if (i + 1 >= 4 && ((i + 1) % 4) == 0) { sbCode.append(getLineSep()).append(TAB).append(TAB).append(TAB).append(TAB); } } if (sbMethodDocs != null) { writeMethodParamDocs(paramEl, javaParamName, sbMethodDocs); } } String elementParamType = null; String elementParamName = null; boolean writeBeanValidation = false; if (!form) { if (!jaxpRequired) { elementParamType = getElementRefName(repElement, info, imports, false); if (elementParamType != null) { if (writeAnnotations && supportBeanValidation && isRepWithElementAvailable(Collections.singletonList(repElement), info.getGrammarInfo())) { writeBeanValidation = true; } int lastIndex = elementParamType.lastIndexOf('.'); if (lastIndex != -1) { elementParamType = elementParamType.substring(lastIndex + 1); } elementParamName = elementParamType.toLowerCase(); } else if (repElement != null) { Element param = DOMUtils.getFirstChildWithName(repElement, getWadlNamespace(), "param"); if (param != null) { elementParamType = getPrimitiveType(param, info, imports); elementParamName = param.getAttribute("name"); } } } else { addImport(imports, Source.class.getName()); elementParamType = Source.class.getSimpleName(); elementParamName = "source"; } } else if (!formOrMultipartParamsAvailable) { if (requestMediaType != null && mediaTypesMap.containsKey(requestMediaType)) { elementParamType = addImportsAndGetSimpleName(imports, mediaTypesMap.get(requestMediaType)); } else { String fullClassName = !multipart ? MultivaluedMap.class.getName() : MultipartBody.class.getName(); elementParamType = addImportsAndGetSimpleName(imports, fullClassName); } elementParamName = !multipart ? "map" : "body"; } if (elementParamType != null) { if (!inParamEls.isEmpty()) { sbCode.append(", "); } if (writeBeanValidation) { addImport(imports, BEAN_VALID_FULL_NAME); sbCode.append("@").append(BEAN_VALID_SIMPLE_NAME).append(" "); } sbCode.append(elementParamType).append(" ").append(elementParamName); } if (sbMethodDocs != null && repElement != null) { writeMethodParamDocs(repElement, elementParamName, sbMethodDocs); } if (suspendedAsync) { if (!inParamEls.isEmpty() || elementParamType != null) { sbCode.append(", "); } if (writeAnnotations) { addImport(imports, Suspended.class.getName()); sbCode.append("@").append(Suspended.class.getSimpleName()).append(" "); } addImport(imports, AsyncResponse.class.getName()); sbCode.append(AsyncResponse.class.getSimpleName()).append(" ").append("async"); } } private boolean isRepeatingParam(Element paramEl) { return Boolean.valueOf(paramEl.getAttribute("repeating")); } private String addListIfRepeating(String type, boolean isRepeating, Set<String> imports) { if (isRepeating) { addImport(imports, List.class.getName()); type = "List<" + type + ">"; } return type; } private Class<?> getParamAnnotation(String paramStyle) { Class<?> paramAnn = PARAM_ANNOTATIONS.get(paramStyle); if (paramAnn == null) { String error = "Unsupported parameter style: " + paramStyle; if (PLAIN_PARAM_STYLE.equals(paramStyle)) { error += ", plain style parameters have to be wrapped by representations"; } throw new ValidationException(error); } return paramAnn; } private void generateEnumClass(String clsName, List<Element> options, File src, String classPackage) { StringBuilder sbImports = new StringBuilder(); StringBuilder sbCode = new StringBuilder(); sbImports.append(getClassComment()).append(getLineSep()); sbImports.append("package " + classPackage) .append(";").append(getLineSep()).append(getLineSep()); sbCode.append("public enum " + clsName); openBlock(sbCode); for (int i = 0; i < options.size(); i++) { String value = options.get(i).getAttribute("value"); sbCode.append(TAB).append(value.toUpperCase().replaceAll("[\\,\\-]", "_")) .append("(\"").append(value).append("\")"); if (i + 1 < options.size()) { sbCode.append(","); } else { sbCode.append(";"); } sbCode.append(getLineSep()); } sbCode.append(TAB).append("private String value;").append(getLineSep()); sbCode.append(TAB).append("private ").append(clsName).append("(String v)"); openBlock(sbCode); tab(sbCode, 2).append("this.value = v;").append(getLineSep()); tabCloseBlock(sbCode, 1); sbCode.append(TAB).append("public static ") .append(clsName).append(" fromString(String value)"); openBlock(sbCode); tab(sbCode, 2); sbCode.append("if (").append("value").append(" != null)"); openBlock(sbCode); tab(sbCode, 3); sbCode.append("for (").append(clsName).append(" v : ") .append(clsName).append(".values())"); openBlock(sbCode); tab(sbCode, 4); sbCode.append("if (value.equalsIgnoreCase(v.value))"); openBlock(sbCode); tab(sbCode, 5); sbCode.append("return v;").append(getLineSep()); tabCloseBlock(sbCode, 4); tabCloseBlock(sbCode, 3); tabCloseBlock(sbCode, 2); tab(sbCode, 2); sbCode.append("throw new IllegalArgumentException();").append(getLineSep()); tabCloseBlock(sbCode, 1); sbCode.append("}"); createJavaSourceFile(src, new QName(classPackage, clsName), sbCode, sbImports, false); } private static StringBuilder tab(StringBuilder sb, int count) { for (int i = 0; i < count; i++) { sb.append(TAB); } return sb; } private StringBuilder tabCloseBlock(StringBuilder sb, int count) { tab(sb, count).append("}").append(getLineSep()); return sb; } private StringBuilder openBlock(StringBuilder sb) { sb.append(" {").append(getLineSep()); return sb; } private String getTypicalClassName(String name) { String theName = name.toUpperCase(); if (theName.length() == 1) { return theName; } else { theName = theName.substring(0, 1) + theName.substring(1).toLowerCase(); return theName.replaceAll("[\\.\\-]", ""); } } private List<Element> getWadlElements(Element parent, String name) { List<Element> elements = parent != null ? DOMUtils.getChildrenWithName(parent, getWadlNamespace(), name) : CastUtils.cast(Collections.emptyList(), Element.class); if (!"resource".equals(name)) { for (int i = 0; i < elements.size(); i++) { Element el = elements.get(i); Element realEl = getWadlElement(el); if (el != realEl) { elements.set(i, realEl); } } } return elements; } private String getPrimitiveType(Element paramEl, ContextInfo info, Set<String> imports) { final String defaultValue = "String"; String type = paramEl.getAttribute("type"); if (type.length() == 0) { return defaultValue; } String[] pair = type.split(":"); if (pair.length == 2) { if (XSD_SPECIFIC_TYPE_MAP.containsKey(pair[1])) { String expandedName = "{" + Constants.URI_2001_SCHEMA_XSD + "}" + pair[1]; if (schemaTypeMap.containsKey(expandedName)) { return addImportsAndGetSimpleName(imports, schemaTypeMap.get(expandedName)); } String xsdType = XSD_SPECIFIC_TYPE_MAP.get(pair[1]); return addImportsAndGetSimpleName(imports, xsdType); } String value = pair[1].replaceAll("[\\-\\_]", ""); return convertRefToClassName(pair[0], value, defaultValue, info, imports); } else { return addImportsAndGetSimpleName(imports, type); } } private String convertRefToClassName(String prefix, String actualValue, String defaultValue, ContextInfo info, Set<String> imports) { GrammarInfo gInfo = info.getGrammarInfo(); if (gInfo != null) { String namespace = gInfo.getNsMap().get(prefix); if (namespace != null || prefix.isEmpty() && gInfo.isSchemaWithoutTargetNamespace()) { String theNs = namespace != null ? namespace : ""; String packageName = getPackageFromNamespace(theNs); String clsName = getSchemaClassName(packageName, gInfo, actualValue, info.getTypeClassNames()); if (clsName == null) { clsName = schemaTypeMap.get("{" + namespace + "}" + actualValue); } if (clsName != null) { return addImportsAndGetSimpleName(imports, clsName); } } } return defaultValue; } private String addImportsAndGetSimpleName(Set<String> imports, String clsName) { String originalName = clsName; int typeIndex = clsName.lastIndexOf(".."); if (typeIndex != -1) { clsName = clsName.substring(0, typeIndex); } addImport(imports, clsName); int index = clsName.lastIndexOf("."); if (index != -1) { clsName = clsName.substring(index + 1); } if (typeIndex != -1) { clsName = clsName + "<" + originalName.substring(typeIndex + 2) + ">"; } return clsName; } private String checkGenericType(String clsName) { if (clsName != null) { int typeIndex = clsName.lastIndexOf(".."); if (typeIndex != -1) { clsName = clsName.substring(0, typeIndex) + "<" + clsName.substring(typeIndex + 2) + ">"; } } return clsName; } private String getElementRefName(Element repElement, ContextInfo info, Set<String> imports, boolean checkPrimitive) { if (repElement == null) { return null; } String elementRef = repElement.getAttribute("element"); if (elementRef.length() > 0) { String[] pair = elementRef.split(":"); if (pair.length == 2 || pair.length == 1 && info.getGrammarInfo().isSchemaWithoutTargetNamespace()) { String ns = pair.length == 1 ? "" : pair[0]; String name = pair.length == 1 ? pair[0] : pair[1]; return convertRefToClassName(ns, name, null, info, imports); } } else { // try mediaTypesMap first String mediaType = repElement.getAttribute("mediaType"); if (mediaTypesMap.containsKey(mediaType)) { return addImportsAndGetSimpleName(imports, mediaTypesMap.get(mediaType)); } if (checkPrimitive) { Element paramEl = DOMUtils.getFirstChildWithName(repElement, getWadlNamespace(), "param"); if (paramEl != null) { String type = getPrimitiveType(paramEl, info, imports); return addListIfRepeating(type, isRepeatingParam(paramEl), imports); } } } return null; } private String getSchemaClassName(String packageName, GrammarInfo gInfo, String localName, Set <String> typeClassNames) { String clsName = matchClassName(typeClassNames, packageName, localName); if (clsName == null && gInfo != null) { String prefixedElementTypeName = gInfo.getElementTypeMap().get(localName); if (prefixedElementTypeName != null) { String[] pair = prefixedElementTypeName.split(":"); String elementTypeName = pair.length == 2 ? pair[1] : pair[0]; clsName = matchClassName(typeClassNames, packageName, elementTypeName); if (clsName == null && elementTypeName.contains("_")) { clsName = matchClassName(typeClassNames, packageName, elementTypeName.replaceAll("_", "")); } if (clsName == null && pair.length == 2) { String namespace = gInfo.getNsMap().get(pair[0]); if (namespace != null) { packageName = getPackageFromNamespace(namespace); clsName = matchClassName(typeClassNames, packageName, elementTypeName); } } } } if (clsName == null && javaTypeMap != null) { clsName = checkGenericType(javaTypeMap.get(packageName + "." + localName)); } return clsName; } private String matchClassName(Set<String> typeClassNames, String packageName, String localName) { if (localName == null) { return null; } String clsName = packageName + "." + localName.toLowerCase(); for (String type : typeClassNames) { if (type.toLowerCase().equals(clsName)) { return type; } } return null; } private void writeFormatAnnotations(List<Element> repElements, StringBuilder sbCode, Set<String> imports, boolean inRep, Element requestRepWithElement) { if (repElements.size() == 0) { return; } Class<?> cls = inRep ? Consumes.class : Produces.class; addImport(imports, cls.getName()); sbCode.append("@").append(cls.getSimpleName()).append("("); if (repElements.size() > 1) { sbCode.append("{"); } boolean first = true; StringBuilder mediaTypes = new StringBuilder(""); for (int i = 0; i < repElements.size(); i++) { String mediaType = repElements.get(i).getAttribute("mediaType"); if (!StringUtils.isEmpty(mediaType) && mediaTypes.indexOf(mediaType) < 0) { if (!inRep && supportMultipleRepsWithElements && repElements.size() > 1 && requestRepWithElement != null && !requestRepWithElement.getAttribute("mediaType").equals(mediaType)) { continue; } if (!first) { mediaTypes.append(", "); } first = false; mediaTypes.append("\"" + mediaType + "\""); } } sbCode.append(mediaTypes.toString()); if (repElements.size() > 1) { sbCode.append(" }"); } sbCode.append(")"); sbCode.append(getLineSep()).append(TAB); } private void createJavaSourceFile(File src, QName qname, StringBuilder sbCode, StringBuilder sbImports, boolean serviceClass) { String content = sbImports.toString() + getLineSep() + sbCode.toString(); String namespace = qname.getNamespaceURI(); if (serviceClass) { generatedServiceClasses.add(namespace + "." + qname.getLocalPart()); } namespace = namespace.replace(".", getFileSep()); File currentDir = new File(src.getAbsolutePath(), namespace); currentDir.mkdirs(); File file = new File(currentDir.getAbsolutePath(), qname.getLocalPart() + ".java"); try { file.createNewFile(); try (Writer writer = new OutputStreamWriter(Files.newOutputStream(file.toPath()), encoding == null ? StandardCharsets.UTF_8.name() : encoding)) { writer.write(content); writer.flush(); } } catch (FileNotFoundException ex) { LOG.warning(file.getAbsolutePath() + " is not found"); } catch (IOException ex) { LOG.warning("Problem writing into " + file.getAbsolutePath()); } } private Application readWadl(String wadl, String docPath) { Element wadlElement = readXmlDocument(new StringReader(wadl)); try { if (validateWadl) { SchemaFactory factory = SchemaFactory.newInstance(Constants.URI_2001_SCHEMA_XSD); URL schemaURL = ResourceUtils.getResourceURL("classpath:/schemas/wadl/wadl.xsd", bus); Reader r = new BufferedReader(new InputStreamReader(schemaURL.openStream(), StandardCharsets.UTF_8)); StreamSource source = new StreamSource(r); source.setSystemId(schemaURL.toString()); Schema s = factory.newSchema(new Source[]{source}); DOMSource wadlDoc = new DOMSource(wadlElement); Validator v = s.newValidator(); WadlValidationErrorHandler errorHandler = new WadlValidationErrorHandler(); v.setErrorHandler(errorHandler); v.validate(wadlDoc); if (errorHandler.isValidationFailed()) { throw new ValidationException("WADL document is not valid."); } } } catch (ValidationException ex) { throw ex; } catch (Exception ex) { throw new ValidationException("WADL document can not be validated", ex); } return new Application(wadlElement, docPath); } private Element readXmlDocument(Reader reader) { try { return StaxUtils.read(new InputSource(reader)).getDocumentElement(); } catch (Exception ex) { throw new IllegalStateException("Unable to read wadl", ex); } } private void generateClassesFromSchema(JCodeModel codeModel, File src) { try { Object writer = JAXBUtils.createFileCodeWriter(src, encoding == null ? StandardCharsets.UTF_8.name() : encoding); codeModel.build(writer); generatedTypeClasses = JAXBUtils.getGeneratedClassNames(codeModel); } catch (Exception e) { throw new IllegalStateException("Unable to write generated Java files for schemas: " + e.getMessage(), e); } } private List<SchemaInfo> getSchemaElements(Application app) { List<Element> grammarEls = getWadlElements(app.getAppElement(), "grammars"); if (grammarEls.size() != 1) { return null; } List<SchemaInfo> schemas = new ArrayList<>(); List<Element> schemasEls = DOMUtils.getChildrenWithName(grammarEls.get(0), Constants.URI_2001_SCHEMA_XSD, "schema"); for (int i = 0; i < schemasEls.size(); i++) { String systemId = app.getWadlPath(); if (schemasEls.size() > 1) { systemId += "#grammar" + (i + 1); } schemas.add(createSchemaInfo(schemasEls.get(i), systemId)); } List<Element> includeEls = getWadlElements(grammarEls.get(0), "include"); for (Element includeEl : includeEls) { String href = includeEl.getAttribute("href"); String schemaURI = resolveLocationWithCatalog(href); if (schemaURI == null) { if (!URI.create(href).isAbsolute() && app.getWadlPath() != null) { String baseWadlPath = getBaseWadlPath(app.getWadlPath()); if (!href.startsWith("/") && !href.contains("..")) { schemaURI = baseWadlPath + href; } else { try { schemaURI = new URL(new URL(baseWadlPath), href).toString(); } catch (Exception ex) { schemaURI = URI.create(baseWadlPath).resolve(href).toString(); } } } else { schemaURI = href; } } schemas.add(createSchemaInfo(readIncludedDocument(schemaURI), schemaURI)); } return schemas; } private static String getBaseWadlPath(String docPath) { int lastSep = docPath.lastIndexOf("/"); return lastSep != -1 ? docPath.substring(0, lastSep + 1) : docPath; } private SchemaInfo createSchemaInfo(Element schemaEl, String systemId) { SchemaInfo info = new SchemaInfo(schemaEl.getAttribute("targetNamespace")); info.setElement(schemaEl); info.setSystemId(systemId); // Lets try to read the schema to deal with the possible // eviction of the DOM element from the memory try { XmlSchema xmlSchema = schemaCollection.read(schemaEl, systemId); info.setSchema(xmlSchema); } catch (Exception ex) { // may be due to unsupported resolvers for protocols like // classpath: or not the valid schema definition, may not be critical // for the purpose of the schema compilation. } return info; } private String resolveLocationWithCatalog(String href) { if (bus != null) { OASISCatalogManager catalogResolver = OASISCatalogManager.getCatalogManager(bus); try { return new OASISCatalogManagerHelper().resolve(catalogResolver, href, null); } catch (Exception e) { throw new RuntimeException("Catalog resolution failed", e); } } else { return null; } } private Element readIncludedDocument(String href) { try { InputStream is = null; if (!href.startsWith("http")) { is = ResourceUtils.getResourceStream(href, bus); } if (is == null) { is = URI.create(href).toURL().openStream(); } return readXmlDocument(new InputStreamReader(is, StandardCharsets.UTF_8)); } catch (Exception ex) { throw new RuntimeException("Resource " + href + " can not be read"); } } private JCodeModel createCodeModel(List<SchemaInfo> schemaElements, Set<String> type) { SchemaCompiler compiler = createCompiler(type); Object elForRun = ReflectionInvokationHandler .createProxyWrapper(new InnerErrorListener(), JAXBUtils.getParamClass(compiler, "setErrorListener")); compiler.setErrorListener(elForRun); compiler.setEntityResolver(OASISCatalogManager.getCatalogManager(bus) .getEntityResolver()); if (!compilerArgs.isEmpty()) { compiler.getOptions().addGrammar(new InputSource("null")); compiler.getOptions().parseArguments(compilerArgs.toArray(new String[compilerArgs.size()])); } addSchemas(schemaElements, compiler); for (InputSource is : bindingFiles) { compiler.getOptions().addBindFile(is); } S2JJAXBModel intermediateModel = compiler.bind(); JCodeModel codeModel = intermediateModel.generateCode(null, elForRun); JAXBUtils.logGeneratedClassNames(LOG, codeModel); return codeModel; } private SchemaCompiler createCompiler(Set<String> typeClassNames) { return JAXBUtils.createSchemaCompilerWithDefaultAllocator(typeClassNames); } private void addSchemas(List<SchemaInfo> schemas, SchemaCompiler compiler) { // handle package customizations first for (int i = 0; i < schemaPackageFiles.size(); i++) { compiler.parseSchema(schemaPackageFiles.get(i)); } for (int i = 0; i < schemas.size(); i++) { SchemaInfo schema = schemas.get(i); String key = schema.getSystemId(); if (key != null) { // TODO: CXF code should have a better solution somewhere, we'll get back to it // when addressing the issue of retrieving WADLs with included schemas if (key.startsWith("classpath:")) { String resource = key.substring(10); URL url = ResourceUtils.getClasspathResourceURL(resource, SourceGenerator.class, bus); if (url != null) { try { key = url.toURI().toString(); } catch (Exception ex) { // won't happen } } } } else { key = Integer.toString(i); } InputSource is = new InputSource((InputStream)null); is.setSystemId(key); is.setPublicId(key); compiler.getOptions().addGrammar(is); compiler.parseSchema(key, schema.getElement()); } } public void setImportsComparator(Comparator<String> importsComparator) { this.importsComparator = importsComparator; } private Set<String> createImports() { return importsComparator == null ? new TreeSet<String>(new DefaultImportsComparator()) : new TreeSet<String>(importsComparator); } public void setGenerateInterfaces(boolean generateInterfaces) { this.generateInterfaces = generateInterfaces; } public void setGenerateImplementation(boolean generate) { this.generateImpl = generate; } public void setPackageName(String name) { this.resourcePackageName = name; } public void setResourceName(String name) { this.resourceName = name; } public void setWadlPath(String name) { this.wadlPath = name; } public void setBindingFiles(List<InputSource> files) { this.bindingFiles = files; } public void setSchemaPackageFiles(List<InputSource> files) { this.schemaPackageFiles = files; } public void setCompilerArgs(List<String> args) { this.compilerArgs = args; } public void setInheritResourceParams(boolean inherit) { this.inheritResourceParams = inherit; } public void setInheritResourceParamsFirst(boolean inherit) { this.inheritResourceParamsFirst = inherit; } public void setSchemaPackageMap(Map<String, String> map) { this.schemaPackageMap = map; } public void setJavaTypeMap(Map<String, String> map) { this.javaTypeMap = map; } public void setSchemaTypeMap(Map<String, String> map) { this.schemaTypeMap = map; } public void setMediaTypeMap(Map<String, String> map) { this.mediaTypesMap = map; } public void setBus(Bus bus) { this.bus = bus; } public List<String> getGeneratedServiceClasses() { return generatedServiceClasses; } public List<String> getGeneratedTypeClasses() { return generatedTypeClasses; } public void setValidateWadl(boolean validateWadl) { this.validateWadl = validateWadl; } public void setEncoding(String encoding) { this.encoding = encoding; } public void setCreateJavaDocs(boolean createJavaDocs) { this.createJavaDocs = createJavaDocs; } public void setSupportBeanValidation(boolean supportBeanValidation) { this.supportBeanValidation = supportBeanValidation; } private static class GrammarInfo { private Map<String, String> nsMap = new HashMap<>(); private Map<String, String> elementTypeMap = new HashMap<>(); private boolean noTargetNamespace; GrammarInfo() { } GrammarInfo(Map<String, String> nsMap, Map<String, String> elementTypeMap, boolean noTargetNamespace) { this.nsMap = nsMap; this.elementTypeMap = elementTypeMap; this.noTargetNamespace = noTargetNamespace; } public Map<String, String> getNsMap() { return nsMap; } public Map<String, String> getElementTypeMap() { return elementTypeMap; } public boolean isSchemaWithoutTargetNamespace() { return noTargetNamespace; } } private static class DefaultImportsComparator implements Comparator<String> { private static final String JAVAX_PREFIX = "javax"; public int compare(String s1, String s2) { boolean javax1 = s1.startsWith(JAVAX_PREFIX); boolean javax2 = s2.startsWith(JAVAX_PREFIX); if (javax1 && !javax2) { return -1; } else if (!javax1 && javax2) { return 1; } else { return s1.compareTo(s2); } } } static class InnerErrorListener { public void error(SAXParseException ex) { throw new RuntimeException("Error compiling schema from WADL : " + ex.getMessage(), ex); } public void fatalError(SAXParseException ex) { throw new RuntimeException("Fatal error compiling schema from WADL : " + ex.getMessage(), ex); } public void info(SAXParseException ex) { // ignore } public void warning(SAXParseException ex) { // ignore } } private class Application { private Element appElement; private String wadlPath; Application(Element appElement, String wadlPath) { this.appElement = appElement; this.wadlPath = wadlPath; } public Element getAppElement() { return appElement; } public String getWadlPath() { return wadlPath; } } private static class ContextInfo { private boolean interfaceIsGenerated; private Set<String> typeClassNames; private GrammarInfo gInfo; private Set<String> resourceClassNames = new HashSet<>(); private Application rootApp; private File srcDir; private List<Element> inheritedParams = new LinkedList<Element>(); ContextInfo(Application rootApp, File srcDir, Set<String> typeClassNames, GrammarInfo gInfo, boolean interfaceIsGenerated) { this.interfaceIsGenerated = interfaceIsGenerated; this.typeClassNames = typeClassNames; this.gInfo = gInfo; this.rootApp = rootApp; this.srcDir = srcDir; } public List<Element> getInheritedParams() { return inheritedParams; } public Application getApp() { return rootApp; } public File getSrcDir() { return srcDir; } public boolean isInterfaceGenerated() { return interfaceIsGenerated; } public Set<String> getTypeClassNames() { return typeClassNames; } public GrammarInfo getGrammarInfo() { return gInfo; } public Set<String> getResourceClassNames() { return resourceClassNames; } } private static class WadlValidationErrorHandler implements ErrorHandler { private int fatalErrors; private int errors; @Override public void warning(SAXParseException exception) throws SAXException { LOG.log(Level.WARNING, exception.toString()); } @Override public void error(SAXParseException exception) throws SAXException { LOG.log(Level.WARNING, exception.toString()); errors++; } @Override public void fatalError(SAXParseException exception) throws SAXException { LOG.log(Level.WARNING, exception.toString()); fatalErrors++; } private boolean isValidationFailed() { return fatalErrors > 0 || errors > 0; } } }