1 package com.atlassian.plugins.rest.doclet.generators.resourcedoc;
2
3 import com.atlassian.annotations.tenancy.TenantAware;
4 import com.atlassian.plugins.rest.common.version.ApiVersion;
5 import com.atlassian.plugins.rest.doclet.generators.schema.RichClass;
6 import com.atlassian.plugins.rest.doclet.generators.schema.SchemaGenerator;
7 import com.atlassian.rest.annotation.RestProperty;
8 import com.google.common.collect.Lists;
9 import com.sun.jersey.api.model.AbstractResource;
10 import com.sun.jersey.api.model.AbstractResourceMethod;
11 import com.sun.jersey.server.wadl.WadlGenerator;
12 import com.sun.jersey.server.wadl.generators.resourcedoc.WadlGeneratorResourceDocSupport;
13 import com.sun.jersey.server.wadl.generators.resourcedoc.model.ResourceDocType;
14 import com.sun.jersey.server.wadl.generators.resourcedoc.xhtml.Elements;
15 import com.sun.research.ws.wadl.Doc;
16 import com.sun.research.ws.wadl.Method;
17 import com.sun.research.ws.wadl.Representation;
18 import com.sun.research.ws.wadl.Resource;
19 import com.sun.research.ws.wadl.Response;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
22 import org.w3c.dom.Document;
23 import org.w3c.dom.NamedNodeMap;
24 import org.w3c.dom.Node;
25 import org.w3c.dom.NodeList;
26
27 import javax.ws.rs.core.MediaType;
28 import javax.xml.namespace.QName;
29 import javax.xml.parsers.DocumentBuilder;
30 import javax.xml.parsers.DocumentBuilderFactory;
31 import java.net.URL;
32 import java.util.HashMap;
33 import java.util.List;
34
35 import static com.atlassian.annotations.tenancy.TenancyScope.TENANTLESS;
36 import static com.atlassian.plugins.rest.doclet.generators.resourcedoc.JsonOperations.toJson;
37 import static com.atlassian.plugins.rest.doclet.generators.resourcedoc.RestMethod.restMethod;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 public class AtlassianWadlGeneratorResourceDocSupport extends WadlGeneratorResourceDocSupport {
53 @TenantAware(TENANTLESS)
54 private HashMap<String, ResourcePathInformation> resourcePathInformation;
55
56 private static final Logger LOG = LoggerFactory.getLogger(AtlassianWadlGeneratorResourceDocSupport.class);
57 private static final String ATLASSIAN_PLUGIN_XML = "atlassian-plugin.xml";
58 private boolean generateSchemas = true;
59
60 public AtlassianWadlGeneratorResourceDocSupport() {
61 super();
62 }
63
64 public AtlassianWadlGeneratorResourceDocSupport(WadlGenerator wadlGenerator, ResourceDocType resourceDoc) {
65 super(wadlGenerator, resourceDoc);
66 }
67
68 @Override
69 public void init() throws Exception {
70 super.init();
71 parseAtlassianPluginXML();
72 }
73
74 public void setGenerateSchemas(Boolean generateSchemas) {
75 this.generateSchemas = generateSchemas;
76 }
77
78 private void parseAtlassianPluginXML() {
79
80 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
81
82 try {
83 final URL resource = getClass().getClassLoader().getResource(ATLASSIAN_PLUGIN_XML);
84 if (resource == null) {
85 return;
86 }
87 LOG.info("Found " + ATLASSIAN_PLUGIN_XML + " file! Looking for rest plugin module descriptors...");
88
89 DocumentBuilder db = dbf.newDocumentBuilder();
90
91 resourcePathInformation = new HashMap<String, ResourcePathInformation>();
92
93 final Document document = db.parse(resource.toExternalForm());
94 final NodeList restPluginModuleDescriptors = document.getElementsByTagName("rest");
95 final int numPluginModuleDescriptors = restPluginModuleDescriptors.getLength();
96 LOG.info("Found " + numPluginModuleDescriptors + " rest plugin module descriptors.");
97
98 for (int i = 0; i < numPluginModuleDescriptors; i++) {
99 final Node node = restPluginModuleDescriptors.item(i);
100
101 final NamedNodeMap attributes = node.getAttributes();
102 final Node pathItem = attributes.getNamedItem("path");
103 final Node versionItem = attributes.getNamedItem("version");
104 if (pathItem == null || versionItem == null) {
105 continue;
106 }
107
108 String resourcePath = pathItem.getNodeValue();
109 String version = versionItem.getNodeValue();
110
111 LOG.info("Found rest end point with path '" + resourcePath + "' and version '" + version + "'");
112
113
114 if (resourcePath.indexOf("/") != -1) {
115 resourcePath = resourcePath.substring(resourcePath.indexOf("/") + 1);
116 }
117
118 final NodeList list = node.getChildNodes();
119 for (int j = 0; j < list.getLength(); j++) {
120 final Node child = list.item(j);
121 if (child.getNodeName().equals("package")) {
122 final String packageName = child.getFirstChild().getNodeValue();
123 LOG.info("Map package '" + packageName + "' to resource path '" + resourcePath + "' and version '" + version + "'");
124 resourcePathInformation.put(packageName, new ResourcePathInformation(resourcePath, version));
125 }
126 }
127 }
128 } catch (Exception ex) {
129 LOG.error("Failed to read " + ATLASSIAN_PLUGIN_XML + " and parse rest plugin module descriptor information. Reason", ex);
130 }
131 }
132
133 @Override
134 public Resource createResource(AbstractResource r, String path) {
135 final Resource result = super.createResource(r, path);
136 boolean resourcePathChanged = false;
137 for (String packageName : resourcePathInformation.keySet()) {
138 if (r.getResourceClass().getPackage().getName().startsWith(packageName)) {
139 final ResourcePathInformation pathInformation = resourcePathInformation.get(packageName);
140 final String newPath = buildResourcePath(result, pathInformation);
141 result.setPath(newPath);
142 resourcePathChanged = true;
143 LOG.info("Setting resource path of rest end point '" + r.getResourceClass().getCanonicalName() + "' to '" + newPath + "'");
144 break;
145 }
146 }
147 if (!resourcePathChanged) {
148 LOG.info("Resource path of rest end point '" + r.getResourceClass().getCanonicalName() + "' unchanged no mapping to rest plugin module descriptor found.");
149 }
150
151 return result;
152 }
153
154 private String buildResourcePath(Resource result, ResourcePathInformation pathInformation) {
155 if (ApiVersion.isNone(pathInformation.getVersion())) {
156 return pathInformation.getPath() + "/" + result.getPath();
157 } else {
158 return pathInformation.getPath() + "/" + pathInformation.getVersion() + "/" + result.getPath();
159 }
160 }
161
162 @Override
163 public Method createMethod(final AbstractResource r, final AbstractResourceMethod m) {
164 final Method method = super.createMethod(r, m);
165 if (restMethod(r.getResourceClass(), m.getMethod()).isExperimental()) {
166 method.getOtherAttributes().put(new QName("experimental"), Boolean.TRUE.toString());
167 }
168 if (m.getMethod().getAnnotation(Deprecated.class) != null) {
169 method.getOtherAttributes().put(new QName("deprecated"), Boolean.TRUE.toString());
170 }
171 return method;
172 }
173
174 @Override
175 public Representation createRequestRepresentation(final AbstractResource r, final AbstractResourceMethod m, final MediaType mediaType) {
176 final Representation representation = super.createRequestRepresentation(r, m, mediaType);
177 if (generateSchemas) {
178 restMethod(r.getResourceClass(), m.getMethod()).getRequestType().ifPresent(richClass ->
179 representation.getDoc().add(schemaDoc(richClass, RestProperty.Scope.REQUEST)));
180 }
181
182 return representation;
183 }
184
185 @Override
186 public List<Response> createResponses(final AbstractResource r, final AbstractResourceMethod m) {
187 List<Response> result = Lists.newArrayList();
188 for (Response response : super.createResponses(r, m)) {
189 if (generateSchemas) {
190 addSchemaIfDefinedForStatus(r, m, response);
191 }
192 result.add(response);
193 }
194
195 return result;
196 }
197
198 private void addSchemaIfDefinedForStatus(AbstractResource resource, final AbstractResourceMethod method, final Response response) {
199 for (Long status : response.getStatus()) {
200 for (RichClass responseType : restMethod(resource.getResourceClass(), method.getMethod()).responseTypesFor(status.intValue())) {
201 for (Representation representation : response.getRepresentation()) {
202 representation.getDoc().add(schemaDoc(responseType, RestProperty.Scope.RESPONSE));
203 }
204 }
205 }
206 }
207
208 private Doc schemaDoc(RichClass model, final RestProperty.Scope scope) {
209 String schema = toJson(SchemaGenerator.generateSchema(model, scope));
210 final Doc doc = new Doc();
211
212 final Elements element = Elements.el("p")
213 .add(Elements.val("h6", "Schema"))
214 .add(Elements.el("pre").add(Elements.val("code", schema)));
215
216 doc.getContent().add(element);
217
218 return doc;
219 }
220
221 public class ResourcePathInformation {
222 private final String path;
223 private final String version;
224
225 public ResourcePathInformation(String path, String version) {
226 this.path = path;
227 this.version = version;
228 }
229
230 public String getVersion() {
231 return version;
232 }
233
234 public String getPath() {
235 return path;
236 }
237 }
238 }