/** * 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.jaxrs.model.wadl; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import javax.ws.rs.BeanParam; import javax.ws.rs.DefaultValue; import javax.ws.rs.Encoded; import javax.ws.rs.FormParam; import javax.ws.rs.HeaderParam; import javax.ws.rs.MatrixParam; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; import javax.ws.rs.container.AsyncResponse; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import javax.ws.rs.ext.MessageBodyWriter; import javax.xml.bind.JAXBContext; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlType; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.ProcessingInstruction; import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; import org.apache.cxf.common.jaxb.JAXBBeanInfo; import org.apache.cxf.common.jaxb.JAXBContextProxy; import org.apache.cxf.common.jaxb.JAXBUtils; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.common.util.PackageUtils; import org.apache.cxf.common.util.PropertyUtils; import org.apache.cxf.common.util.StringUtils; import org.apache.cxf.common.util.XmlSchemaPrimitiveUtils; import org.apache.cxf.common.xmlschema.SchemaCollection; import org.apache.cxf.endpoint.Endpoint; import org.apache.cxf.helpers.CastUtils; import org.apache.cxf.helpers.DOMUtils; import org.apache.cxf.helpers.IOUtils; import org.apache.cxf.jaxrs.JAXRSServiceImpl; import org.apache.cxf.jaxrs.ext.Oneway; import org.apache.cxf.jaxrs.ext.ResponseStatus; import org.apache.cxf.jaxrs.ext.multipart.Multipart; import org.apache.cxf.jaxrs.ext.xml.XMLName; import org.apache.cxf.jaxrs.ext.xml.XMLSource; import org.apache.cxf.jaxrs.impl.HttpHeadersImpl; import org.apache.cxf.jaxrs.impl.MetadataMap; import org.apache.cxf.jaxrs.model.ClassResourceInfo; import org.apache.cxf.jaxrs.model.OperationResourceInfo; import org.apache.cxf.jaxrs.model.Parameter; import org.apache.cxf.jaxrs.model.ParameterType; import org.apache.cxf.jaxrs.model.ResourceTypes; import org.apache.cxf.jaxrs.model.URITemplate; import org.apache.cxf.jaxrs.model.doc.DocumentationProvider; import org.apache.cxf.jaxrs.model.doc.JavaDocProvider; import org.apache.cxf.jaxrs.provider.ServerProviderFactory; import org.apache.cxf.jaxrs.utils.AnnotationUtils; import org.apache.cxf.jaxrs.utils.ExceptionUtils; import org.apache.cxf.jaxrs.utils.InjectionUtils; import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.jaxrs.utils.ResourceUtils; import org.apache.cxf.jaxrs.utils.schemas.SchemaHandler; import org.apache.cxf.message.Message; import org.apache.cxf.service.model.EndpointInfo; import org.apache.cxf.staxutils.DelegatingXMLStreamWriter; import org.apache.cxf.staxutils.StaxUtils; import org.apache.ws.commons.schema.XmlSchema; import org.apache.ws.commons.schema.constants.Constants; public class WadlGenerator implements ContainerRequestFilter { public static final String WADL_QUERY = "_wadl"; public static final MediaType WADL_TYPE = JAXRSUtils.toMediaType("application/vnd.sun.wadl+xml"); public static final String WADL_NS = "http://wadl.dev.java.net/2009/02"; public static final String DEFAULT_WADL_SCHEMA_LOC = "http://www.w3.org/Submission/wadl/wadl.xsd"; private static final Logger LOG = LogUtils.getL7dLogger(WadlGenerator.class); private static final String CONVERT_WADL_RESOURCES_TO_DOM = "convert.wadl.resources.to.dom"; private static final String XLS_NS = "http://www.w3.org/1999/XSL/Transform"; private static final String JAXB_DEFAULT_NAMESPACE = "##default"; private static final String JAXB_DEFAULT_NAME = "##default"; private static final String CLASSPATH_PREFIX = "classpath:"; private static final String DEFAULT_NS_PREFIX = "prefix"; private static final Map<ParameterType, Class<? extends Annotation>> PARAMETER_TYPE_MAP; static { PARAMETER_TYPE_MAP = new HashMap<>(); PARAMETER_TYPE_MAP.put(ParameterType.FORM, FormParam.class); PARAMETER_TYPE_MAP.put(ParameterType.QUERY, QueryParam.class); PARAMETER_TYPE_MAP.put(ParameterType.HEADER, HeaderParam.class); PARAMETER_TYPE_MAP.put(ParameterType.PATH, PathParam.class); PARAMETER_TYPE_MAP.put(ParameterType.MATRIX, MatrixParam.class); } private String wadlNamespace; private boolean singleResourceMultipleMethods = true; private boolean useSingleSlashResource; private boolean ignoreForwardSlash; private boolean linkAnyMediaTypeToXmlSchema; private boolean useJaxbContextForQnames = true; private boolean supportCollections = true; private boolean supportJaxbXmlType = true; private boolean supportJaxbSubstitutions = true; private boolean ignoreOverloadedMethods; private boolean checkAbsolutePathSlash; private boolean keepRelativeDocLinks; private boolean usePathParamsToCompareOperations = true; private boolean incrementNamespacePrefix = true; private boolean ignoreMessageWriters = true; private boolean ignoreRequests; private boolean convertResourcesToDOM = true; private String wadlSchemaLocation; private List<String> externalSchemasCache; private List<URI> externalSchemaLinks; private Map<String, List<String>> externalQnamesMap; private final ConcurrentHashMap<String, String> docLocationMap = new ConcurrentHashMap<>(); private ElementQNameResolver resolver; private List<String> privateAddresses; private String applicationTitle; private String nsPrefix = DEFAULT_NS_PREFIX; private MediaType defaultWadlResponseMediaType = MediaType.APPLICATION_XML_TYPE; private final MediaType defaultRepMediaType = MediaType.WILDCARD_TYPE; private String stylesheetReference; private boolean applyStylesheetLocally; private Bus bus; private final List<DocumentationProvider> docProviders = new LinkedList<DocumentationProvider>(); private ResourceIdGenerator idGenerator; private Map<String, Object> jaxbContextProperties; public WadlGenerator() { } public WadlGenerator(Bus bus) { this.bus = bus; this.bus.setProperty("wadl.service.description.available", "true"); } @Override public void filter(ContainerRequestContext context) { Message m = JAXRSUtils.getCurrentMessage(); if (m == null) { return; } doFilter(context, m); } protected void doFilter(ContainerRequestContext context, Message m) { if (!"GET".equals(m.get(Message.HTTP_REQUEST_METHOD))) { return; } UriInfo ui = context.getUriInfo(); if (!ui.getQueryParameters().containsKey(WADL_QUERY)) { if (stylesheetReference != null || !docLocationMap.isEmpty()) { String path = ui.getPath(false); if (path.startsWith("/") && path.length() > 0) { path = path.substring(1); } if (stylesheetReference != null && path.endsWith(".xsl") || docLocationMap.containsKey(path)) { context.abortWith(getExistingResource(m, ui, path)); } } return; } if (ignoreRequests) { context.abortWith(Response.status(404).build()); return; } HttpHeaders headers = new HttpHeadersImpl(m); List<MediaType> accepts = headers.getAcceptableMediaTypes(); MediaType type = accepts.contains(WADL_TYPE) ? WADL_TYPE : accepts .contains(MediaType.APPLICATION_JSON_TYPE) ? MediaType.APPLICATION_JSON_TYPE : defaultWadlResponseMediaType; Response response = getExistingWadl(m, ui, type); if (response != null) { context.abortWith(response); return; } boolean isJson = isJson(type); StringBuilder sbMain = generateWADL(getBaseURI(m, ui), getResourcesList(m, ui), isJson, m, ui); m.getExchange().put(JAXRSUtils.IGNORE_MESSAGE_WRITERS, !isJson && ignoreMessageWriters); Response r = Response.ok().type(type).entity(createResponseEntity(m, ui, sbMain.toString(), isJson)).build(); context.abortWith(r); } private boolean isJson(MediaType mt) { return mt == MediaType.APPLICATION_JSON_TYPE; } private String getStylesheetInstructionData(String baseURI) { String theStylesheetReference = stylesheetReference; if (!keepRelativeDocLinks) { theStylesheetReference = UriBuilder.fromUri(baseURI) .path(theStylesheetReference).build().toString(); } return "type=\"text/xsl\" href=\"" + theStylesheetReference + "\""; } public StringBuilder generateWADL(String baseURI, List<ClassResourceInfo> cris, boolean isJson, Message m, UriInfo ui) { StringBuilder sbMain = new StringBuilder(); if (!isJson && stylesheetReference != null && !applyStylesheetLocally) { sbMain.append("<?xml-stylesheet ").append(getStylesheetInstructionData(baseURI)).append("?>"); } sbMain.append("<application"); if (!isJson) { sbMain.append(" xmlns=\"").append(getNamespace()).append("\" xmlns:xs=\"") .append(Constants.URI_2001_SCHEMA_XSD).append("\""); } StringBuilder sbGrammars = new StringBuilder(); sbGrammars.append("<grammars>"); StringBuilder sbResources = new StringBuilder(); sbResources.append("<resources base=\"").append(baseURI).append("\">"); MessageBodyWriter<?> jaxbWriter = (m != null && useJaxbContextForQnames) ? ServerProviderFactory.getInstance(m).getDefaultJaxbWriter() : null; ResourceTypes resourceTypes = ResourceUtils.getAllRequestResponseTypes(cris, useJaxbContextForQnames, jaxbWriter); checkXmlSeeAlso(resourceTypes); Set<Class<?>> allTypes = resourceTypes.getAllTypes().keySet(); JAXBContext jaxbContext = null; if (useJaxbContextForQnames && !allTypes.isEmpty()) { jaxbContext = ResourceUtils.createJaxbContext(new HashSet<>(allTypes), null, jaxbContextProperties); if (jaxbContext == null) { LOG.warning("JAXB Context is null: possibly due to one of input classes being not accepted"); } } SchemaWriter schemaWriter = createSchemaWriter(resourceTypes, jaxbContext, ui); ElementQNameResolver qnameResolver = schemaWriter == null ? null : createElementQNameResolver(jaxbContext); Map<Class<?>, QName> clsMap = new IdentityHashMap<>(); Set<ClassResourceInfo> visitedResources = new LinkedHashSet<>(); for (ClassResourceInfo cri : cris) { startResourceTag(sbResources, cri, cri.getURITemplate().getValue()); Annotation description = AnnotationUtils.getClassAnnotation(cri.getServiceClass(), Description.class); if (description == null) { description = AnnotationUtils.getClassAnnotation(cri.getServiceClass(), Descriptions.class); } if (description != null) { handleDocs(new Annotation[] {description}, sbResources, DocTarget.RESOURCE, true, isJson); } else { handleClassJavaDocs(cri, sbResources); } handleResource(sbResources, allTypes, qnameResolver, clsMap, cri, visitedResources, isJson); sbResources.append("</resource>"); } sbResources.append("</resources>"); handleGrammars(sbMain, sbGrammars, schemaWriter, clsMap); sbGrammars.append("</grammars>"); sbMain.append(">"); handleApplicationDocs(sbMain); sbMain.append(sbGrammars.toString()); sbMain.append(sbResources.toString()); sbMain.append("</application>"); return sbMain; } private Object createResponseEntity(Message m, UriInfo ui, String entity, boolean isJson) { try { if (!isJson) { if (stylesheetReference != null && applyStylesheetLocally) { return transformLocally(m, ui, new StreamSource(new StringReader(entity))); } else { return entity; } } return StaxUtils.read(new StringReader(entity)); } catch (Exception ex) { throw ExceptionUtils.toInternalServerErrorException(ex, null); } } protected String getBaseURI(Message m, UriInfo ui) { EndpointInfo ei = m.getExchange().getEndpoint().getEndpointInfo(); String publishedEndpointUrl = (String)ei.getProperty("publishedEndpointUrl"); if (publishedEndpointUrl == null) { return ui.getBaseUri().toString(); } else { return publishedEndpointUrl; } } protected void handleGrammars(StringBuilder sbApp, StringBuilder sbGrammars, SchemaWriter writer, Map<Class<?>, QName> clsMap) { if (writer == null) { return; } Map<String, String> map = new HashMap<>(); for (QName qname : clsMap.values()) { map.put(qname.getPrefix(), qname.getNamespaceURI()); } for (Map.Entry<String, String> entry : map.entrySet()) { sbApp.append(" xmlns:").append(entry.getKey()).append("=\"").append(entry.getValue()) .append("\""); } if (wadlSchemaLocation != null) { sbApp.append(" xmlns:xsi=\"").append(Constants.URI_2001_SCHEMA_XSI).append("\""); sbApp.append(" xsi:schemaLocation=\"") .append(getNamespace()).append(" ").append(wadlSchemaLocation) .append("\""); } writer.write(sbGrammars); } protected void handleResource(StringBuilder sb, Set<Class<?>> jaxbTypes, ElementQNameResolver qnameResolver, Map<Class<?>, QName> clsMap, ClassResourceInfo cri, Set<ClassResourceInfo> visitedResources, boolean isJson) { visitedResources.add(cri); Map<Parameter, Object> classParams = getClassParameters(cri); List<OperationResourceInfo> sortedOps = sortOperationsByPath(cri.getMethodDispatcher() .getOperationResourceInfos()); boolean resourceTagOpened = false; for (int i = 0; i < sortedOps.size(); i++) { OperationResourceInfo ori = sortedOps.get(i); if (i > 0 && ignoreOverloadedMethods && ori.getMethodToInvoke().getName().equals(sortedOps.get(i - 1).getMethodToInvoke().getName())) { continue; } if (ori.getHttpMethod() == null) { Class<?> cls = getMethod(ori).getReturnType(); ClassResourceInfo subcri = cri.findResource(cls, cls); if (subcri != null && !visitedResources.contains(subcri)) { startResourceTag(sb, subcri, ori.getURITemplate().getValue()); handleDocs(subcri.getServiceClass().getAnnotations(), sb, DocTarget.RESOURCE, true, isJson); handlePathAndMatrixParams(sb, ori, isJson); handleResource(sb, jaxbTypes, qnameResolver, clsMap, subcri, visitedResources, isJson); sb.append("</resource>"); } else { handleDynamicSubresource(sb, jaxbTypes, qnameResolver, clsMap, ori, subcri, isJson); } continue; } OperationResourceInfo nextOp = i + 1 < sortedOps.size() ? sortedOps.get(i + 1) : null; resourceTagOpened = handleOperation(sb, jaxbTypes, qnameResolver, clsMap, ori, classParams, nextOp, resourceTagOpened, isJson, i); } } private Map<Parameter, Object> getClassParameters(ClassResourceInfo cri) { Map<Parameter, Object> classParams = new LinkedHashMap<>(); List<Method> paramMethods = cri.getParameterMethods(); for (Method m : paramMethods) { classParams.put(ResourceUtils.getParameter(0, m.getAnnotations(), m.getParameterTypes()[0]), m); } List<Field> fieldParams = cri.getParameterFields(); for (Field f : fieldParams) { classParams.put(ResourceUtils.getParameter(0, f.getAnnotations(), f.getType()), f); } return classParams; } protected void startResourceTag(StringBuilder sb, ClassResourceInfo cri, String path) { sb.append("<resource path=\"").append(getPath(path)).append("\""); if (idGenerator != null) { String id = idGenerator.getClassResourceId(cri); sb.append(" id=\"").append(id).append("\""); } sb.append(">"); } protected String getPath(String path) { String thePath; if (ignoreForwardSlash && path.startsWith("/") && path.length() > 0) { thePath = path.substring(1); } else { thePath = path; } return xmlEncodeIfNeeded(thePath); } private void checkXmlSeeAlso(ResourceTypes resourceTypes) { if (!this.useJaxbContextForQnames) { return; } List<Class<?>> extraClasses = new LinkedList<>(); for (Class<?> cls : resourceTypes.getAllTypes().keySet()) { if (!isXmlRoot(cls) || Modifier.isAbstract(cls.getModifiers())) { XmlSeeAlso seeAlsoAnn = cls.getAnnotation(XmlSeeAlso.class); if (seeAlsoAnn != null) { List<Class<?>> seeAlsoList = CastUtils.cast(Arrays.asList(seeAlsoAnn.value())); if (this.supportJaxbSubstitutions) { for (Class<?> seeAlsoCls : seeAlsoList) { resourceTypes.getSubstitutions().put(seeAlsoCls, cls); } } extraClasses.addAll(seeAlsoList); } } } for (Class<?> cls : extraClasses) { resourceTypes.getAllTypes().put(cls, cls); } } private String xmlEncodeIfNeeded(String value) { StringBuilder builder = new StringBuilder(value.length()); boolean change = false; for (int x = 0; x < value.length(); x++) { char ch = value.charAt(x); String ap = null; switch (ch) { case '\"': ap = """; break; case '\'': ap = "'"; break; case '<': ap = "<"; break; case '>': ap = ">"; break; case '&': ap = "&"; break; default: ap = null; } if (ap != null) { change = true; builder.append(ap); } else { builder.append(ch); } } return change ? builder.toString() : value; } protected void startMethodTag(StringBuilder sb, OperationResourceInfo ori) { sb.append("<method name=\"").append(ori.getHttpMethod()).append("\""); if (idGenerator != null) { String id = idGenerator.getMethodResourceId(ori); sb.append(" id=\"").append(id).append("\""); } sb.append(">"); } protected void endMethodTag(StringBuilder sb, OperationResourceInfo ori) { sb.append("</method>"); } protected void startMethodRequestTag(StringBuilder sb, OperationResourceInfo ori) { sb.append("<request>"); } protected void startMethodResponseTag(StringBuilder sb, OperationResourceInfo ori) { sb.append("<response"); } protected void endMethodRequestTag(StringBuilder sb, OperationResourceInfo ori) { sb.append("</request>"); } protected void endMethodResponseTag(StringBuilder sb, OperationResourceInfo ori) { sb.append("</response>"); } protected void startResourceTag(StringBuilder sb, OperationResourceInfo ori, String path) { sb.append("<resource path=\"").append(path).append("\">"); } protected void endResourceTag(StringBuilder sb, OperationResourceInfo ori) { sb.append("</resource>"); } // CHECKSTYLE:OFF protected boolean handleOperation(StringBuilder sb, Set<Class<?>> jaxbTypes, ElementQNameResolver qnameResolver, Map<Class<?>, QName> clsMap, OperationResourceInfo ori, Map<Parameter, Object> classParams, OperationResourceInfo nextOp, boolean resourceTagOpened, boolean isJson, int index) { Annotation[] anns = getMethod(ori).getAnnotations(); // CHECKSTYLE:ON boolean samePathOperationFollows = singleResourceMultipleMethods && compareOperations(ori, nextOp); String path = ori.getURITemplate().getValue(); if (!resourceTagOpened && openResource(path)) { resourceTagOpened = true; URITemplate template = ori.getClassResourceInfo().getURITemplate(); if (template != null) { String parentPath = template.getValue(); if (parentPath.endsWith("/") && path.startsWith("/") && path.length() > 1) { path = path.substring(1); } } startResourceTag(sb, ori, getPath(path)); handleDocs(anns, sb, DocTarget.RESOURCE, false, isJson); handlePathAndMatrixClassParams(ori, sb, classParams, isJson); handlePathAndMatrixParams(sb, ori, isJson); } else if (index == 0) { handlePathAndMatrixClassParams(ori, sb, classParams, isJson); handlePathAndMatrixParams(sb, ori, isJson); } startMethodTag(sb, ori); if (!handleDocs(anns, sb, DocTarget.METHOD, true, isJson)) { handleOperJavaDocs(ori, sb); } int numOfParams = getMethod(ori).getParameterTypes().length; if ((numOfParams > 1 || numOfParams == 1 && !ori.isAsync()) || !classParams.isEmpty()) { startMethodRequestTag(sb, ori); handleDocs(anns, sb, DocTarget.REQUEST, false, isJson); boolean isForm = isFormRequest(ori); doHandleClassParams(ori, sb, classParams, isJson, ParameterType.QUERY, ParameterType.HEADER); doHandleJaxrsBeanParamClassParams(ori, sb, classParams, isJson, ParameterType.QUERY, ParameterType.HEADER); for (Parameter p : ori.getParameters()) { if (isForm && p.getType() == ParameterType.REQUEST_BODY) { continue; } handleParameter(sb, jaxbTypes, qnameResolver, clsMap, ori, p, isJson); } if (isForm) { handleFormRepresentation(sb, jaxbTypes, qnameResolver, clsMap, ori, getFormClass(ori), isJson); } endMethodRequestTag(sb, ori); } startMethodResponseTag(sb, ori); Class<?> returnType = getMethod(ori).getReturnType(); boolean isVoid = void.class == returnType && !ori.isAsync(); ResponseStatus responseStatus = getMethod(ori).getAnnotation(ResponseStatus.class); if (responseStatus != null) { setResponseStatus(sb, responseStatus.value()); } else if (isVoid) { boolean oneway = getMethod(ori).getAnnotation(Oneway.class) != null; setResponseStatus(sb, oneway ? Response.Status.ACCEPTED : Response.Status.NO_CONTENT); } sb.append(">"); handleDocs(anns, sb, DocTarget.RESPONSE, false, isJson); if (!isVoid) { handleRepresentation(sb, jaxbTypes, qnameResolver, clsMap, ori, returnType, isJson, false); } endMethodResponseTag(sb, ori); endMethodTag(sb, ori); if (resourceTagOpened && !samePathOperationFollows) { endResourceTag(sb, ori); resourceTagOpened = false; } return resourceTagOpened; } private void setResponseStatus(StringBuilder sb, Response.Status... statuses) { sb.append(" status=\""); for (int i = 0; i < statuses.length; i++) { if (i > 0) { sb.append(" "); } sb.append(statuses[i].getStatusCode()); } sb.append("\""); } protected boolean compareOperations(OperationResourceInfo ori1, OperationResourceInfo ori2) { if (ori1 == null || ori2 == null || !ori1.getURITemplate().getValue().equals(ori2.getURITemplate().getValue()) || ori1.getHttpMethod() != null && ori2.getHttpMethod() == null || ori2.getHttpMethod() != null && ori1.getHttpMethod() == null) { return false; } if (usePathParamsToCompareOperations) { int ori1PathParams = 0; int ori1MatrixParams = 0; for (Parameter p : ori1.getParameters()) { if (p.getType() == ParameterType.PATH) { ori1PathParams++; } else if (p.getType() == ParameterType.MATRIX) { ori1MatrixParams++; } } int ori2PathParams = 0; int ori2MatrixParams = 0; for (Parameter p : ori2.getParameters()) { if (p.getType() == ParameterType.PATH) { ori2PathParams++; } else if (p.getType() == ParameterType.MATRIX) { ori2MatrixParams++; } } return ori1PathParams == ori2PathParams && ori1MatrixParams == ori2MatrixParams; } else { return true; } } private boolean openResource(String path) { if ("/".equals(path)) { return useSingleSlashResource; } return true; } protected void handleDynamicSubresource(StringBuilder sb, Set<Class<?>> jaxbTypes, ElementQNameResolver qnameResolver, Map<Class<?>, QName> clsMap, OperationResourceInfo ori, ClassResourceInfo subcri, boolean isJson) { if (!isJson) { if (subcri != null) { sb.append("<!-- Recursive subresource -->"); } else { sb.append("<!-- Dynamic subresource -->"); } } startResourceTag(sb, subcri, ori.getURITemplate().getValue()); handlePathAndMatrixParams(sb, ori, isJson); sb.append("</resource>"); } protected void handlePathAndMatrixClassParams(OperationResourceInfo ori, StringBuilder sb, Map<Parameter, Object> params, boolean isJson) { doHandleClassParams(ori, sb, params, isJson, ParameterType.PATH); doHandleClassParams(ori, sb, params, isJson, ParameterType.MATRIX); doHandleJaxrsBeanParamClassParams(ori, sb, params, isJson, ParameterType.PATH, ParameterType.MATRIX); } protected void doHandleClassParams(OperationResourceInfo ori, StringBuilder sb, Map<Parameter, Object> params, boolean isJson, ParameterType... pType) { Set<ParameterType> pTypes = new LinkedHashSet<>(Arrays.asList(pType)); for (Map.Entry<Parameter, Object> entry : params.entrySet()) { Parameter pm = entry.getKey(); Object obj = entry.getValue(); if (pTypes.contains(pm.getType())) { Class<?> cls = obj instanceof Method ? ((Method)obj).getParameterTypes()[0] : ((Field)obj) .getType(); Type type = obj instanceof Method ? ((Method)obj).getGenericParameterTypes()[0] : ((Field)obj).getGenericType(); Annotation[] ann = obj instanceof Method ? ((Method)obj).getParameterAnnotations()[0] : ((Field)obj).getAnnotations(); doWriteParam(ori, sb, pm, cls, type, pm.getName(), ann, isJson); } } } protected void doHandleJaxrsBeanParamClassParams(OperationResourceInfo ori, StringBuilder sb, Map<Parameter, Object> params, boolean isJson, ParameterType... pType) { for (Map.Entry<Parameter, Object> entry : params.entrySet()) { Parameter pm = entry.getKey(); Object obj = entry.getValue(); if (pm.getType() == ParameterType.BEAN) { Class<?> cls = obj instanceof Method ? ((Method)obj).getParameterTypes()[0] : ((Field)obj) .getType(); doWriteJaxrsBeanParam(sb, ori, cls, isJson, pType); } } } protected void handlePathAndMatrixParams(StringBuilder sb, OperationResourceInfo ori, boolean isJson) { handleParams(sb, ori, ParameterType.PATH, isJson); handleParams(sb, ori, ParameterType.MATRIX, isJson); doWriteJaxrsBeanParams(sb, ori, isJson, ParameterType.PATH, ParameterType.MATRIX); } protected void handleParameter(StringBuilder sb, Set<Class<?>> jaxbTypes, ElementQNameResolver qnameResolver, Map<Class<?>, QName> clsMap, OperationResourceInfo ori, Parameter pm, boolean isJson) { Class<?> cls = getMethod(ori).getParameterTypes()[pm.getIndex()]; if (pm.getType() == ParameterType.REQUEST_BODY && cls != AsyncResponse.class) { handleRepresentation(sb, jaxbTypes, qnameResolver, clsMap, ori, cls, isJson, true); return; } if (pm.getType() == ParameterType.PATH || pm.getType() == ParameterType.MATRIX) { return; } if (pm.getType() == ParameterType.HEADER || pm.getType() == ParameterType.QUERY) { writeParam(sb, pm, ori, isJson); return; } if (pm.getType() == ParameterType.BEAN) { doWriteJaxrsBeanParams(sb, ori, isJson, ParameterType.HEADER, ParameterType.QUERY); } } protected void handleParams(StringBuilder sb, OperationResourceInfo ori, ParameterType type, boolean isJson) { for (Parameter pm : ori.getParameters()) { if (pm.getType() == type) { writeParam(sb, pm, ori, isJson); } } } private Annotation[] getBodyAnnotations(OperationResourceInfo ori, boolean inbound) { Method opMethod = getMethod(ori); if (inbound) { for (Parameter pm : ori.getParameters()) { if (pm.getType() == ParameterType.REQUEST_BODY) { return opMethod.getParameterAnnotations()[pm.getIndex()]; } } return new Annotation[] {}; } else { return opMethod.getDeclaredAnnotations(); } } private void writeParam(StringBuilder sb, Parameter pm, OperationResourceInfo ori, boolean isJson) { Method method = getMethod(ori); Class<?> type = method.getParameterTypes()[pm.getIndex()]; if (!"".equals(pm.getName())) { doWriteParam(ori, sb, pm, type, method.getGenericParameterTypes()[pm.getIndex()], pm.getName(), method.getParameterAnnotations()[pm.getIndex()], isJson); } else { List<Class<?>> parentBeanClasses = new LinkedList<>(); parentBeanClasses.add(type); doWriteBeanParam(ori, sb, type, pm, null, parentBeanClasses, isJson); parentBeanClasses.remove(type); } } private void doWriteJaxrsBeanParams(StringBuilder sb, OperationResourceInfo ori, boolean isJson, ParameterType ...parameterTypes) { for (Parameter p : ori.getParameters()) { if (p.getType() == ParameterType.BEAN) { Method method = getMethod(ori); Class<?> type = method.getParameterTypes()[p.getIndex()]; doWriteJaxrsBeanParam(sb, ori, type, isJson, parameterTypes); } } } private void doWriteJaxrsBeanParam(StringBuilder sb, OperationResourceInfo ori, Class<?> beanType, boolean isJson, ParameterType ...parameterTypes) { for (Method m : beanType.getMethods()) { if (m.getName().startsWith("set")) { String propertyName = StringUtils.uncapitalize(m.getName().substring(3)); Field f = InjectionUtils.getDeclaredField(beanType, propertyName); for (ParameterType parameterType : parameterTypes) { Class<? extends Annotation> annClass = getAnnotationFromParamType(parameterType); Annotation annotation = m.getAnnotation(annClass); if (annotation != null) { Parameter pm = new Parameter(parameterType, propertyName); pm.setEncoded(m.getAnnotation(Encoded.class) != null); DefaultValue dv = m.getAnnotation(DefaultValue.class); if (dv != null) { pm.setDefaultValue(dv.value()); } doWriteParam(ori, sb, pm, m.getParameterTypes()[0], m.getGenericParameterTypes()[0], propertyName, new Annotation[]{}, isJson); } else if (f != null) { annotation = f.getAnnotation(annClass); if (annotation != null) { Parameter pm = new Parameter(parameterType, propertyName); pm.setEncoded(f.getAnnotation(Encoded.class) != null); DefaultValue dv = f.getAnnotation(DefaultValue.class); if (dv != null) { pm.setDefaultValue(dv.value()); } doWriteParam(ori, sb, pm, f.getType(), f.getGenericType(), propertyName, new Annotation[]{}, isJson); } } } if (m.getAnnotation(BeanParam.class) != null) { doWriteJaxrsBeanParam(sb, ori, m.getParameterTypes()[0], isJson, parameterTypes); } else if (f != null && f.getAnnotation(BeanParam.class) != null) { doWriteJaxrsBeanParam(sb, ori, f.getType(), isJson, parameterTypes); } } } } private Class<? extends Annotation> getAnnotationFromParamType(ParameterType pt) { return PARAMETER_TYPE_MAP.get(pt); } private void doWriteBeanParam(OperationResourceInfo ori, StringBuilder sb, Class<?> type, Parameter pm, String parentName, List<Class<?>> parentBeanClasses, boolean isJson) { Map<Parameter, Class<?>> pms = InjectionUtils.getParametersFromBeanClass(type, pm.getType(), true); for (Map.Entry<Parameter, Class<?>> entry : pms.entrySet()) { String name = entry.getKey().getName(); if (parentName != null) { name = parentName + "." + name; } Class<?> paramCls = entry.getValue(); boolean isPrimitive = InjectionUtils.isPrimitive(paramCls) || paramCls.isEnum(); if (isPrimitive || Date.class.isAssignableFrom(paramCls) || XMLGregorianCalendar.class.isAssignableFrom(paramCls) || InjectionUtils.isSupportedCollectionOrArray(paramCls)) { doWriteParam(ori, sb, entry.getKey(), paramCls, paramCls, name, new Annotation[] {}, isJson); } else if (!parentBeanClasses.contains(paramCls)) { parentBeanClasses.add(paramCls); doWriteBeanParam(ori, sb, paramCls, entry.getKey(), name, parentBeanClasses, isJson); parentBeanClasses.remove(paramCls); } } } //CHECKSTYLE:OFF protected void doWriteParam(OperationResourceInfo ori, StringBuilder sb, Parameter pm, Class<?> type, Type genericType, String paramName, Annotation[] anns, boolean isJson) { //CHECKSTYLE:ON ParameterType pType = pm.getType(); boolean isForm = isFormParameter(pm, type, anns); if (paramName == null && isForm) { Multipart m = AnnotationUtils.getAnnotation(anns, Multipart.class); if (m != null) { paramName = m.value(); } } sb.append("<param name=\"").append(paramName).append("\" "); String style = ParameterType.PATH == pType ? "template" : isForm ? "query" : ParameterType.REQUEST_BODY == pType ? "plain" : pType.toString().toLowerCase(); sb.append("style=\"").append(style).append("\""); if (pm.getDefaultValue() != null) { sb.append(" default=\"").append(xmlEncodeIfNeeded(pm.getDefaultValue())) .append("\""); } if (InjectionUtils.isSupportedCollectionOrArray(type)) { type = InjectionUtils.getActualType(genericType); sb.append(" repeating=\"true\""); } String value = XmlSchemaPrimitiveUtils.getSchemaRepresentation(type); if (value == null) { if (type.isEnum()) { value = "xs:string"; } else if (type == InputStream.class) { value = "xs:anyType"; } } if (value != null) { if (isJson) { value = value.substring(3); } sb.append(" type=\"").append(value).append("\""); } if (type.isEnum()) { sb.append(">"); handleDocs(anns, sb, DocTarget.PARAM, true, isJson); setEnumOptions(sb, type); sb.append("</param>"); } else { addDocsAndCloseElement(ori, pm.getIndex(), sb, anns, "param", DocTarget.PARAM, true, isJson); } } private void setEnumOptions(StringBuilder sb, Class<?> enumClass) { try { Method m = enumClass.getMethod("values", new Class<?>[] {}); Object[] values = (Object[])m.invoke(null, new Object[] {}); m = enumClass.getMethod("toString", new Class<?>[] {}); for (Object o : values) { String str = (String)m.invoke(o, new Object[] {}); sb.append("<option value=\"").append(str).append("\"/>"); } } catch (Throwable ex) { // ignore } } //CHECKSTYLE:OFF private void addDocsAndCloseElement(OperationResourceInfo ori, int paramIndex, StringBuilder sb, Annotation[] anns, String elementName, String category, boolean allowDefault, boolean isJson) { //CHECKSTYLE:ON boolean docAnnAvailable = isDocAvailable(anns); if (docAnnAvailable || (ori != null && !docProviders.isEmpty())) { sb.append(">"); if (docAnnAvailable) { handleDocs(anns, sb, category, allowDefault, isJson); } else if (DocTarget.RETURN.equals(category)) { handleOperResponseJavaDocs(ori, sb); } else if (DocTarget.PARAM.equals(category)) { handleOperParamJavaDocs(ori, paramIndex, sb); } sb.append("</").append(elementName).append(">"); } else { sb.append("/>"); } } private boolean isDocAvailable(Annotation[] anns) { return AnnotationUtils.getAnnotation(anns, Description.class) != null || AnnotationUtils.getAnnotation(anns, Descriptions.class) != null; } // TODO: Collapse multiple parameters into a holder // CHECKSTYLE:OFF protected void handleRepresentation(StringBuilder sb, Set<Class<?>> jaxbTypes, ElementQNameResolver qnameResolver, Map<Class<?>, QName> clsMap, OperationResourceInfo ori, Class<?> type, boolean isJson, boolean inbound) { // CHECKSTYLE:ON List<MediaType> types = inbound ? ori.getConsumeTypes() : ori.getProduceTypes(); if (MultivaluedMap.class.isAssignableFrom(type)) { types = Collections.singletonList(MediaType.APPLICATION_FORM_URLENCODED_TYPE); } else if (isWildcard(types)) { types = Collections.singletonList(defaultRepMediaType); } Method opMethod = getMethod(ori); boolean isPrimitive = InjectionUtils.isPrimitive(type) && !ori.isAsync(); for (MediaType mt : types) { sb.append("<representation"); sb.append(" mediaType=\"").append(JAXRSUtils.mediaTypeToString(mt)).append("\""); if (isJson && !mt.getSubtype().contains("json")) { sb.append("/>"); continue; } boolean allowDefault = true; String docCategory; Annotation[] anns; int inParamIndex = -1; Type genericType; if (inbound) { inParamIndex = getRequestBodyParam(ori).getIndex(); anns = opMethod.getParameterAnnotations()[inParamIndex]; if (!isDocAvailable(anns)) { anns = opMethod.getAnnotations(); } docCategory = DocTarget.PARAM; genericType = opMethod.getGenericParameterTypes()[inParamIndex]; } else { anns = opMethod.getAnnotations(); docCategory = DocTarget.RETURN; allowDefault = false; genericType = opMethod.getGenericReturnType(); } if (isPrimitive) { sb.append(">"); Parameter p = inbound ? getRequestBodyParam(ori) : new Parameter(ParameterType.REQUEST_BODY, 0, "result"); doWriteParam(ori, sb, p, type, type, p.getName() == null ? "request" : p.getName(), anns, isJson); sb.append("</representation>"); } else { boolean isCollection = InjectionUtils.isSupportedCollectionOrArray(type); Class<?> theActualType; if (isCollection) { theActualType = InjectionUtils.getActualType(genericType); } else { theActualType = ResourceUtils.getActualJaxbType(type, opMethod, inbound); } if (theActualType == Object.class && !(genericType instanceof Class)) { Type theType = InjectionUtils.processGenericTypeIfNeeded( ori.getClassResourceInfo().getServiceClass(), Object.class, genericType); theActualType = InjectionUtils.getActualType(theType); } if (isJson) { sb.append(" element=\"").append(theActualType.getSimpleName()).append("\""); } else if (qnameResolver != null && (linkAnyMediaTypeToXmlSchema || mt.getSubtype().contains("xml")) && jaxbTypes.contains(theActualType)) { generateQName(sb, qnameResolver, clsMap, theActualType, isCollection, getBodyAnnotations(ori, inbound)); } addDocsAndCloseElement(ori, inParamIndex, sb, anns, "representation", docCategory, allowDefault, isJson); } } } private Parameter getRequestBodyParam(OperationResourceInfo ori) { for (Parameter p : ori.getParameters()) { if (p.getType() == ParameterType.REQUEST_BODY) { return p; } } throw new IllegalStateException(); } private boolean isWildcard(List<MediaType> types) { return types.size() == 1 && types.get(0).equals(MediaType.WILDCARD_TYPE); } private void handleFormRepresentation(StringBuilder sb, Set<Class<?>> jaxbTypes, ElementQNameResolver qnameResolver, Map<Class<?>, QName> clsMap, OperationResourceInfo ori, Class<?> type, boolean isJson) { if (type != null) { handleRepresentation(sb, jaxbTypes, qnameResolver, clsMap, ori, type, false, true); } else { List<MediaType> types = ori.getConsumeTypes(); MediaType formType = isWildcard(types) ? MediaType.APPLICATION_FORM_URLENCODED_TYPE : types .get(0); sb.append("<representation"); sb.append(" mediaType=\"").append(formType).append("\""); if (isJson) { sb.append("/>"); } else { sb.append(">"); List<Parameter> params = ori.getParameters(); for (int i = 0; i < params.size(); i++) { if (isFormParameter(params.get(i), getMethod(ori).getParameterTypes()[i], getMethod(ori) .getParameterAnnotations()[i])) { writeParam(sb, params.get(i), ori, isJson); } } sb.append("</representation>"); } } } protected List<OperationResourceInfo> sortOperationsByPath(Set<OperationResourceInfo> ops) { List<OperationResourceInfo> opsWithSamePath = new LinkedList<>(ops); Collections.sort(opsWithSamePath, new Comparator<OperationResourceInfo>() { @Override public int compare(OperationResourceInfo op1, OperationResourceInfo op2) { boolean sub1 = op1.getHttpMethod() == null; boolean sub2 = op2.getHttpMethod() == null; if (sub1 && !sub2) { return 1; } else if (!sub1 && sub2) { return -1; } URITemplate ut1 = op1.getURITemplate(); URITemplate ut2 = op2.getURITemplate(); int result = ut1.getValue().compareTo(ut2.getValue()); if (result == 0 && !(sub1 && sub2)) { result = op1.getHttpMethod().compareTo(op2.getHttpMethod()); } if (result == 0 && ignoreOverloadedMethods && op1.getMethodToInvoke().getName().equals(op2.getMethodToInvoke().getName())) { Integer paramLen1 = op1.getMethodToInvoke().getParameterTypes().length; Integer paramLen2 = op2.getMethodToInvoke().getParameterTypes().length; result = paramLen1.compareTo(paramLen2) * -1; } return result; } }); return opsWithSamePath; } public List<ClassResourceInfo> getResourcesList(Message m, UriInfo ui) { final String slash = "/"; String path = ui.getPath(); if (!path.startsWith(slash)) { path = slash + path; } List<ClassResourceInfo> all = ((JAXRSServiceImpl)m.getExchange().getService()) .getClassResourceInfos(); boolean absolutePathSlashOn = checkAbsolutePathSlash && ui.getAbsolutePath().getPath().endsWith(slash); if (slash.equals(path) && !absolutePathSlashOn) { return all; } List<ClassResourceInfo> cris = new LinkedList<>(); for (ClassResourceInfo cri : all) { MultivaluedMap<String, String> map = new MetadataMap<>(); if (cri.getURITemplate().match(path, map) && slash.equals(map.getFirst(URITemplate.FINAL_MATCH_GROUP))) { cris.add(cri); } } return cris; } // TODO: deal with caching later on public Response getExistingWadl(Message m, UriInfo ui, MediaType mt) { Endpoint ep = m.getExchange().getEndpoint(); if (ep != null) { String loc = (String)ep.get(JAXRSUtils.DOC_LOCATION); if (loc != null) { try { InputStream is = ResourceUtils.getResourceStream(loc, (Bus)ep.get(Bus.class.getName())); if (is != null) { Object contextProp = m.getContextualProperty(CONVERT_WADL_RESOURCES_TO_DOM); boolean doConvertResourcesToDOM = contextProp == null ? convertResourcesToDOM : PropertyUtils.isTrue(contextProp); if (!doConvertResourcesToDOM || isJson(mt)) { return Response.ok(is, mt).build(); } Document wadlDoc = StaxUtils.read(is); Element appEl = wadlDoc.getDocumentElement(); List<Element> grammarEls = DOMUtils.getChildrenWithName(appEl, WadlGenerator.WADL_NS, "grammars"); if (grammarEls.size() == 1) { handleExistingDocRefs(DOMUtils.getChildrenWithName(grammarEls.get(0), WadlGenerator.WADL_NS, "include"), "href", loc, "", m, ui); } List<Element> resourcesEls = DOMUtils.getChildrenWithName(appEl, WadlGenerator.WADL_NS, "resources"); if (resourcesEls.size() == 1) { DOMUtils.setAttribute(resourcesEls.get(0), "base", getBaseURI(m, ui)); List<Element> resourceEls = DOMUtils.getChildrenWithName(resourcesEls.get(0), WadlGenerator.WADL_NS, "resource"); handleExistingDocRefs(resourceEls, "type", loc, "", m, ui); return finalizeExistingWadlResponse(wadlDoc, m, ui, mt); } } } catch (Exception ex) { throw ExceptionUtils.toInternalServerErrorException(ex, null); } } } return null; } private Response finalizeExistingWadlResponse(Document wadlDoc, Message m, UriInfo ui, MediaType mt) throws Exception { Object entity; if (stylesheetReference != null) { if (!applyStylesheetLocally) { ProcessingInstruction pi = wadlDoc.createProcessingInstruction("xml-stylesheet", getStylesheetInstructionData(getBaseURI(m, ui))); wadlDoc.insertBefore(pi, wadlDoc.getDocumentElement()); entity = copyDOMToString(wadlDoc); } else { entity = transformLocally(m, ui, new DOMSource(wadlDoc)); } } else { entity = new DOMSource(wadlDoc); } return Response.ok(entity, mt).build(); } private String copyDOMToString(Document wadlDoc) throws Exception { DOMSource domSource = new DOMSource(wadlDoc); // temporary workaround StringWriter stringWriter = new StringWriter(); TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformerFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); Transformer transformer = transformerFactory.newTransformer(); transformer.transform(domSource, new StreamResult(stringWriter)); return stringWriter.toString(); } private String transformLocally(Message m, UriInfo ui, Source source) throws Exception { InputStream is = ResourceUtils.getResourceStream(stylesheetReference, m.getExchange().getBus()); TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformerFactory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true); Transformer t = transformerFactory.newTemplates(new StreamSource(is)).newTransformer(); t.setParameter("base.path", (String)m.get("http.base.path")); StringWriter stringWriter = new StringWriter(); t.transform(source, new StreamResult(stringWriter)); return stringWriter.toString(); } // TODO: deal with caching later on public Response getExistingResource(Message m, UriInfo ui, String href) { try { String loc = docLocationMap.get(href); if (loc != null) { int fragmentIndex = loc.lastIndexOf('#'); if (fragmentIndex != -1) { loc = loc.substring(0, fragmentIndex); } InputStream is = ResourceUtils.getResourceStream(loc, m.getExchange().getBus()); if (is != null) { Element docEl = StaxUtils.read(is).getDocumentElement(); if (href.endsWith(".xsl")) { List<Element> xslImports = DOMUtils.getChildrenWithName(docEl, XLS_NS, "import"); handleExistingDocRefs(xslImports, "href", loc, href, m, ui); List<Element> xslIncludes = DOMUtils.getChildrenWithName(docEl, XLS_NS, "include"); handleExistingDocRefs(xslIncludes, "href", loc, href, m, ui); } else { if (fragmentIndex != -1) { List<Element> grammarEls = DOMUtils.getChildrenWithName(docEl, WADL_NS, "grammars"); if (grammarEls.size() == 1) { handleExistingDocRefs(DOMUtils.getChildrenWithName(grammarEls.get(0), WADL_NS, "include"), "href", loc, href, m, ui); } } else { handleExistingDocRefs(DOMUtils.getChildrenWithName(docEl, Constants.URI_2001_SCHEMA_XSD, "import"), "schemaLocation", loc, href, m, ui); handleExistingDocRefs(DOMUtils.getChildrenWithName(docEl, Constants.URI_2001_SCHEMA_XSD, "include"), "schemaLocation", loc, href, m, ui); } } return Response.ok().type(MediaType.APPLICATION_XML_TYPE).entity(new DOMSource(docEl)) .build(); } } else if (stylesheetReference != null && href.endsWith(".xsl")) { InputStream is = ResourceUtils.getResourceStream(href, m.getExchange().getBus()); return Response.ok().type(MediaType.APPLICATION_XML_TYPE).entity(is).build(); } } catch (Exception ex) { throw ExceptionUtils.toBadRequestException(null, null); } return null; } private void handleExistingDocRefs(List<Element> elements, String attrName, String parentDocLoc, String parentRef, Message m, UriInfo ui) { if (keepRelativeDocLinks) { return; } int index = parentDocLoc.lastIndexOf('/'); parentDocLoc = index == -1 ? parentDocLoc : parentDocLoc.substring(0, index + 1); index = parentRef.lastIndexOf('/'); parentRef = index == -1 ? "" : parentRef.substring(0, index + 1); for (Element element : elements) { String href = element.getAttribute(attrName); String originalRef = href; if (!StringUtils.isEmpty(href) && !href.startsWith("#")) { int fragmentIndex = href.lastIndexOf('#'); String fragment = null; if (fragmentIndex != -1) { fragment = href.substring(fragmentIndex + 1); href = href.substring(0, fragmentIndex); } String actualRef = parentRef + href; docLocationMap.put(actualRef, parentDocLoc + originalRef); UriBuilder ub = UriBuilder.fromUri(getBaseURI(m, ui)).path(actualRef).fragment(fragment); URI schemaURI = ub.build(); DOMUtils.setAttribute(element, attrName, schemaURI.toString()); } } } private void generateQName(StringBuilder sb, ElementQNameResolver qnameResolver, Map<Class<?>, QName> clsMap, Class<?> type, boolean isCollection, Annotation[] annotations) { if (!isCollection) { QName typeQName = clsMap.get(type); if (typeQName != null) { writeQName(sb, typeQName); return; } } QName qname = qnameResolver.resolve(type, annotations, Collections.unmodifiableMap(clsMap)); if (qname != null) { if (!isCollection) { writeQName(sb, qname); clsMap.put(type, qname); } else { XMLName name = AnnotationUtils.getAnnotation(annotations, XMLName.class); String localPart; if (name != null) { localPart = JAXRSUtils.convertStringToQName(name.value()).getLocalPart(); } else { localPart = qname.getLocalPart() + "s"; } QName collectionName = new QName(qname.getNamespaceURI(), localPart, qname.getPrefix()); writeQName(sb, collectionName); } } } private void writeQName(StringBuilder sb, QName qname) { sb.append(" element=\"").append(qname.getPrefix()).append(':').append(qname.getLocalPart()).append("\""); } private boolean isXmlRoot(Class<?> cls) { return cls.getAnnotation(XmlRootElement.class) != null; } private SchemaCollection getSchemaCollection(ResourceTypes resourceTypes, JAXBContext context) { if (context == null) { return null; } SchemaCollection xmlSchemaCollection = new SchemaCollection(); Collection<DOMSource> schemas = new HashSet<>(); List<String> targetNamespaces = new ArrayList<>(); try { for (DOMResult r : JAXBUtils.generateJaxbSchemas(context, CastUtils.cast(Collections.emptyMap(), String.class, DOMResult.class))) { Document doc = (Document)r.getNode(); ElementQNameResolver theResolver = createElementQNameResolver(context); String tns = doc.getDocumentElement().getAttribute("targetNamespace"); String tnsPrefix = doc.getDocumentElement().lookupPrefix(tns); if (tnsPrefix == null) { String tnsDecl = doc.getDocumentElement().getAttribute("xmlns:tns"); tnsPrefix = tnsDecl != null && tnsDecl.equals(tns) ? "tns:" : ""; } else { tnsPrefix += ":"; } if (supportJaxbXmlType) { for (Class<?> cls : resourceTypes.getAllTypes().keySet()) { if (isXmlRoot(cls)) { continue; } XmlType root = cls.getAnnotation(XmlType.class); if (root != null) { QName typeName = theResolver.resolve(cls, new Annotation[] {}, Collections.<Class<?>, QName> emptyMap()); if (typeName != null && tns.equals(typeName.getNamespaceURI())) { QName elementName = resourceTypes.getXmlNameMap().get(cls); if (elementName == null) { elementName = typeName; } Element newElement = doc .createElementNS(Constants.URI_2001_SCHEMA_XSD, "xs:element"); newElement.setAttribute("name", elementName.getLocalPart()); newElement.setAttribute("type", tnsPrefix + typeName.getLocalPart()); if (Modifier.isAbstract(cls.getModifiers()) && resourceTypes.getSubstitutions().values().contains(cls)) { newElement.setAttribute("abstract", "true"); } doc.getDocumentElement().appendChild(newElement); } } } if (supportJaxbSubstitutions) { for (Map.Entry<Class<?>, Class<?>> entry : resourceTypes.getSubstitutions().entrySet()) { QName typeName = theResolver.resolve(entry.getKey(), new Annotation[] {}, Collections.<Class<?>, QName> emptyMap()); for (Element element : DOMUtils.findAllElementsByTagNameNS(doc.getDocumentElement(), Constants.URI_2001_SCHEMA_XSD, "element")) { if (element.getAttribute("name").equals(typeName.getLocalPart())) { QName groupName = theResolver.resolve(entry.getValue(), new Annotation[] {}, Collections.<Class<?>, QName> emptyMap()); if (groupName != null) { element.setAttribute("substitutionGroup", tnsPrefix + groupName.getLocalPart()); } } } } } } if (supportCollections && !resourceTypes.getCollectionMap().isEmpty()) { for (Map.Entry<Class<?>, QName> entry : resourceTypes.getCollectionMap().entrySet()) { QName colQName = entry.getValue(); if (colQName == null) { colQName = theResolver.resolve(entry.getKey(), new Annotation[] {}, Collections.<Class<?>, QName> emptyMap()); if (colQName != null) { colQName = new QName(colQName.getNamespaceURI(), colQName.getLocalPart() + "s", colQName.getPrefix()); } } if (colQName == null) { continue; } if (tns.equals(colQName.getNamespaceURI())) { QName typeName = theResolver.resolve(entry.getKey(), new Annotation[] {}, Collections.<Class<?>, QName> emptyMap()); if (typeName != null) { Element newElement = doc .createElementNS(Constants.URI_2001_SCHEMA_XSD, "xs:element"); newElement.setAttribute("name", colQName.getLocalPart()); Element ctElement = doc.createElementNS(Constants.URI_2001_SCHEMA_XSD, "xs:complexType"); newElement.appendChild(ctElement); Element seqElement = doc .createElementNS(Constants.URI_2001_SCHEMA_XSD, "xs:sequence"); ctElement.appendChild(seqElement); Element xsElement = doc.createElementNS(Constants.URI_2001_SCHEMA_XSD, "xs:element"); seqElement.appendChild(xsElement); xsElement.setAttribute("ref", tnsPrefix + typeName.getLocalPart()); xsElement.setAttribute("minOccurs", "0"); xsElement.setAttribute("maxOccurs", "unbounded"); doc.getDocumentElement().appendChild(newElement); } } } } DOMSource source = new DOMSource(doc, r.getSystemId()); schemas.add(source); if (!StringUtils.isEmpty(tns)) { targetNamespaces.add(tns); } } } catch (IOException e) { LOG.fine("No schema can be generated"); return null; } boolean hackAroundEmptyNamespaceIssue = false; for (DOMSource r : schemas) { hackAroundEmptyNamespaceIssue = addSchemaDocument(xmlSchemaCollection, targetNamespaces, (Document)r.getNode(), r.getSystemId(), hackAroundEmptyNamespaceIssue); } return xmlSchemaCollection; } private QName getJaxbQName(String name, String namespace, Class<?> type, Map<Class<?>, QName> clsMap) { QName qname = getQNameFromParts(name, namespace, type, clsMap); if (qname != null) { return qname; } String ns = JAXBUtils.getPackageNamespace(type); if (ns != null) { return getQNameFromParts(name, ns, type, clsMap); } else { return null; } } private QName getJaxbQName(JAXBContextProxy jaxbProxy, Class<?> type, Map<Class<?>, QName> clsMap) { XmlRootElement root = type.getAnnotation(XmlRootElement.class); if (root != null) { return getJaxbQName(root.name(), root.namespace(), type, clsMap); } try { JAXBBeanInfo jaxbInfo = jaxbProxy == null ? null : JAXBUtils.getBeanInfo(jaxbProxy, type); if (jaxbInfo == null) { return null; } Object instance = type.newInstance(); return getQNameFromParts(jaxbInfo.getElementLocalName(instance), jaxbInfo.getElementNamespaceURI(instance), type, clsMap); } catch (Exception ex) { // ignore } return null; } private String getPrefix(String ns, Map<Class<?>, QName> clsMap) { String prefix = null; int index = 0; for (QName name : clsMap.values()) { String currentPrefix = name.getPrefix(); if (currentPrefix.startsWith(nsPrefix)) { int currentIndex = currentPrefix.equals(nsPrefix) ? 0 : Integer.valueOf(currentPrefix.substring(nsPrefix.length())); if (currentIndex > index) { index = currentIndex; } } if (name.getNamespaceURI().equals(ns)) { prefix = currentPrefix; break; } } if (StringUtils.isEmpty(prefix)) { prefix = index == 0 && !incrementNamespacePrefix ? nsPrefix : nsPrefix + (index + 1); } return prefix; } private boolean isFormRequest(OperationResourceInfo ori) { for (Parameter p : ori.getParameters()) { if (p.getType() == ParameterType.FORM || p.getType() == ParameterType.REQUEST_BODY && (getMethod(ori).getParameterTypes()[p.getIndex()] == MultivaluedMap.class || AnnotationUtils .getAnnotation(getMethod(ori).getParameterAnnotations()[p.getIndex()], Multipart.class) != null)) { return true; } } return false; } private Class<?> getFormClass(OperationResourceInfo ori) { List<Parameter> params = ori.getParameters(); for (int i = 0; i < params.size(); i++) { if (isFormParameter(params.get(i), getMethod(ori).getParameterTypes()[i], getMethod(ori) .getParameterAnnotations()[i])) { return null; } } return MultivaluedMap.class; } private boolean isFormParameter(Parameter pm, Class<?> type, Annotation[] anns) { return ParameterType.FORM == pm.getType() || ParameterType.REQUEST_BODY == pm.getType() && AnnotationUtils.getAnnotation(anns, Multipart.class) != null && (InjectionUtils.isPrimitive(type) || type == InputStream.class); } // TODO : can we reuse this block with JAXBBinding somehow ? public boolean addSchemaDocument(SchemaCollection col, List<String> tnsList, Document d, String systemId, boolean hackAroundEmptyNamespaceIssue) { String ns = d.getDocumentElement().getAttribute("targetNamespace"); if (StringUtils.isEmpty(ns)) { if (DOMUtils.getFirstElement(d.getDocumentElement()) == null) { hackAroundEmptyNamespaceIssue = true; return hackAroundEmptyNamespaceIssue; } // create a copy of the dom so we // can modify it. d = copy(d); ns = tnsList.isEmpty() ? "" : tnsList.get(0); d.getDocumentElement().setAttribute("targetNamespace", ns); } if (hackAroundEmptyNamespaceIssue) { d = doEmptyNamespaceHack(d); } Node n = d.getDocumentElement().getFirstChild(); while (n != null) { if (n instanceof Element) { Element e = (Element)n; if (e.getLocalName().equals("import")) { e.removeAttribute("schemaLocation"); } } n = n.getNextSibling(); } synchronized (d) { col.read(d, systemId); } return hackAroundEmptyNamespaceIssue; } private Document doEmptyNamespaceHack(Document d) { boolean hasStuffToRemove = false; Element el = DOMUtils.getFirstElement(d.getDocumentElement()); while (el != null) { if ("import".equals(el.getLocalName()) && StringUtils.isEmpty(el.getAttribute("targetNamespace"))) { hasStuffToRemove = true; break; } el = DOMUtils.getNextElement(el); } if (hasStuffToRemove) { // create a copy of the dom so we // can modify it. d = copy(d); el = DOMUtils.getFirstElement(d.getDocumentElement()); while (el != null) { if ("import".equals(el.getLocalName()) && StringUtils.isEmpty(el.getAttribute("targetNamespace"))) { d.getDocumentElement().removeChild(el); el = DOMUtils.getFirstElement(d.getDocumentElement()); } else { el = DOMUtils.getNextElement(el); } } } return d; } private Document copy(Document doc) { try { return StaxUtils.copy(doc); } catch (XMLStreamException | ParserConfigurationException e) { // ignore } return doc; } private QName getQNameFromParts(String name, String namespace, Class<?> type, Map<Class<?>, QName> clsMap) { if (namespace == null || JAXB_DEFAULT_NAMESPACE.equals(namespace) || namespace.length() == 0) { return null; } if (name == null || name.length() == 0) { return null; } if (JAXB_DEFAULT_NAME.equals(name)) { name = StringUtils.uncapitalize(type.getSimpleName()); } String prefix = getPrefix(namespace, clsMap); return new QName(namespace, name, prefix); } public void setIgnoreMessageWriters(boolean ignoreMessageWriters) { this.ignoreMessageWriters = ignoreMessageWriters; } private void handleApplicationDocs(StringBuilder sbApp) { if (applicationTitle != null) { sbApp.append("<doc title=\"").append(xmlEncodeIfNeeded(applicationTitle)).append("\"/>"); } } protected void handleClassJavaDocs(ClassResourceInfo cri, StringBuilder sb) { for (DocumentationProvider docProvider : docProviders) { addProvidedDocs(sb, docProvider.getClassDoc(cri)); } } protected void handleOperJavaDocs(OperationResourceInfo ori, StringBuilder sb) { for (DocumentationProvider docProvider : docProviders) { addProvidedDocs(sb, docProvider.getMethodDoc(ori)); } } protected void handleOperResponseJavaDocs(OperationResourceInfo ori, StringBuilder sb) { for (DocumentationProvider docProvider : docProviders) { addProvidedDocs(sb, docProvider.getMethodResponseDoc(ori)); } } protected void handleOperParamJavaDocs(OperationResourceInfo ori, int paramIndex, StringBuilder sb) { for (DocumentationProvider docProvider : docProviders) { addProvidedDocs(sb, docProvider.getMethodParameterDoc(ori, paramIndex)); } } private void addProvidedDocs(StringBuilder sb, String text) { if (!StringUtils.isEmpty(text)) { sb.append("<doc>"); sb.append(xmlEncodeIfNeeded(text)); sb.append("</doc>"); } } protected boolean handleDocs(Annotation[] anns, StringBuilder sb, String category, boolean allowDefault, boolean isJson) { boolean found = false; for (Annotation a : anns) { if (a.annotationType() == Descriptions.class) { Descriptions ds = (Descriptions)a; return handleDocs(ds.value(), sb, category, allowDefault, isJson); } if (a.annotationType() == Description.class) { Description d = (Description)a; if (d.target().length() == 0 && !allowDefault || d.target().length() > 0 && !d.target().equals(category)) { continue; } sb.append("<doc"); if (!isJson && d.lang().length() > 0) { sb.append(" xml:lang=\"").append(d.lang()).append("\""); } if (d.title().length() > 0) { sb.append(" title=\"").append(xmlEncodeIfNeeded(d.title())).append("\""); } sb.append(">"); if (d.value().length() > 0) { sb.append(xmlEncodeIfNeeded(d.value())); } else if (d.docuri().length() > 0) { InputStream is; if (d.docuri().startsWith(CLASSPATH_PREFIX)) { String path = d.docuri().substring(CLASSPATH_PREFIX.length()); is = ResourceUtils.getClasspathResourceStream(path, SchemaHandler.class, bus == null ? BusFactory.getDefaultBus() : bus); if (is != null) { try { sb.append(IOUtils.toString(is)); } catch (IOException ex) { // ignore } } } } sb.append("</doc>"); found = true; } } return found; } private String getNamespace() { return wadlNamespace != null ? wadlNamespace : WADL_NS; } public void setWadlNamespace(String namespace) { this.wadlNamespace = namespace; } public void setSingleResourceMultipleMethods(boolean singleResourceMultipleMethods) { this.singleResourceMultipleMethods = singleResourceMultipleMethods; } public void setUseSingleSlashResource(boolean useSingleSlashResource) { this.useSingleSlashResource = useSingleSlashResource; } @Deprecated public void setLinkJsonToXmlSchema(boolean link) { setLinkAnyMediaTypeToXmlSchema(link); } public void setLinkAnyMediaTypeToXmlSchema(boolean link) { linkAnyMediaTypeToXmlSchema = link; } public void setSchemaLocations(List<String> locations) { externalQnamesMap = new HashMap<>(); externalSchemasCache = new ArrayList<>(locations.size()); for (int i = 0; i < locations.size(); i++) { String loc = locations.get(i); try { loadSchemasIntoCache(loc); } catch (Exception ex) { LOG.warning("No schema resource " + loc + " can be loaded : " + ex.getMessage()); externalSchemasCache = null; externalQnamesMap = null; return; } } } private void loadSchemasIntoCache(String loc) throws Exception { InputStream is = ResourceUtils.getResourceStream(loc, bus == null ? BusFactory.getDefaultBus() : bus); if (is == null) { return; } try (ByteArrayInputStream bis = IOUtils.loadIntoBAIS(is)) { XMLSource source = new XMLSource(bis); source.setBuffering(); String targetNs = source.getValue("/*/@targetNamespace"); Map<String, String> nsMap = Collections.singletonMap("xs", Constants.URI_2001_SCHEMA_XSD); String[] elementNames = source.getValues("/*/xs:element/@name", nsMap); externalQnamesMap.put(targetNs, Arrays.asList(elementNames)); String schemaValue = source.getNode("/xs:schema", nsMap, String.class); externalSchemasCache.add(schemaValue); } } public void setUseJaxbContextForQnames(boolean checkJaxbOnly) { this.useJaxbContextForQnames = checkJaxbOnly; } protected ElementQNameResolver createElementQNameResolver(JAXBContext context) { if (resolver != null) { return resolver; } if (useJaxbContextForQnames) { if (context != null) { JAXBContextProxy proxy = JAXBUtils.createJAXBContextProxy(context); return new JaxbContextQNameResolver(proxy); } else { return null; } } else if (externalQnamesMap != null) { return new SchemaQNameResolver(externalQnamesMap); } else { return new XMLNameQNameResolver(); } } protected SchemaWriter createSchemaWriter(ResourceTypes resourceTypes, JAXBContext context, UriInfo ui) { // if neither externalSchemaLinks nor externalSchemasCache is set // then JAXBContext will be used to generate the schema if (externalSchemaLinks != null && externalSchemasCache == null) { return new ExternalSchemaWriter(externalSchemaLinks, ui); } else if (externalSchemasCache != null) { return new StringSchemaWriter(externalSchemasCache, externalSchemaLinks, ui); } else { SchemaCollection coll = getSchemaCollection(resourceTypes, context); if (coll != null) { return new SchemaCollectionWriter(coll); } } return null; } public void setExternalLinks(List<String> externalLinks) { externalSchemaLinks = new LinkedList<>(); for (String s : externalLinks) { try { String href = s; if (href.startsWith("classpath:")) { int index = href.lastIndexOf('/'); href = index == -1 ? href.substring(9) : href.substring(index + 1); docLocationMap.put(href, s); } externalSchemaLinks.add(URI.create(href)); } catch (Exception ex) { LOG.warning("Not a valid URI : " + s); externalSchemaLinks = null; break; } } } protected interface SchemaWriter { void write(StringBuilder sb); } private class StringSchemaWriter implements SchemaWriter { private final List<String> theSchemas; StringSchemaWriter(List<String> schemas, List<URI> links, UriInfo ui) { this.theSchemas = new LinkedList<>(); // we'll need to do the proper schema caching eventually for (String s : schemas) { XMLSource source = new XMLSource(new ByteArrayInputStream(s.getBytes())); source.setBuffering(); Map<String, String> locs = getLocationsMap(source, "import", links, ui); locs.putAll(getLocationsMap(source, "include", links, ui)); String actualSchema = !locs.isEmpty() ? transformSchema(s, locs) : s; theSchemas.add(actualSchema); } } private Map<String, String> getLocationsMap(XMLSource source, String elementName, List<URI> links, UriInfo ui) { Map<String, String> nsMap = Collections.singletonMap("xs", Constants.URI_2001_SCHEMA_XSD); String[] locations = source.getValues("/*/xs:" + elementName + "/@schemaLocation", nsMap); Map<String, String> locs = new HashMap<>(); if (locations == null) { return locs; } for (String loc : locations) { try { URI uri = URI.create(loc); if (!uri.isAbsolute()) { if (links != null) { for (URI overwriteURI : links) { if (overwriteURI.toString().endsWith(loc)) { if (overwriteURI.isAbsolute()) { locs.put(loc, overwriteURI.toString()); } else { locs.put(loc, ui.getBaseUriBuilder().path(overwriteURI.toString()) .build().toString()); } break; } } } if (!locs.containsKey(loc)) { locs.put(loc, ui.getBaseUriBuilder().path(loc).build().toString()); } } } catch (Exception ex) { // continue } } return locs; } private String transformSchema(String schema, Map<String, String> locs) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); SchemaConverter sc = new SchemaConverter(StaxUtils.createXMLStreamWriter(bos), locs); try { StaxUtils.copy(new StreamSource(new StringReader(schema)), sc); sc.flush(); sc.close(); return bos.toString(); } catch (Exception ex) { return schema; } } @Override public void write(StringBuilder sb) { for (String s : theSchemas) { sb.append(s); } } } private class SchemaCollectionWriter implements SchemaWriter { private final SchemaCollection coll; SchemaCollectionWriter(SchemaCollection coll) { this.coll = coll; } @Override public void write(StringBuilder sb) { for (XmlSchema xs : coll.getXmlSchemas()) { if (xs.getItems().isEmpty() || Constants.URI_2001_SCHEMA_XSD.equals(xs.getTargetNamespace())) { continue; } StringWriter writer = new StringWriter(); xs.write(writer); sb.append(writer.toString()); } } } private class ExternalSchemaWriter implements SchemaWriter { private final List<URI> links; private final UriInfo uriInfo; ExternalSchemaWriter(List<URI> links, UriInfo ui) { this.links = links; this.uriInfo = ui; } @Override public void write(StringBuilder sb) { for (URI link : links) { try { URI value = link.isAbsolute() ? link : uriInfo.getBaseUriBuilder().path(link.toString()).build(); sb.append("<include href=\"").append(value.toString()).append("\"/>"); } catch (Exception ex) { LOG.warning("WADL grammar section will be incomplete, this link is not a valid URI : " + link.toString()); } } } } private class JaxbContextQNameResolver implements ElementQNameResolver { private final JAXBContextProxy proxy; JaxbContextQNameResolver(JAXBContextProxy proxy) { this.proxy = proxy; } @Override public QName resolve(Class<?> type, Annotation[] annotations, Map<Class<?>, QName> clsMap) { QName qname = WadlGenerator.this.getJaxbQName(proxy, type, clsMap); if (qname == null && supportJaxbXmlType) { XmlType root = type.getAnnotation(XmlType.class); if (root != null) { XMLName name = AnnotationUtils.getAnnotation(annotations, XMLName.class); if (name == null) { qname = getJaxbQName(root.name(), root.namespace(), type, clsMap); } else { QName tempQName = JAXRSUtils.convertStringToQName(name.value()); qname = new QName(tempQName.getNamespaceURI(), tempQName.getLocalPart(), getPrefix(tempQName.getNamespaceURI(), clsMap)); } } } return qname; } } private class XMLNameQNameResolver implements ElementQNameResolver { @Override public QName resolve(Class<?> type, Annotation[] annotations, Map<Class<?>, QName> clsMap) { XMLName name = AnnotationUtils.getAnnotation(annotations, XMLName.class); if (name == null) { name = type.getAnnotation(XMLName.class); } if (name != null) { QName qname = DOMUtils.convertStringToQName(name.value(), name.prefix()); if (qname.getPrefix().length() > 0) { return qname; } else { return getQNameFromParts(qname.getLocalPart(), qname.getNamespaceURI(), type, clsMap); } } return null; } } private class SchemaQNameResolver implements ElementQNameResolver { private final Map<String, List<String>> map; SchemaQNameResolver(Map<String, List<String>> map) { this.map = map; } @Override public QName resolve(Class<?> type, Annotation[] annotations, Map<Class<?>, QName> clsMap) { String name = type.getSimpleName(); for (Map.Entry<String, List<String>> entry : map.entrySet()) { String elementName = null; if (entry.getValue().contains(name)) { elementName = name; } else if (entry.getValue().contains(name.toLowerCase())) { elementName = name.toLowerCase(); } if (elementName != null) { return getQNameFromParts(elementName, entry.getKey(), type, clsMap); } } return null; } } public void setResolver(ElementQNameResolver resolver) { this.resolver = resolver; } public void setPrivateAddresses(List<String> privateAddresses) { this.privateAddresses = privateAddresses; } public List<String> getPrivateAddresses() { return privateAddresses; } public void setAddResourceAndMethodIds(boolean addResourceAndMethodIds) { ResourceIdGenerator idGen = addResourceAndMethodIds ? new ResourceIdGeneratorImpl() : null; setResourceIdGenerator(idGen); } public void setResourceIdGenerator(ResourceIdGenerator idGen) { this.idGenerator = idGen; } private Method getMethod(OperationResourceInfo ori) { Method annMethod = ori.getAnnotatedMethod(); return annMethod != null ? annMethod : ori.getMethodToInvoke(); } public void setApplicationTitle(String applicationTitle) { this.applicationTitle = applicationTitle; } public void setNamespacePrefix(String prefix) { this.nsPrefix = prefix; } public void setIgnoreForwardSlash(boolean ignoreForwardSlash) { this.ignoreForwardSlash = ignoreForwardSlash; } public void setIgnoreRequests(boolean ignoreRequests) { this.ignoreRequests = ignoreRequests; } public void setSupportCollections(boolean support) { this.supportCollections = support; } /** * Set the default WADL response media type. * For example, a browser may display WADL better if Content-Type * is set to application/xml which is a default response content type. * Users may set it to application/vnd.sun.wadl+xml or other type. * @param mt WADL response media type */ public void setDefaultMediaType(String mt) { this.defaultWadlResponseMediaType = JAXRSUtils.toMediaType(mt); } /** * Set the default representation media type to be used * if JAX-RS Produces or Consumes annotation is missing. * Wild-card media type is used by default in such cases. * @param mt the default representation media type */ public void setDefaultRepresentationMediaType(String mt) { this.defaultWadlResponseMediaType = JAXRSUtils.toMediaType(mt); } public void setSupportJaxbXmlType(boolean supportJaxbXmlType) { this.supportJaxbXmlType = supportJaxbXmlType; } public void setSupportJaxbSubstitutions(boolean supportJaxbSubstitutions) { this.supportJaxbSubstitutions = supportJaxbSubstitutions; } public void setCheckAbsolutePathSlash(boolean checkAbsolutePathSlash) { this.checkAbsolutePathSlash = checkAbsolutePathSlash; } public void setJavaDocPath(String path) throws Exception { setDocumentationProvider(new JavaDocProvider(bus == null ? BusFactory.getDefaultBus() : bus, path)); } public void setJavaDocPaths(String... paths) throws Exception { setDocumentationProvider(new JavaDocProvider(bus == null ? BusFactory.getDefaultBus() : bus, paths)); } public void setJavaDocURLs(final URL[] javaDocURLs) { setDocumentationProvider(new JavaDocProvider(javaDocURLs)); } public void setDocumentationProvider(DocumentationProvider p) { docProviders.add(p); } public void setDocumentationProvider(List<DocumentationProvider> ps) { docProviders.addAll(ps); } public void setStylesheetReference(String stylesheetReference) { this.stylesheetReference = stylesheetReference; } public void setWadlSchemaLocation(String loc) { this.wadlSchemaLocation = loc; } public void setIncludeDefaultWadlSchemaLocation(boolean inc) { if (inc) { setWadlSchemaLocation(DEFAULT_WADL_SCHEMA_LOC); } } public void setIgnoreOverloadedMethods(boolean ignore) { this.ignoreOverloadedMethods = ignore; } public void setKeepRelativeDocLinks(boolean keepRelativeDocLinks) { this.keepRelativeDocLinks = keepRelativeDocLinks; } public void setApplyStylesheetLocally(boolean applyStylesheetLocally) { this.applyStylesheetLocally = applyStylesheetLocally; } public void setUsePathParamsToCompareOperations(boolean usePathParamsToCompareOperations) { this.usePathParamsToCompareOperations = usePathParamsToCompareOperations; } public void setConvertResourcesToDOM(boolean convertResourcesToDOM) { this.convertResourcesToDOM = convertResourcesToDOM; } public void setIncrementNamespacePrefix(boolean incrementNamespacePrefix) { this.incrementNamespacePrefix = incrementNamespacePrefix; } public void setJaxbContextProperties(Map<String, Object> jaxbContextProperties) { this.jaxbContextProperties = jaxbContextProperties; } private static class SchemaConverter extends DelegatingXMLStreamWriter { private static final String SCHEMA_LOCATION = "schemaLocation"; private final Map<String, String> locsMap; SchemaConverter(XMLStreamWriter writer, Map<String, String> locsMap) { super(writer); this.locsMap = locsMap; } @Override public void writeAttribute(String local, String value) throws XMLStreamException { if (SCHEMA_LOCATION.equals(local) && locsMap.containsKey(value)) { value = locsMap.get(value); } super.writeAttribute(local, value); } } private class ResourceIdGeneratorImpl implements ResourceIdGenerator { @Override public String getClassResourceId(ClassResourceInfo cri) { Class<?> serviceClass = cri != null ? cri.getServiceClass() : Object.class; QName jaxbQname = null; if (useJaxbContextForQnames) { jaxbQname = getJaxbQName(null, serviceClass, new HashMap<Class<?>, QName>(0)); } String pName = jaxbQname == null ? PackageUtils.getPackageName(serviceClass) : null; String localName = jaxbQname == null ? serviceClass.getSimpleName() : jaxbQname.getLocalPart(); String nsName = jaxbQname == null ? pName + "." : ""; return nsName + localName; } @Override public String getMethodResourceId(OperationResourceInfo ori) { return getMethod(ori).getName(); } } }