myfaces-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jak...@apache.org
Subject svn commit: r1079447 - in /myfaces/commons/branches/jsf_20: ./ myfaces-commons-resourcehandler/ myfaces-commons-resourcehandler/src/ myfaces-commons-resourcehandler/src/main/ myfaces-commons-resourcehandler/src/main/java/ myfaces-commons-resourcehandle...
Date Tue, 08 Mar 2011 17:26:35 GMT
Author: jakobk
Date: Tue Mar  8 17:26:34 2011
New Revision: 1079447

URL: http://svn.apache.org/viewvc?rev=1079447&view=rev
Log:
MFCOMMONS-29 Advanced JSF 2 ResourceHandler

Added:
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/   (with props)
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/pom.xml
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResource.java
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResourceHandler.java
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/ResourceUtils.java
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfig.java
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfigParser.java
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/commons/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/commons/resourcehandler/
    myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/commons/resourcehandler/ResourceUtilsTest.java
Modified:
    myfaces/commons/branches/jsf_20/pom.xml

Propchange: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Mar  8 17:26:34 2011
@@ -0,0 +1,12 @@
+target
+.classpath
+.project
+.wtpmodules
+*.ipr
+*.iml
+*.iws
+.settings
+maven-eclipse.xml
+.externalToolsBuilder
+.hg
+.hgignore

