View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.trinidad.resource;
20  
21  import java.io.InputStream;
22  import java.io.SequenceInputStream;
23  import java.net.URL;
24  
25  import java.net.URLConnection;
26  import java.net.URLStreamHandler;
27  import java.util.ArrayList;
28  
29  import java.io.IOException;
30  import java.util.Enumeration;
31  import java.util.NoSuchElementException;
32  
33  import org.apache.myfaces.trinidad.logging.TrinidadLogger;
34  import org.apache.myfaces.trinidad.util.URLUtils;
35  
36  /**
37   * A resource loader implementation which combines multiple resources
38   * into a single stream.  This version leverages the DynamicResourceLoader.
39   *
40   */
41  public class AggregatingResourceLoader extends DynamicResourceLoader
42  {
43    /**
44     * Creates a new AggregatingResourceLoader.
45     *
46     * @param path    the aggregated resource path
47     * @param paths   the target resource paths to aggregate
48     * @param target  the resource loader use to find target resource paths
49     * @param parent  the parent resource loader
50     */
51    public AggregatingResourceLoader(
52      String         path,
53      String[]       paths,
54      ResourceLoader target,
55      ResourceLoader parent)
56    {
57      super(path, parent);
58      if (paths == null)
59        throw new NullPointerException();
60  
61      if (target == null)
62        throw new NullPointerException();
63  
64      _paths = paths.clone();
65      _target = target;
66    }
67  
68    /**
69     * Creates a new AggregatingResourceLoader.
70     *
71     * @param path    the aggregated resource path
72     * @param paths   the target resource paths to aggregate
73     * @param target  the resource loader use to find target resource paths
74     */
75    public AggregatingResourceLoader(
76      String         path,
77      String[]       paths,
78      ResourceLoader target)
79    {
80      this(path, paths, target, null);
81    }
82  
83    /**
84     * Sets the separator to use in between streams.  This will typically contain a newline character.
85     * By default the value is <code>null</code> which implies no separator.
86     *
87     * @param separator a string containing the separator characters
88     */
89    public void setSeparator(String separator)
90    {
91      _separator = separator;
92    }
93  
94    /**
95     * Returns a URL which is an aggregate of all the paths.
96     *
97     * @param path the current path
98     * @return a aggregate url
99     * @throws IOException when something bad happens
100    */
101   @Override
102   protected URL getURL(String path) throws IOException
103   {
104     int len = _paths.length;
105     ArrayList<URL> urls = new ArrayList<URL>(len);
106     for(int i = 0; i < len; i++)
107     {
108       URL u = _target.getResource(_paths[i]);
109       if(u != null)
110       {
111         urls.add(u);
112       }
113       else
114       {
115         _LOG.warning("RESOURCE_NOT_FOUND", new Object[]{_paths[i], path});
116       }
117     }
118 
119     urls.trimToSize();
120     URL[] urlArray = urls.toArray(new URL[urls.size()]);
121 
122     AggregatingURLStreamHandler handler = new AggregatingURLStreamHandler(urlArray, _separator);
123     return new URL("aggregating", null, -1, path, handler);
124   }
125 
126   private String[] _paths;
127   private ResourceLoader _target;
128   private String _separator;
129   static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(AggregatingResourceLoader.class);
130 
131   /**
132    * This is a Stream Handler which can be used to construct a URL that is an Aggregate of a list of
133    * other urls.
134    *
135    */
136   public class AggregatingURLStreamHandler extends URLStreamHandler
137   {
138     /**
139      * Constructs and AggregatingURLStreamHandler from an array of URLs containing other data.
140      * This constructor assumes a null separator.
141      *
142      * @param urls the urls
143      */
144     public AggregatingURLStreamHandler(URL[] urls)
145     {
146       this(urls, null);
147     }
148 
149     /**
150      * Constructs and AggregatingURLStreamHandler from an array of URLs containing other data.
151      *
152      * @param urls the urls
153      * @param separator a String containing a separator.  This will typically be an newline character
154      *                  or null.
155      */
156     public AggregatingURLStreamHandler(URL[] urls, String separator)
157     {
158       if(urls == null)
159       {
160         throw new NullPointerException();
161       }
162       _urls = urls.clone();
163       _separator = separator;
164     }
165 
166     /**
167      * Opens a connection containing all of the data from the provided urls.  The seperator character,
168      * if one is provided, will seperate the content of each seperate stream.
169      *
170      * @param u the parent URL object
171      * @return a URLConnection
172      * @throws IOException when something bad happens
173      */
174     @Override
175     protected URLConnection openConnection(URL u) throws IOException
176     {
177       int len = _urls.length;
178       URLConnection[] conn = new URLConnection[len];
179       for(int i = 0; i < len; i++)
180       {
181         conn[i] = _urls[i].openConnection();
182       }
183 
184       return new AggregatingURLConnection(u, conn, _separator);
185     }
186 
187     private URL[] _urls;
188     private String _separator;
189 
190   }
191 
192   static private class AggregatingURLConnection extends URLConnection
193   {
194     public AggregatingURLConnection(URL url, URLConnection[] connections, String separator)
195     {
196       super(url);
197       _connections = connections;
198 
199       if(separator != null)
200       {
201         _separator = separator.getBytes();
202       }
203     }
204 
205     @Override
206     public void connect() throws IOException
207     {
208       for (int i=0, len = _connections.length; i < len; i++)
209       {
210         _connections[i].connect();
211       }
212     }
213 
214     @Override
215     public InputStream getInputStream() throws IOException
216     {
217       boolean hasseparator = (_separator!=null);
218       InputStream[] streams;
219       if(hasseparator)
220       {
221         streams = new InputStream[(_connections.length *2)-1];
222       }
223       else
224       {
225         streams = new InputStream[_connections.length];
226       }
227 
228       for (int i=0, len=_connections.length, sublen = len -1, streamCounter = 0; i < len; i++, streamCounter++)
229       {
230         streams[streamCounter] = _connections[i].getInputStream();
231 
232         //Add the separator if needed
233         if(hasseparator && (i < sublen))
234         {
235           streams[++streamCounter] = new SeparatorInputStream(_separator);
236         }
237       }
238       return new SequenceInputStream(new ArrayEnumeration<InputStream>(streams));
239     }
240 
241     @Override
242     public String getContentType()
243     {
244       return _connections[0].getContentType();
245     }
246 
247     @Override
248     public int getContentLength()
249     {
250       int totalContentLength = _contentLength;
251 
252       // Calculate the total content length
253       // If any piece is unknown in length, then the total is unknown also
254       if (totalContentLength == Integer.MIN_VALUE)
255       {
256         totalContentLength = 0;
257 
258         URLConnection[] connects = _connections;
259 
260         // Ensure that separator calculation happens first
261         // to avoid adding extra length in the case when
262         // the total content length is unknown.
263         if (_separator != null)
264         {
265           totalContentLength += ((connects.length - 1)*_separator.length);
266         }
267 
268         for (int i=0, len = _connections.length; i < len; i++)
269         {
270           int contentLength = _connections[i].getContentLength();
271           if (contentLength < 0)
272           {
273             // Unknown content length for one part implies
274             // unknown content length for aggregated whole
275             totalContentLength = -1;
276             break;
277           }
278           totalContentLength += contentLength;
279         }
280 
281         _contentLength = totalContentLength;
282       }
283 
284       return totalContentLength;
285     }
286 
287     @Override
288     public long getLastModified()
289     {
290       long maxLastModified = -1;
291 
292       for (int i=0, len = _connections.length; i < len; i++)
293       {
294         long lastModified;
295         try
296         {
297           lastModified = URLUtils.getLastModified(_connections[i]);
298         }
299         catch (IOException exception)
300         {
301           maxLastModified = -1;
302           break;
303         }
304 
305         if (lastModified < 0)
306         {
307           maxLastModified = lastModified;
308           break;
309         }
310         maxLastModified = Math.max(maxLastModified, lastModified);
311       }
312 
313       return maxLastModified;
314     }
315 
316     @Override
317     public String getHeaderField(
318       String name)
319     {
320       if ("content-length".equals(name))
321       {
322         return String.valueOf(getContentLength());
323       }
324       else if ("content-type".equals(name))
325       {
326         return getContentType();
327       }
328       else if ("last-modified".equals(name))
329       {
330         return String.valueOf(getLastModified());
331       }
332       else
333       {
334         return super.getHeaderField(name);
335       }
336     }
337 
338     private int _contentLength = Integer.MIN_VALUE;
339     private URLConnection[] _connections;
340     private byte[] _separator;
341 
342   }
343 
344   static private class ArrayEnumeration<T> implements Enumeration<T>
345   {
346     public ArrayEnumeration(T[] array)
347     {
348       _array = array;
349       _len = array.length;
350     }
351 
352     public boolean hasNext()
353     {
354       return _pointer < _len;
355     }
356 
357     public T nextElement() throws NoSuchElementException
358     {
359       try
360       {
361         return _array[_pointer++];
362       }
363       catch (IndexOutOfBoundsException e)
364       {
365         throw new NoSuchElementException();
366       }
367     }
368 
369     public boolean hasMoreElements()
370     {
371       return hasNext();
372     }
373 
374     private T[] _array;
375     private int _len;
376     private int _pointer = 0;
377   }
378 
379   static private class SeparatorInputStream extends InputStream
380   {
381     public SeparatorInputStream (byte[] separatorBytes)
382     {
383       _separator = separatorBytes;
384       _length = _separator.length;
385     }
386 
387     @Override
388     public int read() throws IOException
389     {
390       if(_index < _length)
391       {
392         return _separator[_index++];
393       }
394 
395       return -1;
396     }
397 
398     @Override
399     public int read(byte[] b, int off, int len) throws IOException
400     {
401       int bytesLeft = available();
402 
403       if(len <= bytesLeft)
404       {
405         System.arraycopy(_separator,_index,b,off,len);
406         _index += len;
407         return len;
408       }
409       else
410       {
411         System.arraycopy(_separator, _index, b,off, bytesLeft);
412         _index += bytesLeft;
413         return bytesLeft;
414       }
415     }
416 
417     @Override
418     public long skip(long n) throws IOException
419     {
420       int bytesLeft = available();
421 
422       if(n < 0)
423       {
424         return 0;
425       }
426       else if(n < bytesLeft || n == bytesLeft)
427       {
428         _index += n;
429         return n;
430       }
431       else
432       {
433         _index += bytesLeft;
434         return bytesLeft;
435       }
436     }
437 
438     @Override
439     public int available() throws IOException
440     {
441       return _length - _index;
442     }
443 
444     private byte[] _separator;
445     private int    _length;
446     private int    _index = 0;
447   }
448 }