View Javadoc

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   * This class generates the WADL description of rest resources and considers the rest plugin module descriptors
39   * configured inside the atlassian-plugin.xml file when generating the resource path.
40   * <p/>
41   * It builds up a map that contains a mapping of a package name to a resource path.
42   * The full resource path is concatenated of the following strings:
43   * 1) path as configured for rest plugin module descriptor: e.g. api
44   * 2) version as configured for rest plugin module descriptor e.g. 2.0.alpha1
45   * 3) path of the rest end point e.g. worklog
46   *
47   * <p/>
48   * e.g. /api/2.0.alpha1/worklog/
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          //get the factory
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                 //Remove leading slash
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 }