View Javadoc

1   package com.atlassian.plugin.webresource;
2   
3   import com.atlassian.plugin.servlet.DownloadException;
4   import com.atlassian.plugin.servlet.DownloadableResource;
5   import static com.atlassian.plugin.servlet.AbstractFileServerServlet.PATH_SEPARATOR;
6   import static com.atlassian.plugin.servlet.AbstractFileServerServlet.SERVLET_PATH;
7   
8   import javax.servlet.http.HttpServletRequest;
9   import javax.servlet.http.HttpServletResponse;
10  import java.util.Map;
11  import java.util.List;
12  import java.util.ArrayList;
13  import java.util.Collections;
14  
15  import org.apache.commons.logging.Log;
16  import org.apache.commons.logging.LogFactory;
17  
18  /**
19   * Represents a batch of plugin resources. <p/>
20   *
21   * It provides methods to parse and generate urls to locate a batch of plugin resources. <p/>
22   *
23   * Note BatchPluginResource is also a type of {@link DownloadableResource}. The underlying implementation simply
24   * keeps a list of {@link DownloadableResource} of which this batch represents and delegates method calls.
25   * @since 2.2
26   */
27  public class BatchPluginResource implements DownloadableResource, PluginResource
28  {
29      private static final Log log = LogFactory.getLog(BatchPluginResource.class);
30  
31      /**
32       * The url prefix for a batch of plugin resources: "/download/batch/"
33       */
34      static final String URL_PREFIX = PATH_SEPARATOR + SERVLET_PATH + PATH_SEPARATOR + "batch";
35  
36      final private String type;
37      final private String moduleCompleteKey;
38      final private Map<String, String> params;
39      final private String resourceName;
40      final private List<DownloadableResource> resources;
41  
42      /**
43       * A constructor that creates a default resource name for the batch in the format: moduleCompleteKey.type
44       * For example: test.plugin:resources.js
45       * <p/>
46       * Note that name of the batch does not identify what the batch includes and could have been static e.g. batch.js
47       */
48      public BatchPluginResource(String moduleCompleteKey, String type, Map<String, String> params)
49      {
50          this(moduleCompleteKey + "." + type, moduleCompleteKey, type, params);
51      }
52  
53      /**
54       * This constructor should only ever be used internally within this class. It does not ensure that the resourceName's
55       * file extension is the same as the given type. It is up to the calling code to ensure this.
56       */
57      private BatchPluginResource(String resourceName, String moduleCompleteKey, String type, Map<String, String> params)
58      {
59          this.resourceName = resourceName;
60          this.moduleCompleteKey = moduleCompleteKey;
61          this.type = type;
62          this.params = params;
63          this.resources = new ArrayList<DownloadableResource>();
64      }
65  
66      /**
67       * @return true if there are no resources included in this batch
68       */
69      public boolean isEmpty()
70      {
71          return resources.isEmpty();
72      }
73  
74      public void add(DownloadableResource resource)
75      {
76          resources.add(resource);
77      }
78  
79      public boolean isResourceModified(HttpServletRequest request, HttpServletResponse response)
80      {
81          for (DownloadableResource resource : resources)
82          {
83              if (resource.isResourceModified(request, response))
84                  return true;
85          }
86          return false;
87      }
88  
89      public void serveResource(HttpServletRequest request, HttpServletResponse response) throws DownloadException
90      {
91          log.info("Start to serve batch " + toString());
92          for (DownloadableResource resource : resources)
93          {
94              resource.serveResource(request, response);
95          }
96      }
97  
98      public String getContentType()
99      {
100         String contentType = params.get("content-type");
101         if (contentType != null)
102             return contentType;
103         
104         return null;
105     }
106 
107     /**
108      * Parses the given url and query parameter map into a BatchPluginResource. Query paramters must be
109      * passed in through the map, any in the url String will be ignored.
110      * @param url the url to parse
111      * @param queryParams a map of String key and value pairs representing the query parameters in the url
112      */
113     public static BatchPluginResource parse(String url, Map<String, String> queryParams)
114     {
115         int startIndex = url.indexOf(URL_PREFIX) + URL_PREFIX.length() + 1;
116 
117         if (url.indexOf('?') != -1) // remove query parameters
118         {
119             url = url.substring(0, url.indexOf('?'));
120         }
121 
122         String typeAndModuleKey = url.substring(startIndex);
123         String[] parts = typeAndModuleKey.split("/", 2);
124 
125         if (parts.length < 2)
126             return null;
127 
128         String moduleKey = parts[0];
129         String resourceName = parts[1];
130         String type = resourceName.substring(resourceName.lastIndexOf('.') + 1);
131 
132         return new BatchPluginResource(resourceName, moduleKey, type, queryParams);
133     }
134 
135     public static boolean matches(String url)
136     {
137         return url.indexOf(URL_PREFIX) != -1;
138     }
139 
140     /**
141      * Returns a url string in the format: /download/batch/MODULE_COMPLETE_KEY/resourceName?PARAMS
142      *
143      * e.g. /download/batch/example.plugin:webresources/example.plugin:webresources.css?ie=true
144      * <p/>
145      * It is important for the url structure to be:
146      * 1. the same number of sectioned paths as the SinglePluginResource
147      * 2. include the module completey key in the path before the resource name
148      * This is due to css resources referencing other resources such as images in relative path forms.
149      */
150     public String getUrl()
151     {
152         StringBuilder sb = new StringBuilder();
153         sb.append(URL_PREFIX).append(PATH_SEPARATOR)
154             .append(moduleCompleteKey).append(PATH_SEPARATOR)
155             .append(resourceName);
156 
157         if(params.size() > 0 )
158         {
159             sb.append("?");
160             int count = 0;
161 
162             for (Map.Entry<String, String> entry: params.entrySet())
163             {
164                 sb.append(entry.getKey()).append("=").append(entry.getValue());
165 
166                 if(++count < params.size())
167                     sb.append("&");
168             }
169         }
170 
171         return sb.toString();
172     }
173 
174     public String getResourceName()
175     {
176         return resourceName;
177     }
178 
179     public Map<String, String> getParams()
180     {
181         return Collections.unmodifiableMap(params);
182     }
183 
184     public String getModuleCompleteKey()
185     {
186         return moduleCompleteKey;
187     }
188 
189     public boolean isCacheSupported()
190     {
191         return !"false".equals(params.get("cache"));
192     }
193 
194     public String getType()
195     {
196         return type;
197     }
198 
199     public boolean equals(Object o)
200     {
201         if (this == o) return true;
202         if (o == null || getClass() != o.getClass()) return false;
203 
204         BatchPluginResource that = (BatchPluginResource) o;
205 
206         if (moduleCompleteKey != null ? !moduleCompleteKey.equals(that.moduleCompleteKey) : that.moduleCompleteKey != null)
207             return false;
208         if (params != null ? !params.equals(that.params) : that.params != null) return false;
209         if (type != null ? !type.equals(that.type) : that.type != null) return false;
210 
211         return true;
212     }
213 
214     public int hashCode()
215     {
216         int result;
217         result = (type != null ? type.hashCode() : 0);
218         result = 31 * result + (moduleCompleteKey != null ? moduleCompleteKey.hashCode() : 0);
219         result = 31 * result + (params != null ? params.hashCode() : 0);
220         return result;
221     }
222 
223     public String toString()
224     {
225         return "[moduleCompleteKey=" + moduleCompleteKey + ", type=" + type + ", params=" + params + "]";
226     }
227 }