View Javadoc

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