All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.myfaces.trinidad.resource.AggregatingResourceLoader Maven / Gradle / Ivy

There is a newer version: 2.2.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.myfaces.trinidad.resource;

import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;

import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.NoSuchElementException;

import org.apache.myfaces.trinidad.logging.TrinidadLogger;
import org.apache.myfaces.trinidad.util.URLUtils;

/**
 * A resource loader implementation which combines multiple resources
 * into a single stream.  This version leverages the DynamicResourceLoader.
 *
 */
public class AggregatingResourceLoader extends DynamicResourceLoader
{
  /**
   * Creates a new AggregatingResourceLoader.
   *
   * @param path    the aggregated resource path
   * @param paths   the target resource paths to aggregate
   * @param target  the resource loader use to find target resource paths
   * @param parent  the parent resource loader
   */
  public AggregatingResourceLoader(
    String         path,
    String[]       paths,
    ResourceLoader target,
    ResourceLoader parent)
  {
    super(path, parent);
    
    if ((paths == null) || (paths.length == 0))
      throw new IllegalArgumentException("No paths specified");

    if (target == null)
      throw new NullPointerException();

    _paths = paths.clone();
    _target = target;
  }

  /**
   * Creates a new AggregatingResourceLoader.
   *
   * @param path    the aggregated resource path
   * @param paths   the target resource paths to aggregate
   * @param target  the resource loader use to find target resource paths
   */
  public AggregatingResourceLoader(
    String         path,
    String[]       paths,
    ResourceLoader target)
  {
    this(path, paths, target, null);
  }

  /**
   * Sets the separator to use in between streams.  This will typically contain a newline character.
   * By default the value is null which implies no separator.
   *
   * @param separator a string containing the separator characters
   */
  public void setSeparator(String separator)
  {
    _separator = separator;
  }

  /**
   * Returns a URL which is an aggregate of all the paths.
   *
   * @param path the current path
   * @return a aggregate url
   * @throws IOException when something bad happens
   */
  @Override
  protected URL getURL(String path) throws IOException
  {
    int len = _paths.length;
    ArrayList urls = new ArrayList(len);
    for(int i = 0; i < len; i++)
    {
      URL u = _target.getResource(_paths[i]);
      if(u != null)
      {
        urls.add(u);
      }
      else
      {
        _LOG.warning("RESOURCE_NOT_FOUND", new Object[]{_paths[i], path});
      }
    }

    if (!urls.isEmpty())
    {
      urls.trimToSize();
      URL[] urlArray = urls.toArray(new URL[urls.size()]);

      AggregatingURLStreamHandler handler = new AggregatingURLStreamHandler(urlArray, _separator);
      return new URL("aggregating", null, -1, path, handler);
    }
    else
    {
      // none of the paths worked, throw a meaninful exception
      throw new IOException("Could not find any of:" + Arrays.toString(_paths));
    }
  }
  
  private final String[] _paths;
  private final ResourceLoader _target;
  private volatile String  _separator;
  
  private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(AggregatingResourceLoader.class);

  /**
   * This is a Stream Handler which can be used to construct a URL that is an Aggregate of a list of
   * other urls.
   *
   */
  public class AggregatingURLStreamHandler extends URLStreamHandler
  {
    /**
     * Constructs and AggregatingURLStreamHandler from an array of URLs containing other data.
     * This constructor assumes a null separator.
     *
     * @param urls the urls
     */
    public AggregatingURLStreamHandler(URL[] urls)
    {
      this(urls, null);
    }

    /**
     * Constructs and AggregatingURLStreamHandler from an array of URLs containing other data.
     *
     * @param urls the urls
     * @param separator a String containing a separator.  This will typically be an newline character
     *                  or null.
     */
    public AggregatingURLStreamHandler(URL[] urls, String separator)
    {
      if ((urls == null) || (urls.length == 0))
      {
        throw new IllegalArgumentException("No URLS specified");
      }
      
      _urls = urls.clone();
      _separator = separator;
    }

    /**
     * Opens a connection containing all of the data from the provided urls.  The seperator character,
     * if one is provided, will seperate the content of each seperate stream.
     *
     * @param u the parent URL object
     * @return a URLConnection
     * @throws IOException when something bad happens
     */
    @Override
    protected URLConnection openConnection(URL u) throws IOException
    {
      int len = _urls.length;
      URLConnection[] conn = new URLConnection[len];
      for(int i = 0; i < len; i++)
      {
        conn[i] = _urls[i].openConnection();
      }

      return new AggregatingURLConnection(u, conn, _separator);
    }

    private final URL[]  _urls;
    private final String _separator;

  }

  static private class AggregatingURLConnection extends URLConnection
  {
    public AggregatingURLConnection(URL url, URLConnection[] connections, String separator)
    {
      super(url);
      
      if ((connections == null) || (connections.length == 0))
        throw new IllegalArgumentException("No connections specified");
      
      _connections = connections;
      _separator = (separator != null) ? separator.getBytes() : null;
    }

