1   package com.atlassian.plugins.rest.module;
2   
3   import com.atlassian.plugin.ModuleDescriptor;
4   import com.atlassian.plugin.Plugin;
5   import com.atlassian.plugin.PluginManager;
6   import com.atlassian.plugin.PluginParseException;
7   import com.atlassian.plugin.descriptors.AbstractModuleDescriptor;
8   import com.atlassian.plugin.hostcontainer.HostContainer;
9   import com.atlassian.plugin.module.ModuleFactory;
10  import com.atlassian.plugin.osgi.factory.OsgiPlugin;
11  import com.atlassian.plugin.servlet.filter.FilterLocation;
12  import com.atlassian.plugins.rest.module.servlet.RestServletModuleManager;
13  import com.google.common.base.Preconditions;
14  import org.dom4j.Element;
15  import org.osgi.framework.ServiceRegistration;
16  
17  import java.util.Collection;
18  import java.util.HashSet;
19  import java.util.Set;
20  
21  /**
22   * <p>The REST module descriptor.</p>
23   * <p>Example configuration in your {@link PluginManager#PLUGIN_DESCRIPTOR_FILENAME plugin descriptor}:</p>
24   * &lt;rest key="module-key"&gt;<br/>
25   * &nbsp;&nbsp;&nbsp;&nbsp;&lt;url-pattern&gt;/myapi/*&lt;/url-pattern&gt;<br/>
26   * &lt;/rest&gt;<br/>
27   * <p>Your REST apis will then be available at <code>/context/rest/myapi</code></p>
28   */
29  public class RestModuleDescriptor extends AbstractModuleDescriptor<Object>
30  {
31      private final RestServletModuleManager servletModuleManager;
32      /**
33       * <p>This is the context path of REST APIs.</p>
34       * <p>Typically if the application lives at {@code http://localhost:9090/app} and the REST context path is {@code /rest}, then APIs will be available at {@code http://localhost:9090/app/rest}</p>
35       */
36      private final String restContext;
37  
38      private RestApiContext restApiContext;
39  
40      private ServiceRegistration serviceRegistration;
41      private RestServletFilterModuleDescriptor restServletFilterModuleDescriptor;
42      private final ModuleFactory moduleFactory;
43      private OsgiPlugin osgiPlugin;
44      private Element element;
45  
46      public RestModuleDescriptor(ModuleFactory moduleFactory, RestServletModuleManager servletModuleManager, String restContext)
47      {
48          this.moduleFactory = moduleFactory;
49          this.servletModuleManager = Preconditions.checkNotNull(servletModuleManager);
50          this.restContext = Preconditions.checkNotNull(restContext);
51      }
52  
53      @Override
54      public void init(Plugin plugin, Element element) throws PluginParseException
55      {
56          super.init(plugin, element);
57  
58          this.restApiContext = new RestApiContext(restContext, parsePath(element), parseVersion(element), parsePackages(element));
59  
60          osgiPlugin = (OsgiPlugin) plugin;
61          this.element = element;
62      }
63  
64      private String parsePath(Element element)
65      {
66          return element.attributeValue("path");
67      }
68  
69      private Set<String> parsePackages(Element rootElement)
70      {
71          Set<String> packages = new HashSet<String>();
72          for (Element pkgElement : (Collection<Element>) rootElement.elements("package"))
73          {
74              packages.add(pkgElement.getTextTrim());
75          }
76          return packages;
77      }
78  
79      private ApiVersion parseVersion(Element element)
80      {
81          try
82          {
83              return new ApiVersion(element.attributeValue("version"));
84          }
85          catch (InvalidVersionException e)
86          {
87              // rethrowing the exception with more information
88              throw new InvalidVersionException(plugin, this, e);
89          }
90      }
91  
92      private Element updateElementForFilterConfiguration(final Element element)
93      {
94          final Element copy = element.createCopy();
95  
96          // adding the default location
97          copy.addAttribute("location", FilterLocation.BEFORE_DISPATCH.name());
98  
99          // adding the url-pattern
100         copy.addElement("url-pattern").addText(restApiContext.getApiPath() + RestApiContext.SLASH + restApiContext.getVersion() + RestApiContext.ANY_PATH_PATTERN);
101 
102         copy.addAttribute("key", copy.attributeValue("key") + "-filter");
103         return copy;
104     }
105 
106     /**
107      * @return <code>null</code>, the REST module descriptor doesn't instansiate any module.
108      */
109     public Object getModule()
110     {
111         return null;
112     }
113 
114     @Override
115     public boolean equals(Object o)
116     {
117         if (o == null)
118         {
119             return false;
120         }
121         if (this == o)
122         {
123             return true;
124         }
125         if (this.getClass() != o.getClass())
126         {
127             return false;
128         }
129 
130         final RestModuleDescriptor that = (RestModuleDescriptor) o;
131         return that.getCompleteKey().equals(getCompleteKey());
132     }
133 
134     @Override
135     public int hashCode()
136     {
137         return getCompleteKey().hashCode();
138     }
139 
140     @Override
141     public void disabled()
142     {
143         if (restServletFilterModuleDescriptor != null)
144         {
145             restServletFilterModuleDescriptor.disabled();
146             restServletFilterModuleDescriptor = null;
147         }
148 
149         if (serviceRegistration != null)
150         {
151             try
152             {
153                 serviceRegistration.unregister();
154             }
155             catch (IllegalStateException ex)
156             {
157                 // this has
158             }
159             serviceRegistration = null;
160         }
161 
162         super.disabled();
163     }
164 
165     @Override
166     public void enabled()
167     {
168         super.enabled();
169         restServletFilterModuleDescriptor = new RestServletFilterModuleDescriptor(osgiPlugin, moduleFactory, servletModuleManager, restApiContext);
170         restServletFilterModuleDescriptor.init(plugin, updateElementForFilterConfiguration(element));
171         restServletFilterModuleDescriptor.enabled();
172 
173         // dynamically register the servlet filter that serves the REST API requests
174         serviceRegistration = osgiPlugin.getBundle().getBundleContext().registerService(
175                 new String[]{
176                         restServletFilterModuleDescriptor.getClass().getName(),
177                         ModuleDescriptor.class.getName(),
178                 }, restServletFilterModuleDescriptor, null);
179     }
180 }