Added: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/pom.xml
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/pom.xml?rev=1079447&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/pom.xml (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/pom.xml Tue Mar  8 17:26:34 2011
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ * 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.myfaces.commons</groupId>
+        <artifactId>commons20</artifactId>
+        <version>1.0.2-SNAPSHOT</version>
+    </parent>
+    <artifactId>myfaces-resourcehandler20</artifactId>
+    <packaging>jar</packaging>
+    <name>Apache MyFaces Commons ResourceHandler</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- Install a "-sources.jar" file -->
+                <artifactId>maven-source-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>attach-source</id>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+    
+        <dependency>
+            <groupId>org.apache.myfaces.core</groupId>
+            <artifactId>myfaces-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.8.1</version>
+            <scope>test</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>org.apache.myfaces.test</groupId>
+            <artifactId>myfaces-test20</artifactId>
+            <scope>test</scope>
+        </dependency>
+        
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <!--
+                - Build and install into the repository some additional artifacts that we don't
+                - want to build during normal development because they take too long.
+            -->
+            <id>generate-assembly</id>
+            <activation>
+                <property>
+                    <name>performRelease</name>
+                    <value>true</value>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <!-- Install in the repository a "-javadoc.jar" file -->
+                        <artifactId>maven-javadoc-plugin</artifactId>
+                        <version>2.4</version>
+                        <executions>
+                            <execution>
+                                <id>attach-javadoc</id>
+                                <goals>
+                                    <goal>jar</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>

Added: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResource.java
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResource.java?rev=1079447&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResource.java (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResource.java Tue Mar  8 17:26:34 2011
@@ -0,0 +1,424 @@
+/*
+ * 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.commons.resourcehandler;
+
+import javax.faces.application.ProjectStage;
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.context.FacesContext;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * Resource implementation for AdvancedResourceHandler.
+ *
+ * @author Jakob Korherr
+ */
+public class AdvancedResource extends Resource
+{
+
+    private static final Logger logger = Logger.getLogger(AdvancedResource.class.getName());
+
+    /**
+     * Base directory for resources
+     */
+    public static final String BASE_DIR = "META-INF/resources/";
+
+    /**
+     * Subdir of the ServletContext tmp dir to store compressed resources.
+     */
+    private static final String COMPRESSION_BASE_DIR = "oam-resourcehandler-cache/";
+
+    /**
+     * Suffix for compressed files.
+     */
+    private static final String COMPRESSED_FILE_SUFFIX = ".gzip";
+
+    /**
+     * URL path separator between locale, library name and resource name.
+     */
+    public static final String PATH_SEPARATOR = "/$/";
+
+    /**
+     * Regex of PATH_SEPARATOR for String.split();
+     */
+    public static final String PATH_SEPARATOR_REGEX = "/\\$/";
+
+    /**
+     * Size of the byte array buffer.
+     */
+    private static final int BUFFER_SIZE = 2048;
+
+    /**
+     * Accept-Encoding HTTP header field.
+     */
+    private static final String ACCEPT_ENCODING_HEADER = "Accept-Encoding";
+
+    private String requestedLocalePrefix;
+    private boolean developmentStage;
+
+    public AdvancedResource(String resourceName,
+                            String libraryName,
+                            String contentType,
+                            String requestedLocalePrefix)
+    {
+        this.setResourceName(resourceName);
+        this.setLibraryName(libraryName);
+
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        developmentStage = facesContext.isProjectStage(ProjectStage.Development);
+
+        // handle contentType
+        if (contentType == null)
+        {
+            //Resolve contentType using ExternalContext.getMimeType
+            contentType = facesContext.getExternalContext().getMimeType(resourceName);
+        }
+        this.setContentType(contentType);
+
+        // handle localePrefix
+        if (requestedLocalePrefix != null)
+        {
+            this.requestedLocalePrefix = requestedLocalePrefix + "/";
+
+            // check if resource exists with locale prefix in path
+            if (!resourceExists())
+            {
+                // check for language only (if not already done)
+                int underscoreIndex = requestedLocalePrefix.indexOf('_');
+                if (underscoreIndex != -1)
+                {
+                    // only use "de" instead of "de_AT"
+                    String language = requestedLocalePrefix.substring(0, underscoreIndex);
+                    this.requestedLocalePrefix = language + "/";
+
+                    // check if resource exists with locale prefix in path
+                    if (!resourceExists())
+                    {
+                        // do not use the locale prefix
+                        this.requestedLocalePrefix = null;
+                    }
+                }
+                else
+                {
+                    // do not use the locale prefix
+                    this.requestedLocalePrefix = null;
+                }
+            }
+        }
+
+        // handle compression (only available if ProjectStage != Development).
+        if (!developmentStage && isCompressable() && !isCompressedVersionAvailable(facesContext))
+        {
+            try
+            {
+                // create compressed version
+                createCompressedVersion(facesContext);
+            }
+            catch (IOException ioe)
+            {
+                // we were not able to create the compressed version
+                logger.log(Level.WARNING, "Could not create compressed version of Resource " + this, ioe);
+            }
+        }
+    }
+
+    /**
+     * Returns true if this resource really exists.
+     *
+     * @return
+     */
+    public boolean resourceExists()
+    {
+        return getURL() != null;
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+
+        if (shouldServeCompressedVersion(facesContext))
+        {
+            return getCompressedInputStream(facesContext);
+        }
+
+        return getUncompressedInputStream();
+    }
+
+    @Override
+    public URL getURL()
+    {
+        return ResourceUtils.getContextClassLoader().getResource(BASE_DIR + getInternalResourcePath());
+    }
+
+    @Override
+    public String getRequestPath()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+
+        StringBuilder path = new StringBuilder();
+        path.append(ResourceUtils.getFacesServletPrefix(facesContext));
+        path.append(ResourceHandler.RESOURCE_IDENTIFIER);
+        path.append("/");
+
+        // calculate current localePrefix (could be different from the one requested, e.g. on locale change)
+        String requestLocalePrefix = ResourceUtils.getRequestLocalePrefix();
+        if (requestLocalePrefix != null)
+        {
+            path.append(requestLocalePrefix);
+            path.append(PATH_SEPARATOR);
+        }
+
+        path.append(getLibraryName());
+        path.append(PATH_SEPARATOR);
+        path.append(getResourceName());
+
+        return facesContext.getApplication().getViewHandler().getResourceURL(facesContext, path.toString());
+    }
+
+    @Override
+    public Map<String, String> getResponseHeaders()
+    {
+        // Adopted from MyFaces' ResourceImpl
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+
+        if (facesContext.getApplication().getResourceHandler().isResourceRequest(facesContext))
+        {
+            Map<String, String> headers = new HashMap<String, String>();
+
+            long lastModified;
+            try
+            {
+                lastModified = ResourceUtils.getResourceLastModified(this.getURL());
+            }
+            catch (IOException e)
+            {
+                lastModified = -1;
+            }
+
+            if (lastModified >= 0)
+            {
+                headers.put("Last-Modified", ResourceUtils.formatDateHeader(lastModified));
+
+                long expires;
+                if (facesContext.isProjectStage(ProjectStage.Development))
+                {
+                    // Force to expire now to prevent caching on development time.
+                    expires = System.currentTimeMillis();
+                }
+                else
+                {
+                    expires = System.currentTimeMillis() + ResourceUtils.getMaxTimeExpires(facesContext);
+                }
+                headers.put("Expires", ResourceUtils.formatDateHeader(expires));
+            }
+
+            // add header if we're using content compression
+            if (shouldServeCompressedVersion(facesContext))
+            {
+                headers.put("Content-Encoding", "gzip");
+            }
+
+            return headers;
+        }
+        else
+        {
+            //No need to return headers
+            return Collections.emptyMap();
+        }
+    }
+
+    @Override
+    public boolean userAgentNeedsUpdate(FacesContext facesContext)
+    {
+        if (facesContext.isProjectStage(ProjectStage.Development))
+        {
+            // always re-send resources in development
+            return true;
+        }
+
+        // adopted from MyFaces' ResourceImpl:
+
+        // RFC2616 says related to If-Modified-Since header the following:
+        //
+        // "... The If-Modified-Since request-header field is used with a method to
+        // make it conditional: if the requested variant has not been modified since
+        // the time specified in this field, an entity will not be returned from
+        // the server; instead, a 304 (not modified) response will be returned
+        // without any message-body..."
+        //
+        // This method is called from ResourceHandlerImpl.handleResourceRequest and if
+        // returns false send a 304 Not Modified response.
+
+        String ifModifiedSinceString = facesContext.getExternalContext().getRequestHeaderMap().get("If-Modified-Since");
+
+        if (ifModifiedSinceString == null)
+        {
+            return true;
+        }
+
+        Long ifModifiedSince = ResourceUtils.parseDateHeader(ifModifiedSinceString);
+
+        if (ifModifiedSince == null)
+        {
+            return true;
+        }
+
+        Long lastModified;
+        try
+        {
+            lastModified = ResourceUtils.getResourceLastModified(this.getURL());
+        }
+        catch (IOException exception)
+        {
+            lastModified = -1L;
+        }
+
+        if (lastModified >= 0)
+        {
+            // If the lastModified date is lower or equal than ifModifiedSince,
+            // the agent does not need to update.
+            // Note the lastModified time is set at milisecond precision, but when
+            // the date is parsed and sent on ifModifiedSince, the exceding miliseconds
+            // are trimmed. So, we have to compare trimming this from the calculated
+            // lastModified time.
+            if ( (lastModified-(lastModified % 1000)) <= ifModifiedSince)
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    private String getInternalResourcePath()
+    {
+        StringBuilder path = new StringBuilder();
+        path.append(getLibraryName());
+        path.append("/");
+
+        if (requestedLocalePrefix != null)
+        {
+            path.append(requestedLocalePrefix);
+        }
+
+        path.append(getResourceName());
+
+        return path.toString();
+    }
+
+    private boolean isCompressable()
+    {
+        // GZIP compression is supported for .css and .js files
+        return this.getResourceName().endsWith(".css") || this.getResourceName().endsWith(".js");
+    }
+
+    private boolean userAgentSupportsCompression(FacesContext facesContext)
+    {
+        String acceptEncodingHeader = facesContext.getExternalContext()
+                .getRequestHeaderMap().get(ACCEPT_ENCODING_HEADER);
+
+        return ResourceUtils.isGZIPEncodingAccepted(acceptEncodingHeader);
+    }
+
+    private boolean shouldServeCompressedVersion(FacesContext facesContext)
+    {
+        // we should serve a compressed version of the resource, if
+        //   - ProjectStage != Development
+        //   - a compressed version is available (created in constructor)
+        //   - the user agent supports compresssion
+        return !developmentStage
+                && isCompressedVersionAvailable(facesContext)
+                && userAgentSupportsCompression(facesContext);
+    }
+
+    private boolean isCompressedVersionAvailable(FacesContext facesContext)
+    {
+        return getCompressedFile(facesContext).exists();
+    }
+
+    private File getCompressedFile(FacesContext facesContext)
+    {
+        File tmpdir = ResourceUtils.getServletContextTmpDir(facesContext);
+
+        return new File(tmpdir, COMPRESSION_BASE_DIR + getInternalResourcePath() + COMPRESSED_FILE_SUFFIX);
+    }
+
+    private InputStream getUncompressedInputStream() throws IOException
+    {
+        return ResourceUtils.getContextClassLoader().getResourceAsStream(BASE_DIR + getInternalResourcePath());
+    }
+
+    private InputStream getCompressedInputStream(FacesContext facesContext) throws IOException
+    {
+        return new FileInputStream(getCompressedFile(facesContext));
+    }
+
+    /**
+     * Uses GZIPOutputStream to compress this resource.
+     * It will be stored where getCompressedFile() points to.
+     *
+     * Note that the resource really must be compressable (isCompressable() must return true).
+     *
+     * @return
+     */
+    private void createCompressedVersion(FacesContext facesContext) throws IOException
+    {
+        File target = getCompressedFile(facesContext);
+        target.mkdirs();  // ensure necessary directories exist
+        target.delete();  // remove any existing file
+
+        InputStream inputStream = null;
+        FileOutputStream fileOutputStream;
+        GZIPOutputStream gzipOutputStream = null;
+        try
+        {
+            inputStream = getUncompressedInputStream();
+            fileOutputStream = new FileOutputStream(target);
+            gzipOutputStream = new GZIPOutputStream(fileOutputStream);
+            byte[] buffer = new byte[BUFFER_SIZE];
+
+            ResourceUtils.pipeBytes(inputStream, gzipOutputStream, buffer);
+        }
+        finally
+        {
+            if (inputStream != null)
+            {
+                inputStream.close();
+            }
+            if (gzipOutputStream != null)
+            {
+                // also closes fileOutputStream   
+                gzipOutputStream.close();
+            }
+        }
+    }
+
+}

Added: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResourceHandler.java
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResourceHandler.java?rev=1079447&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResourceHandler.java (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/AdvancedResourceHandler.java Tue Mar  8 17:26:34 2011
@@ -0,0 +1,162 @@
+/*
+ * 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.commons.resourcehandler;
+
+import org.apache.myfaces.commons.resourcehandler.config.AdvancedResourceHandlerConfig;
+
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.application.ResourceHandlerWrapper;
+import javax.faces.context.FacesContext;
+
+/**
+ * Custom JSF 2 ResourceHandler implementation, supporting:
+ *   - relative paths between resources (css files referencing images without using #resource['..'])
+ *   - caching resources in the client (disabled if ProjectStage == Development)
+ *   - GZIP compression and local cache in tmp dir (disabled if ProjectStage == Development)
+ *   - i18n (supporting country code and language).
+ *
+ * The i18n mechanism looks up the resource in the following order (e.g. current Locale is "de_AT"):
+ *   1. libraryName/de_AT/resourceName
+ *   2. libraryName/de/resourceName
+ *   3. libraryName/resourceName
+ *
+ * Standard features NOT available in this ResourceHandler:
+ *   - Support for ValueExpressions in resource files
+ *
+ * Install it in faces-config.xml via:
+ *
+ * <application>
+ *     <resource-handler>org.apache.myfaces.commons.resourcehandler.AdvancedResourceHandler</resource-handler>
+ * </application>
+ *
+ * In addition, libraries handled by this ResourceHandler must be specified in
+ * META-INF/advanced-resources.xml in the following format:
+ *
+ * <advanced-resources>
+ *     <libraries>
+ *         <library>library1</library>
+ *         <library>library2</library>
+ *     </libraries>
+ * </advanced-resources>
+ *
+ *
+ * ATTENTION: This ResourceHandler only works with prefix mapping. Please make sure your application
+ * uses prefix mapping ONLY (like e.g. /faces or /jsf) or at least provides the prefix mapping "/faces"
+ * for the FacesServlet. 
+ *
+ * @author Jakob Korherr
+ */
+public class AdvancedResourceHandler extends ResourceHandlerWrapper
+{
+
+    /**
+     * web.xml config parameter for max expire time of resources.
+     *
+     * Note that this one is the same as in MyFaces core.
+     */
+    public static final String RESOURCE_MAX_TIME_EXPIRES = "org.apache.myfaces.RESOURCE_MAX_TIME_EXPIRES";
+
+    public static final long DEFAULT_RESOURCE_MAX_TIME_EXPIRES = 604800000L;
+
+    /**
+     * Config file for AdvancedResourceHandler.
+     */
+    public static final String CONFIG_FILE = "META-INF/advanced-resources.xml";
+
+    private ResourceHandler wrappedHandler;
+    private AdvancedResourceHandlerConfig config;
+
+    public AdvancedResourceHandler(ResourceHandler wrappedHandler)
+    {
+        this.wrappedHandler = wrappedHandler;
+        this.config = new AdvancedResourceHandlerConfig();
+    }
+
+    @Override
+    public ResourceHandler getWrapped()
+    {
+        return wrappedHandler;
+    }
+
+    @Override
+    public Resource createResource(String resourceName)
+    {
+        return createResource(resourceName, null, null);   
+    }
+
+    @Override
+    public Resource createResource(String resourceName, String libraryName)
+    {
+        return createResource(resourceName, libraryName, null);
+    }
+
+    @Override
+    public Resource createResource(String resourceName, String libraryName, String contentType)
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        String requestedLocalePrefix = null;
+
+        // check if libraryName is null and the current request is a resource request
+        // in this case we need to check if this resource request should be handled by us
+        // --> valid resource url: de/$_$/library/Name/$_$/resource/Name
+        if (libraryName == null && facesContext.getApplication().getResourceHandler().isResourceRequest(facesContext))
+        {
+            // try to extract the libraryName and the locale from the resourceName
+            String[] urlParts = resourceName.split(AdvancedResource.PATH_SEPARATOR_REGEX);
+            if (urlParts.length == 3)
+            {
+                // locale + libraryName + resourceName
+                requestedLocalePrefix = urlParts[0];
+                libraryName = urlParts[1];
+                resourceName = urlParts[2];
+            }
+            else if(urlParts.length == 2)
+            {
+                // libraryName + resourceName
+                libraryName = urlParts[0];
+                resourceName = urlParts[1];
+            }
+        }
+
+        if (libraryName != null && config.isAdvancedLibrary(libraryName))
+        {
+            // the requested resource is an AdvancedResource
+            AdvancedResource resource = new AdvancedResource(resourceName, libraryName,
+                    contentType, requestedLocalePrefix);
+
+            if (resource.resourceExists())
+            {
+                return resource;
+            }
+            else
+            {
+                // the given resource should be handled by this ResourceHandler (otherwise there would not be
+                // a match in resourcePackageMapping), but it does not exist.
+                return null;
+            }
+        }
+        else
+        {
+            // use wrapped ResourceHandler (from MyFaces or Mojarra)
+            return super.createResource(resourceName, libraryName, contentType);
+        }
+    }
+
+}

Added: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/ResourceUtils.java
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/ResourceUtils.java?rev=1079447&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/ResourceUtils.java (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/ResourceUtils.java Tue Mar  8 17:26:34 2011
@@ -0,0 +1,474 @@
+/*
+ * 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.commons.resourcehandler;
+
+import javax.faces.FacesException;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+/**
+ * Utility methods needed in PackagedResource and PackagedResourceHandler. 
+ *
+ * @author Jakob Korherr
+ */
+public class ResourceUtils
+{
+
+    private static final String HTTP_RESPONSE_DATE_HEADER =
+        "EEE, dd MMM yyyy HH:mm:ss zzz";
+
+    private static final String[] HTTP_REQUEST_DATE_HEADER = {
+            "EEE, dd MMM yyyy HH:mm:ss zzz", "EEEEEE, dd-MMM-yy HH:mm:ss zzz",
+            "EEE MMMM d HH:mm:ss yyyy" };
+
+    private static TimeZone __GMT = TimeZone.getTimeZone("GMT");
+
+    /**
+     * The key with which the the FacesServlet mapping prefix is cached in the application map.
+     */
+    private static final String FACES_SERVLET_PREFIX_KEY
+            = "org.apache.myfaces.commons.resourcehandler.FACES_SERVLET_PREFIX";
+
+    /**
+     * Default value for the FacesServlet mapping prefix.
+     */
+    private static final String DEFAULT_FACES_SERVLET_PREFIX = "/faces";
+
+    /**
+     * Key for ServletContext attribute containing the tmp directory of the current ServletContext.
+     * Specified by Servlet API 2.2 and higher.
+     */
+    private static final String SERVLETCONTEXT_TMP_DIR_ATTR = "javax.servlet.context.tempdir";
+
+    /**
+     * Gets the FacesServlet mapping prefix to use for the request path.
+     *
+     * @param facesContext
+     * @return
+     */
+    public static String getFacesServletPrefix(FacesContext facesContext)
+    {
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> applicationMap = externalContext.getApplicationMap();
+
+        // check if already cached
+        String prefix = (String) applicationMap.get(FACES_SERVLET_PREFIX_KEY);
+        if (prefix == null)
+        {
+            // try to extract it from current request
+            prefix = getFacesServletPrefixMapping(facesContext);
+            if (prefix == null)
+            {
+                // none found, use default
+                prefix = DEFAULT_FACES_SERVLET_PREFIX;
+            }
+
+            // cache it
+            applicationMap.put(FACES_SERVLET_PREFIX_KEY, prefix);
+        }
+
+        return prefix;
+    }
+
+    /**
+     * Get the current Thread's ContextClassLoader.
+     *
+     * Copied from MyFaces' ClassLoaderUtils.
+     *
+     * @return
+     */
+    public static ClassLoader getContextClassLoader()
+    {
+        if (System.getSecurityManager() != null)
+        {
+            try
+            {
+                ClassLoader cl = AccessController.doPrivileged(new PrivilegedExceptionAction<ClassLoader>()
+                {
+                    public ClassLoader run() throws PrivilegedActionException
+                    {
+                        return Thread.currentThread().getContextClassLoader();
+                    }
+                });
+                return cl;
+            }
+            catch (PrivilegedActionException pae)
+            {
+                throw new FacesException(pae);
+            }
+        }
+        else
+        {
+            return Thread.currentThread().getContextClassLoader();
+        }
+    }
+
+    /**
+     * Get max expire time for resources.
+     *
+     * @param facesContext
+     * @return
+     */
+    public static long getMaxTimeExpires(FacesContext facesContext)
+    {
+        ExternalContext externalContext = facesContext.getExternalContext();
+        Map<String, Object> applicationMap = externalContext.getApplicationMap();
+
+        // check if already cached
+        Long expires = (Long) applicationMap.get(AdvancedResourceHandler.RESOURCE_MAX_TIME_EXPIRES);
+        if (expires == null)
+        {
+            // get from web.xml
+            String webXmlValue = externalContext.getInitParameter(AdvancedResourceHandler.RESOURCE_MAX_TIME_EXPIRES);
+            if (webXmlValue != null)
+            {
+                try
+                {
+                    expires = Long.parseLong(webXmlValue);
+                }
+                catch (NumberFormatException e)
+                {
+                    throw new FacesException("Config parameter "
+                            + AdvancedResourceHandler.RESOURCE_MAX_TIME_EXPIRES + " must be a Long", e);
+                }
+            }
+            else
+            {
+                // none found, use default
+                expires = AdvancedResourceHandler.DEFAULT_RESOURCE_MAX_TIME_EXPIRES;
+            }
+
+            // cache it
+            applicationMap.put(AdvancedResourceHandler.RESOURCE_MAX_TIME_EXPIRES, expires);
+        }
+
+        return expires;
+    }
+
+    /**
+     * Returns the String representation of the current locale for the use in the request path.
+     *
+     * @return
+     */
+    public static String getRequestLocalePrefix()
+    {
+        FacesContext facesContext = FacesContext.getCurrentInstance();
+        Locale locale = facesContext.getViewRoot().getLocale();
+
+        if (locale != null)
+        {
+            String language = locale.getLanguage();
+            String country = locale.getCountry();
+
+            if (country != null)
+            {
+                // de_AT
+                return language + "_" + country;
+            }
+            else
+            {
+                // de
+                return language;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the prefix mapping of the FacesServlet from the current request (e.g. /faces).
+     * If no mapping can be determined or the current request used extension mapping (e.g. *.jsf)
+     * null will be returned.
+     *
+     * This method was adopted from MyFaces' BaseResourceHandlerSupport.
+     *
+     * @param facesContext
+     * @return
+     */
+    public static String getFacesServletPrefixMapping(FacesContext facesContext)
+    {
+        ExternalContext externalContext = facesContext.getExternalContext();
+
+        String pathInfo = externalContext.getRequestPathInfo();
+        String servletPath = externalContext.getRequestServletPath();
+
+        if (pathInfo != null)
+        {
+            // If there is a "extra path", it's definitely no extension mapping.
+            // Now we just have to determine the path which has been specified
+            // in the url-pattern, but that's easy as it's the same as the
+            // current servletPath. It doesn't even matter if "/*" has been used
+            // as in this case the servletPath is just an empty string according
+            // to the Servlet Specification (SRV 4.4).
+            return servletPath;
+        }
+        else
+        {
+            // In the case of extension mapping, no "extra path" is available.
+            // Still it's possible that prefix-based mapping has been used.
+            // Actually, if there was an exact match no "extra path"
+            // is available (e.g. if the url-pattern is "/faces/*"
+            // and the request-uri is "/context/faces").
+            int slashPos = servletPath.lastIndexOf('/');
+            int extensionPos = servletPath.lastIndexOf('.');
+            if (extensionPos > -1 && extensionPos > slashPos)
+            {
+                // we are only interested in the prefix mapping
+                return null;
+            }
+            else
+            {
+                // There is no extension in the given servletPath and therefore
+                // we assume that it's an exact match using prefix-based mapping.
+                return servletPath;
+            }
+        }
+    }
+
+    /**
+     * Taken from MyFaces' ResourceLoaderUtils.
+     *
+     * @param url
+     * @return
+     * @throws IOException
+     */
+    public static long getResourceLastModified(URL url) throws IOException
+    {
+        if ("file".equals(url.getProtocol()))
+        {
+            String externalForm = url.toExternalForm();
+            // Remove the "file:"
+            File file = new File(externalForm.substring(5));
+
+            return file.lastModified();
+        }
+        else
+        {
+            return getResourceLastModified(url.openConnection());
+        }
+    }
+
+    /**
+     * Taken from MyFaces' ResourceLoaderUtils.
+     * 
+     * @param connection
+     * @return
+     * @throws IOException
+     */
+    public static long getResourceLastModified(URLConnection connection) throws IOException
+    {
+        long modified;
+        if (connection instanceof JarURLConnection)
+        {
+            // The following hack is required to work-around a JDK bug.
+            // getLastModified() on a JAR entry URL delegates to the actual JAR file
+            // rather than the JAR entry.
+            // This opens internally, and does not close, an input stream to the JAR
+            // file.
+            // In turn, you cannot close it by yourself, because it's internal.
+            // The work-around is to get the modification date of the JAR file
+            // manually,
+            // and then close that connection again.
+
+            URL jarFileUrl = ((JarURLConnection) connection).getJarFileURL();
+            URLConnection jarFileConnection = jarFileUrl.openConnection();
+
+            try
+            {
+                modified = jarFileConnection.getLastModified();
+            }
+            finally
+            {
+                try
+                {
+                    jarFileConnection.getInputStream().close();
+                }
+                catch (Exception exception)
+                {
+                    // Ignored
+                }
+            }
+        }
+        else
+        {
+            modified = connection.getLastModified();
+        }
+
+        return modified;
+    }
+
+    /**
+     * Taken from MyFaces' ResourceLoaderUtils. 
+     *
+     * @param value
+     * @return
+     */
+    public static String formatDateHeader(long value)
+    {
+        SimpleDateFormat format = new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, Locale.US);
+        format.setTimeZone(__GMT);
+        return format.format(new Date(value));
+    }
+
+    /**
+     * Taken from MyFaces' ResourceLoaderUtils.
+     *
+     * @param value
+     * @return
+     */
+    public static Long parseDateHeader(String value)
+    {
+        Date date = null;
+        for (int i = 0; (date == null) && (i < HTTP_REQUEST_DATE_HEADER.length); i++)
+        {
+            try
+            {
+                SimpleDateFormat format = new SimpleDateFormat(HTTP_REQUEST_DATE_HEADER[i], Locale.US);
+                format.setTimeZone(__GMT);
+                date = format.parse(value);
+            }
+            catch (ParseException e)
+            {
+                // nothing
+            }
+        }
+        if (date == null)
+        {
+            return null;
+        }
+        return new Long(date.getTime());
+    }
+
+    /**
+     * Returns the tmp dir of the current ServletContext,
+     * which every Servlet container has to provide.
+     *
+     * @param facesContext
+     * @return
+     */
+    public static File getServletContextTmpDir(FacesContext facesContext)
+    {
+        // note that the ApplicationMap directly accesses the ServletContext attribute map
+        return (File) facesContext.getExternalContext().getApplicationMap().get(SERVLETCONTEXT_TMP_DIR_ATTR);
+    }
+
+    /**
+     * Reads the specified input stream into the provided byte array storage and
+     * writes it to the output stream.
+     */
+    public static void pipeBytes(InputStream in, OutputStream out, byte[] buffer) throws IOException
+    {
+        int length;
+
+        while ((length = (in.read(buffer))) >= 0)
+        {
+            out.write(buffer, 0, length);
+        }
+    }
+
+    /**
+     * Checks if the user agent supports GZIP compression on basis of the "Accept-Encoding" header field. 
+     * Created according to RFC2616, section 14.3 Accept-Encoding.
+     *
+     * Some examples of Accept-Encoding:
+     *
+     *     Accept-Encoding: gzip, deflate
+     *     Accept-Encoding:
+     *     Accept-Encoding: *
+     *     Accept-Encoding: compress;q=0.5, gzip;q=1.0
+     *     Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0 
+     *
+     * @param acceptEncodingHeader
+     * @return
+     */
+    public static boolean isGZIPEncodingAccepted(String acceptEncodingHeader)
+    {
+        int gzipIndex = acceptEncodingHeader.indexOf("gzip");
+        if (gzipIndex != -1)
+        {
+            // "gzip" appears in the header
+            // --> check if banned via q=0
+            return !isEncodingQValueZero(acceptEncodingHeader, gzipIndex);
+        }
+
+        // no "gzip" in header --> check for "*"
+        int starIndex = acceptEncodingHeader.indexOf('*');
+        if (starIndex != -1)
+        {
+            // "*" appears in the header
+            // --> check if banned via q=0
+            return !isEncodingQValueZero(acceptEncodingHeader, starIndex);
+        }
+
+        // neither "gzip" nor "*" in header
+        return false;
+    }
+
+    private static boolean isEncodingQValueZero(String acceptEncodingHeader, int startIndex)
+    {
+        // remove any precending definitions
+        String encodingSubstring = acceptEncodingHeader.substring(startIndex);
+
+        // remove any subsequent definitions (separated via ,)
+        int commaIndex = encodingSubstring.indexOf(',');
+        if (commaIndex != -1)
+        {
+            encodingSubstring = encodingSubstring.substring(0, commaIndex);
+        }
+
+        int qZeroIndex = encodingSubstring.indexOf("q=0");
+        if (qZeroIndex != -1)
+        {
+            String qZeroSubstring = encodingSubstring.substring(qZeroIndex).trim();
+            if (qZeroSubstring.matches("q=0(\\.(0){0,3})?"))
+            {
+                // "q=0" or "q=0." or "q=0.0" or "q=0.00" or "q=0.000" found
+                // NOTE that there MUST NOT be more than 3 digits after the decimal point (per RFC section 3.9)
+                return true;
+            }
+            else
+            {
+                // q=0.xyz" found with any of xyz being != 0
+                return false;
+            }
+        }
+        else
+        {
+            // "q=0" not found
+            return false;
+        }
+    }
+    
+}

Added: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfig.java
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfig.java?rev=1079447&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfig.java (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfig.java Tue Mar  8 17:26:34 2011
@@ -0,0 +1,67 @@
+/*
+ * 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.commons.resourcehandler.config;
+
+import java.util.Collection;
+import java.util.HashSet;
+
+/**
+ * @author Jakob Korherr
+ */
+public class AdvancedResourceHandlerConfig
+{
+
+    private Collection<String> libraries;
+
+    /**
+     * Creates and fills the config (with the help of the related parser).
+     */
+    public AdvancedResourceHandlerConfig()
+    {
+        // use HashSet to perform contains() in constant time!!
+        libraries = new HashSet<String>();
+
+        // parse config files to add content
+        AdvancedResourceHandlerConfigParser parser = new AdvancedResourceHandlerConfigParser();
+        parser.parse(this);
+    }
+
+    /**
+     * Adds the given library to the libraries which are handled by the AdvancedResourceHandler.
+     * Should only be called by the config parser. 
+     * 
+     * @param library
+     */
+    void addLibrary(String library)
+    {
+        libraries.add(library);
+    }
+
+    /**
+     * Check if the given library should be handled by the AdvancedResourceHandler.
+     *
+     * @param library
+     * @return
+     */
+    public boolean isAdvancedLibrary(String library)
+    {
+        return libraries.contains(library);
+    }
+
+}

Added: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfigParser.java
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfigParser.java?rev=1079447&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfigParser.java (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/main/java/org/apache/myfaces/commons/resourcehandler/config/AdvancedResourceHandlerConfigParser.java Tue Mar  8 17:26:34 2011
@@ -0,0 +1,143 @@
+/*
+ * 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.commons.resourcehandler.config;
+
+import org.apache.myfaces.commons.resourcehandler.AdvancedResourceHandler;
+import org.apache.myfaces.commons.resourcehandler.ResourceUtils;
+
+import javax.faces.FacesException;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * XML-parser for AdvancedResourceHandlerConfig. 
+ *
+ * @author Jakob Korherr
+ */
+class AdvancedResourceHandlerConfigParser
+{
+
+    public void parse(AdvancedResourceHandlerConfig config)
+    {
+        ClassLoader classLoader = ResourceUtils.getContextClassLoader();
+        Enumeration<URL> configUrls = null;
+
+        try
+        {
+            // NOTE that there could be multiple config files (in different jar files)
+            configUrls = classLoader.getResources(AdvancedResourceHandler.CONFIG_FILE);
+        }
+        catch (IOException e)
+        {
+            throw new FacesException("Could not open " + AdvancedResourceHandler.CONFIG_FILE, e);
+        }
+
+        if (configUrls != null && configUrls.hasMoreElements())
+        {
+            // we have at least one config file and thus can start parsing
+            while (configUrls.hasMoreElements())
+            {
+                parseFile(configUrls.nextElement(), config);
+            }
+        }
+    }
+
+    private void parseFile(URL configFileUrl, AdvancedResourceHandlerConfig config)
+    {
+        XMLStreamReader streamReader = null;
+        InputStream configFileInputStream = null;
+
+        try
+        {
+            // open configfile stream
+            configFileInputStream = configFileUrl.openStream();
+
+            // create XMLInputFactory
+            XMLInputFactory factory = XMLInputFactory.newInstance();
+            streamReader = factory.createXMLStreamReader(configFileInputStream);
+
+            String localName = null; // localName of current xml element
+
+            while (streamReader.hasNext())
+            {
+                streamReader.next();
+
+                if (streamReader.isStartElement())
+                {
+                    localName = streamReader.getLocalName();
+                }
+                else if (streamReader.isCharacters())
+                {
+                    if ("library".equals(localName))
+                    {
+                        String library = streamReader.getText().trim();
+
+                        if (library.length() > 0)
+                        {
+                            config.addLibrary(library);
+                        }
+                    }
+                }
+                else if (streamReader.isEndElement())
+                {
+                    // set localName to null to prevent matching the following text section
+                    // between this end-element and the next start-element
+                    localName = null;
+                }
+            }
+        }
+        catch (Exception e)
+        {
+            throw new FacesException("Could not parse advanced-resources.xml", e);
+        }
+        finally
+        {
+            // close Reader and InputStream
+            if (streamReader != null)
+            {
+                try
+                {
+                    streamReader.close();
+                }
+                catch (XMLStreamException e)
+                {
+                    throw new FacesException("Could not close advanced-resources.xml", e);
+                }
+                finally
+                {
+                    // configFileInputStream is available if streamReader is not null
+                    try
+                    {
+                        configFileInputStream.close();
+                    }
+                    catch (IOException e)
+                    {
+                        throw new FacesException("Could not close advanced-resources.xml", e);
+                    }
+                }
+            }
+        }
+    }
+
+}

Added: myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/commons/resourcehandler/ResourceUtilsTest.java
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/commons/resourcehandler/ResourceUtilsTest.java?rev=1079447&view=auto
==============================================================================
--- myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/commons/resourcehandler/ResourceUtilsTest.java (added)
+++ myfaces/commons/branches/jsf_20/myfaces-commons-resourcehandler/src/test/java/org/apache/myfaces/commons/resourcehandler/ResourceUtilsTest.java Tue Mar  8 17:26:34 2011
@@ -0,0 +1,81 @@
+/*
+ * 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.commons.resourcehandler;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test cases for ResourceUtils.
+ *
+ * @author Jakob Korherr
+ */
+@RunWith(JUnit4.class)
+public class ResourceUtilsTest
+{
+
+    @Test
+    public void testIsGZIPEncodingAccepted()
+    {
+        // ACCEPT
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip,deflate")); // most common header value
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0.001"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0.01"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0.1"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip; q=0.001"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip;   q=0.001    "));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip;   q=0.001    ,  compress"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("compress, gzip;   q=0.001 "));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("gzip;   q=0.001    ,  compress;q=0.2 "));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("identity; q=0.2 ,  gzip;   q=0.001    ,  compress"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("*"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("*;q=1"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("*;q=0.001"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("*; q=0.1  "));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("  *;   q=0.001    ,  compress"));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("identity; q=0.2 ,  *;   q=0.001   "));
+        Assert.assertTrue(ResourceUtils.isGZIPEncodingAccepted("identity; q=0.2 ,  *;   q=0.001    ,  compress"));
+
+        // DENY
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted(""));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("deflate"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0."));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0.0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0.00"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0.000"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("gzip; q=0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("deflate, gzip;   q=0   , compress"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("gzip;q=0, *"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*, gzip;q=0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*;q=0, gzip;q=0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*;q=0.3, gzip;q=0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*;q=0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*;q=0."));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*;q=0.0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*;q=0.00"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("*;q=0.000"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("deflate, *;q=0"));
+        Assert.assertFalse(ResourceUtils.isGZIPEncodingAccepted("deflate, *;q=0, identity "));
+    }
+
+}

Modified: myfaces/commons/branches/jsf_20/pom.xml
URL: http://svn.apache.org/viewvc/myfaces/commons/branches/jsf_20/pom.xml?rev=1079447&r1=1079446&r2=1079447&view=diff
==============================================================================
--- myfaces/commons/branches/jsf_20/pom.xml (original)
+++ myfaces/commons/branches/jsf_20/pom.xml Tue Mar  8 17:26:34 2011
@@ -255,6 +255,7 @@
         <module>myfaces-commons-validators</module>
         <module>myfaces-commons-converters</module>
         <module>myfaces-commons-components</module>
+        <module>myfaces-commons-resourcehandler</module>
         <module>examples</module>
     </modules>
 



Mime
View raw message