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