You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by lu...@apache.org on 2012/07/05 10:49:50 UTC

svn commit: r1357494 - in /myfaces/core/trunk: impl/src/main/java/org/apache/myfaces/config/ impl/src/main/java/org/apache/myfaces/config/annotation/ impl/src/main/java/org/apache/myfaces/config/util/ impl/src/main/java/org/apache/myfaces/spi/ impl/src...

Author: lu4242
Date: Thu Jul  5 08:49:50 2012
New Revision: 1357494

URL: http://svn.apache.org/viewvc?rev=1357494&view=rev
Log:
MYFACES-3553 [GAE] Add param to select jar files to be scanned for .faces-config.xml or .taglib.xml or annotations 

Added:
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/GAEUtils.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/JarUtils.java
Modified:
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/DefaultFacesConfigResourceProvider.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/annotation/DefaultAnnotationProvider.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProvider.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProviderWrapper.java
    myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/compiler/DefaultFaceletConfigResourceProvider.java
    myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/DefaultFacesConfigResourceProvider.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/DefaultFacesConfigResourceProvider.java?rev=1357494&r1=1357493&r2=1357494&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/DefaultFacesConfigResourceProvider.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/DefaultFacesConfigResourceProvider.java Thu Jul  5 08:49:50 2012
@@ -26,9 +26,12 @@ import java.util.Enumeration;
 import java.util.List;
 
 import javax.faces.context.ExternalContext;
+import org.apache.myfaces.shared.config.MyfacesConfig;
 
 import org.apache.myfaces.shared.util.ClassUtils;
 import org.apache.myfaces.spi.FacesConfigResourceProvider;