    @Override
    public void connect() throws IOException
    {
      for (int i=0, len = _connections.length; i < len; i++)
      {
        _connections[i].connect();
      }
    }

    @Override
    public InputStream getInputStream() throws IOException
    {
      boolean hasseparator = (_separator!=null);
      InputStream[] streams;
      if(hasseparator)
      {
        streams = new InputStream[(_connections.length *2)-1];
      }
      else
      {
        streams = new InputStream[_connections.length];
      }

      for (int i=0, len=_connections.length, sublen = len -1, streamCounter = 0; i < len; i++, streamCounter++)
      {
        streams[streamCounter] = _connections[i].getInputStream();

        //Add the separator if needed
        if(hasseparator && (i < sublen))
        {
          streams[++streamCounter] = new SeparatorInputStream(_separator);
        }
      }
      return new SequenceInputStream(new ArrayEnumeration(streams));
    }

    @Override
    public String getContentType()
    {
      return _connections[0].getContentType();
    }

    @Override
    public int getContentLength()
    {
      int totalContentLength = _contentLength;

      // Calculate the total content length
      // If any piece is unknown in length, then the total is unknown also
      if (totalContentLength == Integer.MIN_VALUE)
      {
        totalContentLength = 0;

        URLConnection[] connects = _connections;

        // Ensure that separator calculation happens first
        // to avoid adding extra length in the case when
        // the total content length is unknown.
        if (_separator != null)
        {
          totalContentLength += ((connects.length - 1)*_separator.length);
        }

        for (int i=0, len = _connections.length; i < len; i++)
        {
          int contentLength = _connections[i].getContentLength();
          if (contentLength < 0)
          {
            // Unknown content length for one part implies
            // unknown content length for aggregated whole
            totalContentLength = -1;
            break;
          }
          totalContentLength += contentLength;
        }

        _contentLength = totalContentLength;
      }

      return totalContentLength;
    }

    @Override
    public long getLastModified()
    {
      long maxLastModified = -1;

      for (int i=0, len = _connections.length; i < len; i++)
      {
        long lastModified;
        try
        {
          lastModified = URLUtils.getLastModified(_connections[i]);
        }
        catch (IOException exception)
        {
          maxLastModified = -1;
          break;
        }

        if (lastModified < 0)
        {
          maxLastModified = lastModified;
          break;
        }
        maxLastModified = Math.max(maxLastModified, lastModified);
      }

      return maxLastModified;
    }

    @Override
    public String getHeaderField(
      String name)
    {
      if ("content-length".equals(name))
      {
        return String.valueOf(getContentLength());
      }
      else if ("content-type".equals(name))
      {
        return getContentType();
      }
      else if ("last-modified".equals(name))
      {
        return String.valueOf(getLastModified());
      }
      else
      {
        return super.getHeaderField(name);
      }
    }

    private volatile int _contentLength = Integer.MIN_VALUE;
    private final URLConnection[] _connections;
    private final byte[] _separator;

  }

  static private class ArrayEnumeration implements Enumeration
  {
    public ArrayEnumeration(T[] array)
    {
      if (array == null)
        throw new NullPointerException();
      
      _array = array;
      _len = array.length;
    }

    public boolean hasNext()
    {
      return _pointer < _len;
    }

    public T nextElement() throws NoSuchElementException
    {
      try
      {
        return _array[_pointer++];
      }
      catch (IndexOutOfBoundsException e)
      {
        throw new NoSuchElementException();
      }
    }

    public boolean hasMoreElements()
    {
      return hasNext();
    }

    private final T[] _array;
    private final int _len;
    private volatile int _pointer = 0;
  }

  static private class SeparatorInputStream extends InputStream
  {
    public SeparatorInputStream (byte[] separatorBytes)
    {
      _separator = separatorBytes;
      _length = _separator.length;
    }

    @Override
    public int read() throws IOException
    {
      if(_index < _length)
      {
        return _separator[_index++];
      }

      return -1;
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException
    {
      int bytesLeft = available();

      if(len <= bytesLeft)
      {
        System.arraycopy(_separator,_index,b,off,len);
        _index += len;
        return len;
      }
      else
      {
        System.arraycopy(_separator, _index, b,off, bytesLeft);
        _index += bytesLeft;
        return bytesLeft;
      }
    }

    @Override
    public long skip(long n) throws IOException
    {
      int bytesLeft = available();

      if(n < 0)
      {
        return 0;
      }
      else if(n < bytesLeft || n == bytesLeft)
      {
        _index += n;
        return n;
      }
      else
      {
        _index += bytesLeft;
        return bytesLeft;
      }
    }

    @Override
    public int available() throws IOException
    {
      return _length - _index;
    }

    private final    byte[] _separator;
    private final    int    _length;
    private volatile int    _index = 0;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy