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