+import org.apache.myfaces.util.ContainerUtils;
+import org.apache.myfaces.config.util.GAEUtils;
 import org.apache.myfaces.view.facelets.util.Classpath;
 
 /**
@@ -68,11 +71,27 @@ public class DefaultFacesConfigResourceP
             urlSet.add(resources.nextElement());
         }
 
-        //Scan files inside META-INF ending with .faces-config.xml
-        URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACES_CONFIG_SUFFIX);
-        for (int i = 0; i < urls.length; i++)
+        String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
+        jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
+        if (ContainerUtils.isRunningOnGoogleAppEngine(context) && 
+            jarFilesToScanParam != null &&
+            jarFilesToScanParam.length() > 0)
         {
-            urlSet.add(urls[i]);
+            Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
+                    context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
+            if (urlsGAE != null)
+            {
+                urlSet.addAll(urlsGAE);
+            }
+        }
+        else
+        {
+            //Scan files inside META-INF ending with .faces-config.xml
+            URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACES_CONFIG_SUFFIX);
+            for (int i = 0; i < urls.length; i++)
+            {
+                urlSet.add(urls[i]);
+            }
         }
         
         return urlSet;

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/annotation/DefaultAnnotationProvider.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/annotation/DefaultAnnotationProvider.java?rev=1357494&r1=1357493&r2=1357494&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/annotation/DefaultAnnotationProvider.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/annotation/DefaultAnnotationProvider.java Thu Jul  5 08:49:50 2012
@@ -50,9 +50,14 @@ import javax.faces.render.FacesRenderer;
 import javax.faces.validator.FacesValidator;
 
 import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
+import org.apache.myfaces.shared.config.MyfacesConfig;
 import org.apache.myfaces.shared.util.ClassUtils;
 import org.apache.myfaces.spi.AnnotationProvider;
 import org.apache.myfaces.spi.AnnotationProviderFactory;
+import org.apache.myfaces.util.ContainerUtils;
+import org.apache.myfaces.config.util.GAEUtils;
+import org.apache.myfaces.config.util.JarUtils;
+import org.apache.myfaces.shared.util.StringUtils;
 import org.apache.myfaces.view.facelets.util.Classpath;
 
 /**
@@ -164,15 +169,29 @@ public class DefaultAnnotationProvider e
         }
         
         //2. Scan for annotations on classpath
-        try
-        {
-            AnnotationProvider provider
-                    = AnnotationProviderFactory.getAnnotationProviderFactory(ctx).getAnnotationProvider(ctx);
-            classes = getAnnotatedMetaInfClasses(ctx, provider.getBaseUrls());
+        String jarAnnotationFilesToScanParam = MyfacesConfig.getCurrentInstance(ctx).getGaeJsfAnnotationsJarFiles();
+        jarAnnotationFilesToScanParam = jarAnnotationFilesToScanParam != null ? 
+                jarAnnotationFilesToScanParam.trim() : null;
+        if (ContainerUtils.isRunningOnGoogleAppEngine(ctx) && 
+            jarAnnotationFilesToScanParam != null &&
+            jarAnnotationFilesToScanParam.length() > 0)
+        {
+            // Skip call AnnotationProvider.getBaseUrls(ctx), and instead use the value of the config parameter
+            // to find which classes needs to be scanned for annotations
+            classes = getGAEAnnotatedMetaInfClasses(ctx, jarAnnotationFilesToScanParam);
         }
-        catch (IOException e)
+        else
         {
-            throw new FacesException(e);
+            try
+            {
+                AnnotationProvider provider
+                        = AnnotationProviderFactory.getAnnotationProviderFactory(ctx).getAnnotationProvider(ctx);
+                classes = getAnnotatedMetaInfClasses(ctx, provider.getBaseUrls(ctx));
+            }
+            catch (IOException e)
+            {
+                throw new FacesException(e);
+            }
         }
         
         for (Class<?> clazz : classes)
@@ -183,16 +202,18 @@ public class DefaultAnnotationProvider e
         //3. Scan on myfaces-impl for annotations available on myfaces-impl.
         //Also scan jar including META-INF/standard-faces-config.xml
         //(myfaces-impl jar file)
-        URL url = getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
-        if (url == null)
-        {
-            url = getClass().getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
-        }
-        classes = getAnnotatedMyfacesImplClasses(ctx, url);
-        for (Class<?> clazz : classes)
-        {
-            processClass(map, clazz);
-        }
+        // -= Leonardo Uribe =- No annotations in MyFaces jars, code not
+        // necessary, because all config is already in standard-faces-config.xml
+        //URL url = getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
+        //if (url == null)
+        //{
+        //    url = getClass().getClassLoader().getResource(STANDARD_FACES_CONFIG_RESOURCE);
+        //}
+        //classes = getAnnotatedMyfacesImplClasses(ctx, url);
+        //for (Class<?> clazz : classes)
+        //{
+        //    processClass(map, clazz);
+        //}
         
         return map;
     }
@@ -219,6 +240,39 @@ public class DefaultAnnotationProvider e
         
         return urlSet;
     }
+    
+    @Override
+    public Set<URL> getBaseUrls(ExternalContext context) throws IOException
+    {
+        String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
+        jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
+        if (ContainerUtils.isRunningOnGoogleAppEngine(context) && 
+            jarFilesToScanParam != null &&
+            jarFilesToScanParam.length() > 0)
+        {
+            Set<URL> urlSet = new HashSet<URL>();
+            
+            //This usually happens when maven-jetty-plugin is used
+            //Scan jars looking for paths including META-INF/faces-config.xml
+            Enumeration<URL> resources = getClassLoader().getResources(FACES_CONFIG_IMPLICIT);
+            while (resources.hasMoreElements())
+            {
+                urlSet.add(resources.nextElement());
+            }
+            
+            Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
+                    context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACES_CONFIG_SUFFIX);
+            if (urlsGAE != null)
+            {
+                urlSet.addAll(urlsGAE);
+            }
+            return urlSet;
+        }
+        else
+        {
+            return getBaseUrls();
+        }
+    }
 
     protected Collection<Class<?>> getAnnotatedMetaInfClasses(ExternalContext ctx, Set<URL> urls)
     {
@@ -244,6 +298,45 @@ public class DefaultAnnotationProvider e
         }
         return Collections.emptyList();
     }
+    
+    protected Collection<Class<?>> getGAEAnnotatedMetaInfClasses(ExternalContext context, String filter)
+    {
+        if (!filter.equals("none"))
+        {
+            String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
+            Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
+            if (paths != null)
+            {
+                List<Class<?>> list = new ArrayList<Class<?>>();
+                for (Object pathObject : paths)
+                {
+                    String path = (String) pathObject;
+                    if (path.endsWith(".jar") && GAEUtils.wildcardMatch(path, jarFilesToScan, GAEUtils.WEB_LIB_PREFIX))
+                    {
+                        // GAE does not use WAR format, so the app is just uncompressed in a directory
+                        // What we need here is just take the path of the file, and open the file as a
+                        // jar file. Then, if the jar should be scanned, try to find the required file.
+                        try
+                        {
+                            URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/"); 
+                            JarFile jarFile = JarUtils.getJarFile(jarUrl);
+                            if (jarFile != null)
+                            {
+                                archiveClasses(context, jarFile, list);
+                            }
+                        }
+                        catch(IOException e)
+                        {
+                            log.log(Level.SEVERE, 
+                                    "IOException when reading jar file for annotations using filter: "+filter, e);
+                        }
+                    }
+                }
+                return list;
+            }
+        }
+        return Collections.emptyList();
+    }
 
     protected Collection<Class<?>> getAnnotatedMyfacesImplClasses(ExternalContext ctx, URL url)
     {

Added: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/GAEUtils.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/GAEUtils.java?rev=1357494&view=auto
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/GAEUtils.java (added)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/GAEUtils.java Thu Jul  5 08:49:50 2012
@@ -0,0 +1,590 @@
+/*
+ * 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.config.util;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.Stack;
+import java.util.TreeSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import javax.faces.context.ExternalContext;
+import org.apache.myfaces.shared.util.StringUtils;
+
+/**
+ * Utility methods to use in Google Application Engine (GAE)
+ * 
+ * @author Leonardo Uribe
+ */
+public class GAEUtils
+{
+
+    public static final String WEB_LIB_PREFIX = "/WEB-INF/lib/";
+    
+
+    /**
+     * Look in all jars located inside /WEB-INF/lib/ folder for files that has
+     * some specified prefix and suffix. It is a simplification that can be done
+     * in GAE, because no JSF libraries are outside /WEB-INF/lib
+     *
+     * @param context
+     * @param classloader
+     * @param prefix
+     * @param suffix
+     * @return
+     * @throws IOException
+     */
+    public static Collection<URL> searchInWebLib(
+            ExternalContext context, ClassLoader classloader, String filter, 
+            String prefix, String suffix) throws IOException
+    {
+        if (!filter.equals("none"))
+        {
+            String[] jarFilesToScan = StringUtils.trim(StringUtils.splitLongString(filter, ','));
+            Set<URL> urlSet = null;
+            Set<String> paths = context.getResourcePaths(WEB_LIB_PREFIX);
+            if (paths != null)
+            {
+                for (Object pathObject : paths)
+                {
+                    String path = (String) pathObject;
+                    if (path.endsWith(".jar") && wildcardMatch(path, jarFilesToScan, WEB_LIB_PREFIX))
+                    {
+                        // GAE does not use WAR format, so the app is just uncompressed in a directory
+                        // What we need here is just take the path of the file, and open the file as a
+                        // jar file. Then, if the jar should be scanned, try to find the required file.
+                        URL jarUrl = new URL("jar:" + context.getResource(path).toExternalForm() + "!/");
+                        JarFile jarFile = JarUtils.getJarFile(jarUrl);
+
+                        Enumeration<JarEntry> entries = jarFile.entries();
+                        while (entries.hasMoreElements())
+                        {
+                            JarEntry entry = entries.nextElement();
+                            if (entry.isDirectory())
+                            {
+                                continue; // This is a directory
+                            }
+                            String name = entry.getName();
+                            if (!name.startsWith(prefix))
+                            {
+                                continue; // Attribute files
+                            }
+                            if (name.endsWith(suffix))
+                            {
+                                // Get it from classloader, because no URL can be
+                                // derived from JarEntry
+                                Enumeration<URL> alternateFacesConfigs = classloader.getResources(name);
+                                while (alternateFacesConfigs.hasMoreElements())
+                                {
+                                    if (urlSet == null)
+                                    {
+                                        urlSet = new TreeSet<URL>();
+                                    }
+                                    urlSet.add(alternateFacesConfigs.nextElement());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            return urlSet;
+        }
+        return null;
+    }
+
+    public static boolean wildcardMatch(String filename, String[] wildcardMatchers, String prefix)
+    {
+        for (String matcher : wildcardMatchers)
+        {
+            if (wildcardMatch(filename, prefix + matcher))
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    // NOTE: CODE TAKEN FROM COMMONS-IO AND REFACTORED TO USE INSIDE GAE
+    //-----------------------------------------------------------------------
+    /**
+     * Checks a filename to see if it matches the specified wildcard matcher,
+     * always testing case-sensitive. <p> The wildcard matcher uses the
+     * characters '?' and '*' to represent a single or multiple (zero or more)
+     * wildcard characters. This is the same as often found on Dos/Unix command
+     * lines. The check is case-sensitive always.
+     * <pre>
+     * wildcardMatch("c.txt", "*.txt")      --> true
+     * wildcardMatch("c.txt", "*.jpg")      --> false
+     * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
+     * wildcardMatch("c.txt", "*.???")      --> true
+     * wildcardMatch("c.txt", "*.????")     --> false
+     * </pre> N.B. the sequence "*?" does not work properly at present in match
+     * strings.
+     *
+     * @param filename the filename to match on
+     * @param wildcardMatcher the wildcard string to match against
+     * @return true if the filename matches the wilcard string
+     * @see IOCase#SENSITIVE
+     */
+    static boolean wildcardMatch(String filename, String wildcardMatcher)
+    {
+        return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE);
+    }
+
+    /**
+     * Checks a filename to see if it matches the specified wildcard matcher
+     * using the case rules of the system. <p> The wildcard matcher uses the
+     * characters '?' and '*' to represent a single or multiple (zero or more)
+     * wildcard characters. This is the same as often found on Dos/Unix command
+     * lines. The check is case-sensitive on Unix and case-insensitive on
+     * Windows.
+     * <pre>
+     * wildcardMatch("c.txt", "*.txt")      --> true
+     * wildcardMatch("c.txt", "*.jpg")      --> false
+     * wildcardMatch("a/b/c.txt", "a/b/*")  --> true
+     * wildcardMatch("c.txt", "*.???")      --> true
+     * wildcardMatch("c.txt", "*.????")     --> false
+     * </pre> N.B. the sequence "*?" does not work properly at present in match
+     * strings.
+     *
+     * @param filename the filename to match on
+     * @param wildcardMatcher the wildcard string to match against
+     * @return true if the filename matches the wilcard string
+     * @see IOCase#SYSTEM
+     */
+    static boolean wildcardMatchOnSystem(String filename, String wildcardMatcher)
+    {
+        //return wildcardMatch(filename, wildcardMatcher, IOCase.SYSTEM);
+        return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE);
+    }
+
+    /**
+     * Checks a filename to see if it matches the specified wildcard matcher
+     * allowing control over case-sensitivity. <p> The wildcard matcher uses the
+     * characters '?' and '*' to represent a single or multiple (zero or more)
+     * wildcard characters. N.B. the sequence "*?" does not work properly at
+     * present in match strings.
+     *
+     * @param filename the filename to match on
+     * @param wildcardMatcher the wildcard string to match against
+     * @param caseSensitivity what case sensitivity rule to use, null means
+     * case-sensitive
+     * @return true if the filename matches the wilcard string
+     * @since 1.3
+     */
+    static boolean wildcardMatch(String filename, String wildcardMatcher, IOCase caseSensitivity)
+    {
+        if (filename == null && wildcardMatcher == null)
+        {
+            return true;
+        }
+        if (filename == null || wildcardMatcher == null)
+        {
+            return false;
+        }
+        if (caseSensitivity == null)
+        {
+            caseSensitivity = IOCase.SENSITIVE;
+        }
+        String[] wcs = splitOnTokens(wildcardMatcher);
+        boolean anyChars = false;
+        int textIdx = 0;
+        int wcsIdx = 0;
+        Stack<int[]> backtrack = new Stack<int[]>();
+
+        // loop around a backtrack stack, to handle complex * matching
+        do
+        {
+            if (backtrack.size() > 0)
+            {
+                int[] array = backtrack.pop();
+                wcsIdx = array[0];
+                textIdx = array[1];
+                anyChars = true;
+            }
+
+            // loop whilst tokens and text left to process
+            while (wcsIdx < wcs.length)
+            {
+
+                if (wcs[wcsIdx].equals("?"))
+                {
+                    // ? so move to next text char
+                    textIdx++;
+                    if (textIdx > filename.length())
+                    {
+                        break;
+                    }
+                    anyChars = false;
+
+                }
+                else if (wcs[wcsIdx].equals("*"))
+                {
+                    // set any chars status
+                    anyChars = true;
+                    if (wcsIdx == wcs.length - 1)
+                    {
+                        textIdx = filename.length();
+                    }
+
+                }
+                else
+                {
+                    // matching text token
+                    if (anyChars)
+                    {
+                        // any chars then try to locate text token
+                        textIdx = caseSensitivity.checkIndexOf(filename, textIdx, wcs[wcsIdx]);
+                        if (textIdx == -1)
+                        {
+                            // token not found
+                            break;
+                        }
+                        int repeat = caseSensitivity.checkIndexOf(filename, textIdx + 1, wcs[wcsIdx]);
+                        if (repeat >= 0)
+                        {
+                            backtrack.push(new int[]
+                                    {
+                                        wcsIdx, repeat
+                                    });
+                        }
+                    }
+                    else
+                    {
+                        // matching from current position
+                        if (!caseSensitivity.checkRegionMatches(filename, textIdx, wcs[wcsIdx]))
+                        {
+                            // couldnt match token
+                            break;
+                        }
+                    }
+
+                    // matched text token, move text index to end of matched token
+                    textIdx += wcs[wcsIdx].length();
+                    anyChars = false;
+                }
+
+                wcsIdx++;
+            }
+
+            // full match
+            if (wcsIdx == wcs.length && textIdx == filename.length())
+            {
+                return true;
+            }
+
+        } while (backtrack.size() > 0);
+
+        return false;
+    }
+
+    /**
+     * Splits a string into a number of tokens. The text is split by '?' and
+     * '*'. Where multiple '*' occur consecutively they are collapsed into a
+     * single '*'.
+     *
+     * @param text the text to split
+     * @return the array of tokens, never null
+     */
+    static String[] splitOnTokens(String text)
+    {
+        // used by wildcardMatch
+        // package level so a unit test may run on this
+
+        if (text.indexOf('?') == -1 && text.indexOf('*') == -1)
+        {
+            return new String[]
+                    {
+                        text
+                    };
+        }
+
+        char[] array = text.toCharArray();
+        ArrayList<String> list = new ArrayList<String>();
+        StringBuilder buffer = new StringBuilder();
+        for (int i = 0; i < array.length; i++)
+        {
+            if (array[i] == '?' || array[i] == '*')
+            {
+                if (buffer.length() != 0)
+                {
+                    list.add(buffer.toString());
+                    buffer.setLength(0);
+                }
+                if (array[i] == '?')
+                {
+                    list.add("?");
+                }
+                else if (list.isEmpty()
+                        || i > 0 && list.get(list.size() - 1).equals("*") == false)
+                {
+                    list.add("*");
+                }
+            }
+            else
+            {
+                buffer.append(array[i]);
+            }
+        }
+        if (buffer.length() != 0)
+        {
+            list.add(buffer.toString());
+        }
+
+        return list.toArray(new String[list.size()]);
+    }
+
+    final static class IOCase implements Serializable
+    {
+
+        /**
+         * The constant for case sensitive regardless of operating system.
+         */
+        public static final IOCase SENSITIVE = new IOCase("Sensitive", true);
+        /**
+         * The constant for case insensitive regardless of operating system.
+         */
+        public static final IOCase INSENSITIVE = new IOCase("Insensitive", false);
+        /**
+         * The constant for case sensitivity determined by the current operating
+         * system. Windows is case-insensitive when comparing filenames, Unix is
+         * case-sensitive. <p> <strong>Note:</strong> This only caters for
+         * Windows and Unix. Other operating systems (e.g. OSX and OpenVMS) are
+         * treated as case sensitive if they use the Unix file separator and
+         * case-insensitive if they use the Windows file separator (see {@link java.io.File#separatorChar}).
+         * <p> If you derialize this constant of Windows, and deserialize on
+         * Unix, or vice versa, then the value of the case-sensitivity flag will
+         * change.
+         */
+        //public static final IOCase SYSTEM = new IOCase("System", !FilenameUtils.isSystemWindows());
+        /**
+         * Serialization version.
+         */
+        private static final long serialVersionUID = -6343169151696340687L;
+        /**
+         * The enumeration name.
+         */
+        private final String name;
+        /**
+         * The sensitivity flag.
+         */
+        private final transient boolean sensitive;
+
+        //-----------------------------------------------------------------------
+        /**
+         * Factory method to create an IOCase from a name.
+         *
+         * @param name the name to find
+         * @return the IOCase object
+         * @throws IllegalArgumentException if the name is invalid
+         */
+        public static IOCase forName(String name)
+        {
+            if (IOCase.SENSITIVE.name.equals(name))
+            {
+                return IOCase.SENSITIVE;
+            }
+            if (IOCase.INSENSITIVE.name.equals(name))
+            {
+                return IOCase.INSENSITIVE;
+            }
+            //if (IOCase.SYSTEM.name.equals(name)){
+            //    return IOCase.SYSTEM;
+            //}
+            throw new IllegalArgumentException("Invalid IOCase name: " + name);
+        }
+
+        //-----------------------------------------------------------------------
+        /**
+         * Private constructor.
+         *
+         * @param name the name
+         * @param sensitive the sensitivity
+         */
+        private IOCase(String name, boolean sensitive)
+        {
+            this.name = name;
+            this.sensitive = sensitive;
+        }
+
+        /**
+         * Replaces the enumeration from the stream with a real one. This
+         * ensures that the correct flag is set for SYSTEM.
+         *
+         * @return the resolved object
+         */
+        private Object readResolve()
+        {
+            return forName(name);
+        }
+
+        //-----------------------------------------------------------------------
+        /**
+         * Gets the name of the constant.
+         *
+         * @return the name of the constant
+         */
+        public String getName()
+        {
+            return name;
+        }
+
+        /**
+         * Does the object represent case sensitive comparison.
+         *
+         * @return true if case sensitive
+         */
+        public boolean isCaseSensitive()
+        {
+            return sensitive;
+        }
+
+        //-----------------------------------------------------------------------
+        /**
+         * Compares two strings using the case-sensitivity rule. <p> This method
+         * mimics {@link String#compareTo} but takes case-sensitivity into
+         * account.
+         *
+         * @param str1 the first string to compare, not null
+         * @param str2 the second string to compare, not null
+         * @return true if equal using the case rules
+         * @throws NullPointerException if either string is null
+         */
+        public int checkCompareTo(String str1, String str2)
+        {
+            if (str1 == null || str2 == null)
+            {
+                throw new NullPointerException("The strings must not be null");
+            }
+            return sensitive ? str1.compareTo(str2) : str1.compareToIgnoreCase(str2);
+        }
+
+        /**
+         * Compares two strings using the case-sensitivity rule. <p> This method
+         * mimics {@link String#equals} but takes case-sensitivity into account.
+         *
+         * @param str1 the first string to compare, not null
+         * @param str2 the second string to compare, not null
+         * @return true if equal using the case rules
+         * @throws NullPointerException if either string is null
+         */
+        public boolean checkEquals(String str1, String str2)
+        {
+            if (str1 == null || str2 == null)
+            {
+                throw new NullPointerException("The strings must not be null");
+            }
+            return sensitive ? str1.equals(str2) : str1.equalsIgnoreCase(str2);
+        }
+
+        /**
+         * Checks if one string starts with another using the case-sensitivity
+         * rule. <p> This method mimics {@link String#startsWith(String)} but
+         * takes case-sensitivity into account.
+         *
+         * @param str the string to check, not null
+         * @param start the start to compare against, not null
+         * @return true if equal using the case rules
+         * @throws NullPointerException if either string is null
+         */
+        public boolean checkStartsWith(String str, String start)
+        {
+            return str.regionMatches(!sensitive, 0, start, 0, start.length());
+        }
+
+        /**
+         * Checks if one string ends with another using the case-sensitivity
+         * rule. <p> This method mimics {@link String#endsWith} but takes
+         * case-sensitivity into account.
+         *
+         * @param str the string to check, not null
+         * @param end the end to compare against, not null
+         * @return true if equal using the case rules
+         * @throws NullPointerException if either string is null
+         */
+        public boolean checkEndsWith(String str, String end)
+        {
+            int endLen = end.length();
+            return str.regionMatches(!sensitive, str.length() - endLen, end, 0, endLen);
+        }
+
+        /**
+         * Checks if one string contains another starting at a specific index
+         * using the case-sensitivity rule. <p> This method mimics parts of {@link String#indexOf(String, int)}
+         * but takes case-sensitivity into account.
+         *
+         * @param str the string to check, not null
+         * @param strStartIndex the index to start at in str
+         * @param search the start to search for, not null
+         * @return the first index of the search String, -1 if no match or {@code null}
+         * string input
+         * @throws NullPointerException if either string is null
+         * @since 2.0
+         */
+        public int checkIndexOf(String str, int strStartIndex, String search)
+        {
+            int endIndex = str.length() - search.length();
+            if (endIndex >= strStartIndex)
+            {
+                for (int i = strStartIndex; i <= endIndex; i++)
+                {
+                    if (checkRegionMatches(str, i, search))
+                    {
+                        return i;
+                    }
+                }
+            }
+            return -1;
+        }
+
+        /**
+         * Checks if one string contains another at a specific index using the
+         * case-sensitivity rule. <p> This method mimics parts of {@link 
+         * String#regionMatches(boolean, int, String, int, int)}
+         * but takes case-sensitivity into account.
+         *
+         * @param str the string to check, not null
+         * @param strStartIndex the index to start at in str
+         * @param search the start to search for, not null
+         * @return true if equal using the case rules
+         * @throws NullPointerException if either string is null
+         */
+        public boolean checkRegionMatches(String str, int strStartIndex, String search)
+        {
+            return str.regionMatches(!sensitive, strStartIndex, search, 0, search.length());
+        }
+
+        //-----------------------------------------------------------------------
+        /**
+         * Gets a string describing the sensitivity.
+         *
+         * @return a string describing the sensitivity
+         */
+        @Override
+        public String toString()
+        {
+            return name;
+        }
+    }
+}

Added: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/JarUtils.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/JarUtils.java?rev=1357494&view=auto
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/JarUtils.java (added)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/config/util/JarUtils.java Thu Jul  5 08:49:50 2012
@@ -0,0 +1,87 @@
+/*
+ * 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.config.util;
+
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarFile;
+
+/**
+ *
+ * @author Leonardo Uribe
+ */
+public class JarUtils
+{
+
+    public static JarFile getJarFile(URL url) throws IOException
+    {
+        URLConnection conn = url.openConnection();
+        conn.setUseCaches(false);
+        conn.setDefaultUseCaches(false);
+
+        JarFile jarFile;
+        if (conn instanceof JarURLConnection)
+        {
+            jarFile = ((JarURLConnection) conn).getJarFile();
+        }
+        else
+        {
+            jarFile = _getAlternativeJarFile(url);
+        }
+        return jarFile;
+    }
+    
+    /**
+     * taken from org.apache.myfaces.view.facelets.util.Classpath
+     * 
+     * For URLs to JARs that do not use JarURLConnection - allowed by the servlet spec - attempt to produce a JarFile
+     * object all the same. Known servlet engines that function like this include Weblogic and OC4J. This is not a full
+     * solution, since an unpacked WAR or EAR will not have JAR "files" as such.
+     */
+    private static JarFile _getAlternativeJarFile(URL url) throws IOException
+    {
+        String urlFile = url.getFile();
+
+        // Trim off any suffix - which is prefixed by "!/" on Weblogic
+        int separatorIndex = urlFile.indexOf("!/");
+
+        // OK, didn't find that. Try the less safe "!", used on OC4J
+        if (separatorIndex == -1)
+        {
+            separatorIndex = urlFile.indexOf('!');
+        }
+
+        if (separatorIndex != -1)
+        {
+            String jarFileUrl = urlFile.substring(0, separatorIndex);
+            // And trim off any "file:" prefix.
+            if (jarFileUrl.startsWith("file:"))
+            {
+                jarFileUrl = jarFileUrl.substring("file:".length());
+            }
+
+            return new JarFile(jarFileUrl);
+        }
+
+        return null;
+    }
+
+}

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProvider.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProvider.java?rev=1357494&r1=1357493&r2=1357494&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProvider.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProvider.java Thu Jul  5 08:49:50 2012
@@ -92,10 +92,26 @@ public abstract class AnnotationProvider
      * the "META-INF" directory (considered <code>applicationConfigurationResources)<code></li>
      * </ol>
      * 
+     * @deprecated 
      * @return
      */
+    @Deprecated
     public abstract Set<URL> getBaseUrls() throws IOException;
     
+    /**
+     * Same as getBaseUrls(), but with the ExternalContext reference.
+     * By default it calls to getBaseUrls()
+     * 
+     * @since 2.1.9, 2.0.15
+     * @param ctx
+     * @return
+     * @throws IOException 
+     */
+    public Set<URL> getBaseUrls(ExternalContext ctx) throws IOException
+    {
+        return getBaseUrls();
+    }
+    
     public AnnotationProvider getWrapped()
     {
         return null;

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProviderWrapper.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProviderWrapper.java?rev=1357494&r1=1357493&r2=1357494&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProviderWrapper.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/spi/AnnotationProviderWrapper.java Thu Jul  5 08:49:50 2012
@@ -52,4 +52,8 @@ public abstract class AnnotationProvider
         return getWrapped().getBaseUrls();
     }
     
+    public Set<URL> getBaseUrls(ExternalContext ctx) throws IOException
+    {
+        return getWrapped().getBaseUrls(ctx);
+    }
 }

Modified: myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/compiler/DefaultFaceletConfigResourceProvider.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/compiler/DefaultFaceletConfigResourceProvider.java?rev=1357494&r1=1357493&r2=1357494&view=diff
==============================================================================
--- myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/compiler/DefaultFaceletConfigResourceProvider.java (original)
+++ myfaces/core/trunk/impl/src/main/java/org/apache/myfaces/view/facelets/compiler/DefaultFaceletConfigResourceProvider.java Thu Jul  5 08:49:50 2012
@@ -25,9 +25,12 @@ import java.util.Collection;
 import java.util.List;
 
 import javax.faces.context.ExternalContext;
+import org.apache.myfaces.shared.config.MyfacesConfig;
 
 import org.apache.myfaces.shared.util.ClassUtils;
 import org.apache.myfaces.spi.FaceletConfigResourceProvider;
+import org.apache.myfaces.util.ContainerUtils;
+import org.apache.myfaces.config.util.GAEUtils;
 import org.apache.myfaces.view.facelets.util.Classpath;
 
 /**
@@ -52,13 +55,28 @@ public class DefaultFaceletConfigResourc
     {
         List<URL> urlSet = new ArrayList<URL>();
         
-        //Scan files inside META-INF ending with .faces-config.xml
-        URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACELET_TAGLIB_SUFFIX);
-        for (int i = 0; i < urls.length; i++)
+        String jarFilesToScanParam = MyfacesConfig.getCurrentInstance(context).getGaeJsfJarFiles();
+        jarFilesToScanParam = jarFilesToScanParam != null ? jarFilesToScanParam.trim() : null;
+        if (ContainerUtils.isRunningOnGoogleAppEngine(context) && 
+            jarFilesToScanParam != null &&
+            jarFilesToScanParam.length() > 0)
         {
-            urlSet.add(urls[i]);
+            Collection<URL> urlsGAE = GAEUtils.searchInWebLib(
+                    context, getClassLoader(), jarFilesToScanParam, META_INF_PREFIX, FACELET_TAGLIB_SUFFIX);
+            if (urlsGAE != null)
+            {
+                urlSet.addAll(urlsGAE);
+            }
+        }
+        else
+        {
+            //Scan files inside META-INF ending with .faces-config.xml
+            URL[] urls = Classpath.search(getClassLoader(), META_INF_PREFIX, FACELET_TAGLIB_SUFFIX);
+            for (int i = 0; i < urls.length; i++)
+            {
+                urlSet.add(urls[i]);
+            }
         }
-        
         return urlSet;
     }
 

Modified: myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java
URL: http://svn.apache.org/viewvc/myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java?rev=1357494&r1=1357493&r2=1357494&view=diff
==============================================================================
--- myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java (original)
+++ myfaces/core/trunk/shared/src/main/java/org/apache/myfaces/shared/config/MyfacesConfig.java Thu Jul  5 08:49:50 2012
@@ -390,6 +390,34 @@ public class MyfacesConfig
     public final static String INIT_PARAM_SUPPORT_JSP_AND_FACES_EL = "org.apache.myfaces.SUPPORT_JSP_AND_FACES_EL";
     public final static boolean INIT_PARAM_SUPPORT_JSP_AND_FACES_EL_DEFAULT = true;
     
+    /**
+     * When the application runs inside Google Application Engine container (GAE),
+     * indicate which jar files should be scanned for files (faces-config, facelets taglib
+     * or annotations). It accept simple wildcard patterns like myfavoritejsflib-*.jar or 
+     * myfavoritejsflib-1.1.?.jar. By default, all the classpath is scanned for files 
+     * annotations (so it adds an small delay on startup).
+     */
+    @JSFWebConfigParam(since = "2.1.8, 2.0.14", expectedValues="none, myfavoritejsflib-*.jar",
+            tags="performance, GAE")
+    public static final String INIT_PARAM_GAE_JSF_JAR_FILES = "org.apache.myfaces.GAE_JSF_JAR_FILES";
+    public final static String INIT_PARAM_GAE_JSF_JAR_FILES_DEFAULT = null;
+
+    /**
+     * When the application runs inside Google Application Engine container (GAE),
+     * indicate which jar files should be scanned for annotations. This param overrides
+     * org.apache.myfaces.GAE_JSF_JAR_FILES behavior that tries to find faces-config.xml or
+     * files ending with .faces-config.xml in /META-INF folder and if that so, try to
+     * find JSF annotations in the whole jar file. It accept simple wildcard patterns 
+     * like myfavoritejsflib-*.jar or myfavoritejsflib-1.1.?.jar.
+     * By default, all the classpath is scanned for annotations (so it adds an small
+     * delay on startup).
+     */
+    @JSFWebConfigParam(since = "2.1.8, 2.0.14", expectedValues="none, myfavoritejsflib-*.jar",
+            tags="performance, GAE")
+    public static final String INIT_PARAM_GAE_JSF_ANNOTATIONS_JAR_FILES = 
+            "org.apache.myfaces.GAE_JSF_ANNOTATIONS_JAR_FILES";
+    public final static String INIT_PARAM_GAE_JSF_ANNOTATIONS_JAR_FILES_DEFAULT = null;
+
     private boolean _prettyHtml;
     private boolean _detectJavascript;
     private boolean _allowJavascript;
@@ -419,6 +447,8 @@ public class MyfacesConfig
     private boolean _viewUniqueIdsCacheEnabled;
     private int _componentUniqueIdsCacheSize;
     private boolean _supportJSPAndFacesEL;
+    private String _gaeJsfJarFiles;
+    private String _gaeJsfAnnotationsJarFiles;
 
     private static final boolean TOMAHAWK_AVAILABLE;
     private static final boolean MYFACES_IMPL_AVAILABLE;
@@ -517,6 +547,8 @@ public class MyfacesConfig
         setViewUniqueIdsCacheEnabled(INIT_PARAM_VIEW_UNIQUE_IDS_CACHE_ENABLED_DEFAULT);
         setComponentUniqueIdsCacheSize(INIT_PARAM_COMPONENT_UNIQUE_IDS_CACHE_SIZE_DEFAULT);
         setSupportJSPAndFacesEL(INIT_PARAM_SUPPORT_JSP_AND_FACES_EL_DEFAULT);
+        setGaeJsfJarFiles(INIT_PARAM_GAE_JSF_JAR_FILES_DEFAULT);
+        setGaeJsfAnnotationsJarFiles(INIT_PARAM_GAE_JSF_ANNOTATIONS_JAR_FILES_DEFAULT);
     }
 
     private static MyfacesConfig createAndInitializeMyFacesConfig(ExternalContext extCtx)
@@ -619,6 +651,11 @@ public class MyfacesConfig
         myfacesConfig.setSupportJSPAndFacesEL(WebConfigParamUtils.getBooleanInitParameter(extCtx, 
                 INIT_PARAM_SUPPORT_JSP_AND_FACES_EL, INIT_PARAM_SUPPORT_JSP_AND_FACES_EL_DEFAULT));
         
+        myfacesConfig.setGaeJsfJarFiles(WebConfigParamUtils.getStringInitParameter(extCtx, 
+                INIT_PARAM_GAE_JSF_JAR_FILES, INIT_PARAM_GAE_JSF_JAR_FILES_DEFAULT));
+        myfacesConfig.setGaeJsfAnnotationsJarFiles(WebConfigParamUtils.getStringInitParameter(extCtx, 
+                INIT_PARAM_GAE_JSF_ANNOTATIONS_JAR_FILES, INIT_PARAM_GAE_JSF_ANNOTATIONS_JAR_FILES_DEFAULT));
+        
         if (TOMAHAWK_AVAILABLE)
         {
             myfacesConfig.setDetectJavascript(getBooleanInitParameter(extCtx, INIT_PARAM_DETECT_JAVASCRIPT,
@@ -1116,4 +1153,25 @@ public class MyfacesConfig
     {
         this._componentUniqueIdsCacheSize = componentUniqueIdsCacheSize;
     }
+
+    public String getGaeJsfJarFiles()
+    {
+        return _gaeJsfJarFiles;
+    }
+
+    public void setGaeJsfJarFiles(String gaeJsfJarFiles)
+    {
+        this._gaeJsfJarFiles = gaeJsfJarFiles;
+    }
+
+    public String getGaeJsfAnnotationsJarFiles()
+    {
+        return _gaeJsfAnnotationsJarFiles;
+    }
+
+    public void setGaeJsfAnnotationsJarFiles(String gaeJsfAnnotationsJarFiles)
+    {
+        this._gaeJsfAnnotationsJarFiles = gaeJsfAnnotationsJarFiles;
+    }
+
 }