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