EXTSCRIPT-154: Code Rewrite/Refactoring, adding the resource handlers, we donĀ“t do anything with those in this rewrite, maybe in the next one we will patch facelets in the impl directly to handle that.
git-svn-id: https://svn.apache.org/repos/asf/myfaces/extensions/scripting/trunk@1298758 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/AliasResourceMetaImpl.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/AliasResourceMetaImpl.java
new file mode 100644
index 0000000..71248e3
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/AliasResourceMetaImpl.java
@@ -0,0 +1,96 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+/**
+ * Contains the metadata information to reference a resource
+ *
+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
+ * @version $Revision: 700544 $ $Date: 2008-09-30 13:44:02 -0500 (Mar, 30 Sep 2008) $
+ */
+public class AliasResourceMetaImpl extends ResourceMetaImpl
+{
+ private String _realResourceName;
+
+ private boolean _couldContainValueExpressions;
+
+ public AliasResourceMetaImpl(String prefix, String libraryName, String libraryVersion,
+ String resourceName, String resourceVersion, String realResourceName, boolean couldContainValueExpressions)
+ {
+ super(prefix, libraryName, libraryVersion,
+ resourceName, resourceVersion);
+ _realResourceName = realResourceName;
+ _couldContainValueExpressions = couldContainValueExpressions;
+ }
+
+ public String getRealResourceName()
+ {
+ return _realResourceName;
+ }
+
+ public void setRealResourceName(String realResourceName)
+ {
+ _realResourceName = realResourceName;
+ }
+
+ @Override
+ public String getResourceIdentifier()
+ {
+ StringBuilder builder = new StringBuilder();
+ boolean firstSlashAdded = false;
+ if (getLocalePrefix() != null && getLocalePrefix().length() > 0)
+ {
+ builder.append(getLocalePrefix());
+ firstSlashAdded = true;
+ }
+ if (getLibraryName() != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(getLibraryName());
+ firstSlashAdded = true;
+ }
+ if (getLibraryVersion() != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(getLibraryVersion());
+ firstSlashAdded = true;
+ }
+ if (getRealResourceName() != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(getRealResourceName());
+ firstSlashAdded = true;
+ }
+ if (getResourceVersion() != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(getResourceVersion());
+ builder.append(
+ getRealResourceName().substring(getRealResourceName().lastIndexOf('.')));
+ firstSlashAdded = true;
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public boolean couldResourceContainValueExpressions()
+ {
+ return _couldContainValueExpressions;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/BaseResourceHandlerSupport.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/BaseResourceHandlerSupport.java
new file mode 100644
index 0000000..b51b2dd
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/BaseResourceHandlerSupport.java
@@ -0,0 +1,236 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import java.util.Map;
+
+/**
+ * A ResourceHandlerSupport implementation for use with standard Java Servlet engines,
+ * ie an engine that supports javax.servlet, and uses a standard web.xml file.
+ *
+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
+ * @version $Revision: 946779 $ $Date: 2010-05-20 15:31:42 -0500 (Jue, 20 May 2010) $
+ */
+public class BaseResourceHandlerSupport extends ResourceHandlerSupport
+{
+
+ /**
+ * Set the max time in miliseconds set on the "Expires" header for a resource.
+ * (default to one week in miliseconds or 604800000)
+ */
+ public static final String RESOURCE_MAX_TIME_EXPIRES = "org.apache.myfaces.RESOURCE_MAX_TIME_EXPIRES";
+
+ /**
+ * Identifies the FacesServlet mapping in the current request map.
+ */
+ private static final String CACHED_SERVLET_MAPPING =
+ BaseResourceHandlerSupport.class.getName() + ".CACHED_SERVLET_MAPPING";
+
+ private Long _startupTime;
+
+ private Long _maxTimeExpires;
+
+ public BaseResourceHandlerSupport()
+ {
+ _startupTime = System.currentTimeMillis();
+ }
+
+ public ResourceLoader[] getResourceLoaders()
+ {
+ return null;
+ }
+
+ public String calculateResourceBasePath(FacesContext facesContext)
+ {
+ FacesServletMapping mapping = getFacesServletMapping(facesContext);
+ ExternalContext externalContext = facesContext.getExternalContext();
+
+ if (mapping != null)
+ {
+ String resourceBasePath = null;
+ if (mapping.isExtensionMapping())
+ {
+ // Mapping using a suffix. In this case we have to strip
+ // the suffix. If we have a url like:
+ // http://localhost:8080/testjsf20/javax.faces.resource/imagen.jpg.jsf?ln=dojo
+ //
+ // The servlet path is /javax.faces.resource/imagen.jpg.jsf
+ //
+ // For obtain the resource name we have to remove the .jsf suffix and
+ // the prefix ResourceHandler.RESOURCE_IDENTIFIER
+ resourceBasePath = externalContext.getRequestServletPath();
+ int stripPoint = resourceBasePath.lastIndexOf('.');
+ if (stripPoint > 0)
+ {
+ resourceBasePath = resourceBasePath.substring(0, stripPoint);
+ }
+ }
+ else
+ {
+ // Mapping using prefix. In this case we have to strip
+ // the prefix used for mapping. If we have a url like:
+ // http://localhost:8080/testjsf20/faces/javax.faces.resource/imagen.jpg?ln=dojo
+ //
+ // The servlet path is /faces
+ // and the path info is /javax.faces.resource/imagen.jpg
+ //
+ // For obtain the resource name we have to remove the /faces prefix and
+ // then the prefix ResourceHandler.RESOURCE_IDENTIFIER
+ resourceBasePath = externalContext.getRequestPathInfo();
+ }
+ return resourceBasePath;
+ }
+ else
+ {
+ //If no mapping is detected, just return the
+ //information follows the servlet path but before
+ //the query string
+ return externalContext.getRequestPathInfo();
+ }
+ }
+
+ public boolean isExtensionMapping()
+ {
+ FacesServletMapping mapping = getFacesServletMapping(
+ FacesContext.getCurrentInstance());
+ if (mapping != null)
+ {
+ if (mapping.isExtensionMapping())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public String getMapping()
+ {
+ FacesServletMapping mapping = getFacesServletMapping(
+ FacesContext.getCurrentInstance());
+ if (mapping != null)
+ {
+ if (mapping.isExtensionMapping())
+ {
+ return mapping.getExtension();
+ }
+ else
+ {
+ return mapping.getPrefix();
+ }
+ }
+ return "";
+ }
+
+ /**
+ * Read the web.xml file that is in the classpath and parse its internals to
+ * figure out how the FacesServlet is mapped for the current webapp.
+ */
+ protected FacesServletMapping getFacesServletMapping(FacesContext context)
+ {
+ Map<Object, Object> attributes = context.getAttributes();
+
+ // Has the mapping already been determined during this request?
+ FacesServletMapping mapping = (FacesServletMapping) attributes.get(CACHED_SERVLET_MAPPING);
+ if (mapping == null)
+ {
+ ExternalContext externalContext = context.getExternalContext();
+ mapping = calculateFacesServletMapping(externalContext.getRequestServletPath(),
+ externalContext.getRequestPathInfo());
+
+ attributes.put(CACHED_SERVLET_MAPPING, mapping);
+ }
+ return mapping;
+ }
+
+ /**
+ * Determines the mapping of the FacesServlet in the web.xml configuration
+ * file. However, there is no need to actually parse this configuration file
+ * as runtime information is sufficient.
+ *
+ * @param servletPath The servletPath of the current request
+ * @param pathInfo The pathInfo of the current request
+ * @return the mapping of the FacesServlet in the web.xml configuration file
+ */
+ protected static FacesServletMapping calculateFacesServletMapping(
+ String servletPath, String pathInfo)
+ {
+ 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 FacesServletMapping.createPrefixMapping(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)
+ {
+ String extension = servletPath.substring(extensionPos);
+ return FacesServletMapping.createExtensionMapping(extension);
+ }
+ else
+ {
+ // There is no extension in the given servletPath and therefore
+ // we assume that it's an exact match using prefix-based mapping.
+ return FacesServletMapping.createPrefixMapping(servletPath);
+ }
+ }
+ }
+
+ public long getStartupTime()
+ {
+ return _startupTime;
+ }
+
+ public long getMaxTimeExpires()
+ {
+ if (_maxTimeExpires == null)
+ {
+ String time = FacesContext.getCurrentInstance().getExternalContext().getInitParameter(RESOURCE_MAX_TIME_EXPIRES);
+ if (time != null && time.length() > 0)
+ {
+ try
+ {
+ _maxTimeExpires = Long.parseLong(time);
+ }
+ catch (NumberFormatException e)
+ {
+ _maxTimeExpires = 604800000L;
+ }
+ }
+ else
+ {
+ _maxTimeExpires = 604800000L;
+ }
+ }
+ return _maxTimeExpires;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ClassLoaderResourceLoader.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ClassLoaderResourceLoader.java
new file mode 100644
index 0000000..200142b
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ClassLoaderResourceLoader.java
@@ -0,0 +1,479 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import org.apache.myfaces.extensions.scripting.core.util.ClassLoaderUtils;
+
+import javax.faces.application.ProjectStage;
+import javax.faces.context.FacesContext;
+import java.io.InputStream;
+import java.net.URL;
+
+/**
+ * A resource loader implementation which loads resources from the thread ClassLoader.
+ *
+ */
+public class ClassLoaderResourceLoader extends ResourceLoader
+{
+ /**
+ * It checks version like this: 1, 1_0, 1_0_0, 100_100
+ *
+ * Used on getLibraryVersion to filter resource directories
+ **/
+ //protected static Pattern VERSION_CHECKER = Pattern.compile("\\p{Digit}+(_\\p{Digit}*)*");
+
+ /**
+ * It checks version like this: /1.js, /1_0.js, /1_0_0.js, /100_100.js
+ *
+ * Used on getResourceVersion to filter resources
+ **/
+ //protected static Pattern RESOURCE_VERSION_CHECKER = Pattern.compile("/\\p{Digit}+(_\\p{Digit}*)*\\..*");
+
+ /*
+ private FileFilter _libraryFileFilter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if (pathname.isDirectory() && VERSION_CHECKER.matcher(pathname.getName()).matches())
+ {
+ return true;
+ }
+ return false;
+ }
+ };*/
+
+ /*
+ private FileFilter _resourceFileFilter = new FileFilter()
+ {
+ public boolean accept(File pathname)
+ {
+ if (pathname.isDirectory() && RESOURCE_VERSION_CHECKER.matcher(pathname.getName()).matches())
+ {
+ return true;
+ }
+ return false;
+ }
+ };*/
+
+ private final boolean _developmentStage;
+
+ public ClassLoaderResourceLoader(String prefix)
+ {
+ super(prefix);
+ _developmentStage = FacesContext.getCurrentInstance().isProjectStage(ProjectStage.Development);
+ }
+
+ @Override
+ public String getLibraryVersion(String path)
+ {
+ return null;
+ /*
+ String libraryVersion = null;
+ if (getPrefix() != null)
+ path = getPrefix() + '/' + path;
+
+ URL url = getClassLoader().getResource(path);
+
+ if (url == null)
+ {
+ // This library does not exists for this
+ // ResourceLoader
+ return null;
+ }
+
+ // The problem here is how to scan the directory. When a ClassLoader
+ // is used two cases could occur
+ // 1. The files are unpacked so we can use Url.toURI and crawl
+ // the directory using the api for files.
+ // 2. The files are packed in a jar. This case is more tricky,
+ // because we only have a URL. Checking the jar api we can use
+ // JarURLConnection (Sounds strange, but the api of
+ // URL.openConnection says that for a jar connection a
+ // JarURLConnection is returned). From this point we can access
+ // to the jar api and solve the algoritm.
+ if (url.getProtocol().equals("file"))
+ {
+ try
+ {
+ File directory = new File(url.toURI());
+ if (directory.isDirectory())
+ {
+ File[] versions = directory.listFiles(_libraryFileFilter);
+ for (int i = 0; i < versions.length; i++)
+ {
+ String version = versions[i].getName();
+ if (VERSION_CHECKER.matcher(version).matches())
+ {
+ if (libraryVersion == null)
+ {
+ libraryVersion = version;
+ }
+ else if (getVersionComparator().compare(libraryVersion, version) < 0)
+ {
+ libraryVersion = version;
+ }
+ }
+ }
+ }
+ }
+ catch (URISyntaxException e)
+ {
+ // Just return null, because library version cannot be
+ // resolved.
+ Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName());
+ if (log.isLoggable(Level.WARNING))
+ {
+ log.log(Level.WARNING, "url "+url.toString()+" cannot be translated to uri: "+e.getMessage(), e);
+ }
+ }
+ }
+ else if (isJarResourceProtocol(url.getProtocol()))
+ {
+ try
+ {
+ url = getClassLoader().getResource(path + '/');
+
+ if (url != null)
+ {
+ JarURLConnection conn = (JarURLConnection)url.openConnection();
+ // See DIGESTER-29 for related problem
+ conn.setUseCaches(false);
+
+ try
+ {
+ if (conn.getJarEntry().isDirectory())
+ {
+ // Unfortunately, we have to scan all entry files
+ // because there is no proper api to scan it as a
+ // directory tree.
+ JarFile file = conn.getJarFile();
+ for (Enumeration<JarEntry> en = file.entries(); en.hasMoreElements();)
+ {
+ JarEntry entry = en.nextElement();
+ String entryName = entry.getName();
+
+ if (entryName.startsWith(path + '/'))
+ {
+ if (entryName.length() == path.length() + 1)
+ {
+ // the same string, just skip it
+ continue;
+ }
+
+ if (entryName.charAt(entryName.length() - 1) != '/')
+ {
+ // Skip files
+ continue;
+ }
+
+ entryName = entryName.substring(path.length() + 1, entryName.length() - 1);
+
+ if (entryName.indexOf('/') >= 0)
+ {
+ // Inner Directory
+ continue;
+ }
+
+ String version = entryName;
+ if (VERSION_CHECKER.matcher(version).matches())
+ {
+ if (libraryVersion == null)
+ {
+ libraryVersion = version;
+ }
+ else if (getVersionComparator().compare(libraryVersion, version) < 0)
+ {
+ libraryVersion = version;
+ }
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ //See TRINIDAD-73
+ //just close the input stream again if
+ //by inspecting the entries the stream
+ //was let open.
+ try
+ {
+ conn.getInputStream().close();
+ }
+ catch (Exception exception)
+ {
+ // Ignored
+ }
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ // Just return null, because library version cannot be
+ // resolved.
+ Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName());
+ if (log.isLoggable(Level.WARNING))
+ {
+ log.log(Level.WARNING, "IOException when scanning for resource in jar file:", e);
+ }
+ }
+ }
+ return libraryVersion;
+ */
+ }
+
+ @Override
+ public InputStream getResourceInputStream(ResourceMeta resourceMeta)
+ {
+ if (getPrefix() != null && !"".equals(getPrefix()))
+ {
+ return getClassLoader().getResourceAsStream(getPrefix() + '/' + resourceMeta.getResourceIdentifier());
+ }
+ else
+ {
+ return getClassLoader().getResourceAsStream(resourceMeta.getResourceIdentifier());
+ }
+ }
+
+ @Override
+ public URL getResourceURL(ResourceMeta resourceMeta)
+ {
+ if (getPrefix() != null && !"".equals(getPrefix()))
+ {
+ return getClassLoader().getResource(getPrefix() + '/' + resourceMeta.getResourceIdentifier());
+ }
+ else
+ {
+ return getClassLoader().getResource(resourceMeta.getResourceIdentifier());
+ }
+ }
+
+ @Override
+ public String getResourceVersion(String path)
+ {
+ return null;
+ /*
+ String resourceVersion = null;
+
+ if (getPrefix() != null)
+ path = getPrefix() + '/' + path;
+
+ URL url = getClassLoader().getResource(path);
+
+ if (url == null)
+ {
+ // This library does not exists for this
+ // ResourceLoader
+ return null;
+ }
+
+ if (url.getProtocol().equals("file"))
+ {
+ try
+ {
+ File directory = new File(url.toURI());
+ if (directory.isDirectory())
+ {
+ File[] versions = directory.listFiles(_resourceFileFilter);
+ for (int i = 0; i < versions.length; i++)
+ {
+ String version = versions[i].getName();
+ if (resourceVersion == null)
+ {
+ resourceVersion = version;
+ }
+ else if (getVersionComparator().compare(resourceVersion, version) < 0)
+ {
+ resourceVersion = version;
+ }
+ }
+ //Since it is a directory and no version found set resourceVersion as invalid
+ if (resourceVersion == null)
+ {
+ resourceVersion = VERSION_INVALID;
+ }
+ }
+ }
+ catch (URISyntaxException e)
+ {
+ Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName());
+ if (log.isLoggable(Level.WARNING))
+ {
+ log.log(Level.WARNING, "url "+url.toString()+" cannot be translated to uri: "+e.getMessage(), e);
+ }
+ }
+ }
+ else if (isJarResourceProtocol(url.getProtocol()))
+ {
+ try
+ {
+ url = getClassLoader().getResource(path + '/');
+
+ if (url != null)
+ {
+ JarURLConnection conn = (JarURLConnection)url.openConnection();
+ // See DIGESTER-29 for related problem
+ conn.setUseCaches(false);
+
+ try
+ {
+ if (conn.getJarEntry().isDirectory())
+ {
+ // Unfortunately, we have to scan all entry files
+ JarFile file = conn.getJarFile();
+ for (Enumeration<JarEntry> en = file.entries(); en.hasMoreElements();)
+ {
+ JarEntry entry = en.nextElement();
+ String entryName = entry.getName();
+
+ if (entryName.startsWith(path + '/'))
+ {
+ if (entryName.length() == path.length() + 1)
+ {
+ // the same string, just skip it
+ continue;
+ }
+
+ entryName = entryName.substring(path.length());
+ if (RESOURCE_VERSION_CHECKER.matcher(entryName).matches())
+ {
+ String version = entryName.substring(1, entryName.lastIndexOf('.'));
+ if (resourceVersion == null)
+ {
+ resourceVersion = version;
+ }
+ else if (getVersionComparator().compare(resourceVersion, version) < 0)
+ {
+ resourceVersion = version;
+ }
+ }
+ }
+ }
+ if (resourceVersion == null)
+ {
+ resourceVersion = VERSION_INVALID;
+ }
+ }
+ }
+ finally
+ {
+ //See TRINIDAD-73
+ //just close the input stream again if
+ //by inspecting the entries the stream
+ //was let open.
+ try
+ {
+ conn.getInputStream().close();
+ }
+ catch (Exception exception)
+ {
+ // Ignored
+ }
+ }
+
+ }
+ }
+ catch (IOException e)
+ {
+ // Just return null, because library version cannot be
+ // resolved.
+ Logger log = Logger.getLogger(ClassLoaderResourceLoader.class.getName());
+ if (log.isLoggable(Level.WARNING))
+ {
+ log.log(Level.WARNING, "IOException when scanning for resource in jar file:", e);
+ }
+ }
+ }
+ return resourceVersion;
+ */
+ }
+
+ @Override
+ public ResourceMeta createResourceMeta(String prefix, String libraryName, String libraryVersion,
+ String resourceName, String resourceVersion)
+ {
+ if (_developmentStage && libraryName != null &&
+ ResourceLoaderUtils.JAVAX_FACES_LIBRARY_NAME.equals(libraryName) &&
+ ResourceLoaderUtils.JSF_JS_RESOURCE_NAME.equals(resourceName))
+ {
+ // InternalClassLoaderResourceLoader will serve it, so return null in this case.
+ return null;
+ } else if (_developmentStage && libraryName != null &&
+ ResourceLoaderUtils.MYFACES_LIBRARY_NAME.equals(libraryName) &&
+ ResourceLoaderUtils.MYFACES_JS_RESOURCE_NAME.equals(resourceName)) {
+ // InternalClassLoaderResourceLoader will serve it, so return null in this case.
+ return null;
+ } else
+ {
+ return new ResourceMetaImpl(prefix, libraryName, libraryVersion, resourceName, resourceVersion);
+ }
+ }
+
+ /**
+ * Returns the ClassLoader to use when looking up resources under the top level package. By default, this is the
+ * context class loader.
+ *
+ * @return the ClassLoader used to lookup resources
+ */
+ protected ClassLoader getClassLoader()
+ {
+ return ClassLoaderUtils.getDefaultClassLoader();
+ }
+
+ @Override
+ public boolean libraryExists(String libraryName)
+ {
+ if (getPrefix() != null && !"".equals(getPrefix()))
+ {
+ URL url = getClassLoader().getResource(getPrefix() + '/' + libraryName);
+ if (url != null)
+ {
+ return true;
+ }
+ }
+ else
+ {
+ URL url = getClassLoader().getResource(libraryName);
+ if (url != null)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * <p>Determines whether the given URL resource protocol refers to a JAR file. Note that
+ * BEA WebLogic and IBM WebSphere don't use the "jar://" protocol for some reason even
+ * though you can treat these resources just like normal JAR files, i.e. you can ignore
+ * the difference between these protocols after this method has returned.</p>
+ *
+ * @param protocol the URL resource protocol you want to check
+ *
+ * @return <code>true</code> if the given URL resource protocol refers to a JAR file,
+ * <code>false</code> otherwise
+ */
+ /*
+ private static boolean isJarResourceProtocol(String protocol)
+ {
+ // Websphere uses the protocol "wsjar://" and Weblogic uses the protocol "zip://".
+ return "jar".equals(protocol) || "wsjar".equals(protocol) || "zip".equals(protocol);
+ }*/
+
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ExternalContextResourceLoader.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ExternalContextResourceLoader.java
new file mode 100644
index 0000000..6e10fa7
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ExternalContextResourceLoader.java
@@ -0,0 +1,205 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import javax.faces.context.FacesContext;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * A resource loader implementation which loads resources from the webapp root. It uses the methods on ExternalContext
+ * for handle resources.
+ *
+ */
+public class ExternalContextResourceLoader extends ResourceLoader
+{
+ /**
+ * It checks version like this: /1/, /1_0/, /1_0_0/, /100_100/
+ *
+ * Used on getLibraryVersion to filter resource directories
+ **/
+ protected static Pattern VERSION_CHECKER = Pattern.compile("/\\p{Digit}+(_\\p{Digit}*)*/");
+
+ /**
+ * It checks version like this: /1.js, /1_0.js, /1_0_0.js, /100_100.js
+ *
+ * Used on getResourceVersion to filter resources
+ **/
+ protected static Pattern RESOURCE_VERSION_CHECKER = Pattern.compile("/\\p{Digit}+(_\\p{Digit}*)*\\..*");
+
+ public ExternalContextResourceLoader(String prefix)
+ {
+ super(prefix);
+ }
+
+ protected Set<String> getResourcePaths(String path)
+ {
+ return FacesContext.getCurrentInstance().getExternalContext().getResourcePaths(getPrefix() + '/' + path);
+ }
+
+ @Override
+ public String getResourceVersion(String path)
+ {
+ String resourceVersion = null;
+ Set<String> resourcePaths = this.getResourcePaths(path);
+ if (getPrefix() != null)
+ path = getPrefix() + '/' + path;
+
+ if (null != resourcePaths && !resourcePaths.isEmpty())
+ {
+ // resourceVersion = // execute the comment
+ // Look in the resourcePaths for versioned resources.
+ // If one or more versioned resources are found, take
+ // the one with the "highest" version number as the value
+ // of resourceVersion. If no versioned libraries
+ // are found, let resourceVersion remain null.
+ for (String resourcePath : resourcePaths)
+ {
+ String version = resourcePath.substring(path.length());
+
+ if (RESOURCE_VERSION_CHECKER.matcher(version).matches())
+ {
+ version = version.substring(1, version.lastIndexOf('.'));
+ if (resourceVersion == null)
+ {
+ resourceVersion = version;
+ }
+ else if (getVersionComparator().compare(resourceVersion, version) < 0)
+ {
+ resourceVersion = version;
+ }
+ }
+ }
+ //Since it is a directory and no version was found, set as invalid
+ if (resourceVersion == null)
+ {
+ resourceVersion = VERSION_INVALID;
+ }
+ }
+ return resourceVersion;
+ }
+
+ @Override
+ public String getLibraryVersion(String path)
+ {
+ String libraryVersion = null;
+ Set<String> libraryPaths = this.getResourcePaths(path);
+ path = getPrefix() + '/' + path;
+ if (null != libraryPaths && !libraryPaths.isEmpty())
+ {
+ // Look in the libraryPaths for versioned libraries.
+ // If one or more versioned libraries are found, take
+ // the one with the "highest" version number as the value
+ // of libraryVersion. If no versioned libraries
+ // are found, let libraryVersion remain null.
+
+ for (Iterator<String> it = libraryPaths.iterator(); it.hasNext();)
+ {
+ String libraryPath = it.next();
+ String version = libraryPath.substring(path.length());
+
+ if (VERSION_CHECKER.matcher(version).matches())
+ {
+ version = version.substring(1, version.length() - 1);
+ if (libraryVersion == null)
+ {
+ libraryVersion = version;
+ }
+ else if (getVersionComparator().compare(libraryVersion, version) < 0)
+ {
+ libraryVersion = version;
+ }
+ }
+ }
+ }
+ return libraryVersion;
+ }
+
+ @Override
+ public URL getResourceURL(ResourceMeta resourceMeta)
+ {
+ try
+ {
+ return FacesContext.getCurrentInstance().getExternalContext().getResource(
+ getPrefix() + '/' + resourceMeta.getResourceIdentifier());
+ }
+ catch (MalformedURLException e)
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public InputStream getResourceInputStream(ResourceMeta resourceMeta)
+ {
+ return FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream(
+ getPrefix() + '/' + resourceMeta.getResourceIdentifier());
+ }
+
+ @Override
+ public ResourceMeta createResourceMeta(String prefix, String libraryName, String libraryVersion,
+ String resourceName, String resourceVersion)
+ {
+ return new ResourceMetaImpl(prefix, libraryName, libraryVersion, resourceName, resourceVersion);
+ }
+
+ @Override
+ public boolean libraryExists(String libraryName)
+ {
+ if (getPrefix() != null && !"".equals(getPrefix()))
+ {
+ try
+ {
+ URL url =
+ FacesContext.getCurrentInstance().getExternalContext().getResource(
+ getPrefix() + '/' + libraryName);
+ if (url != null)
+ {
+ return true;
+ }
+ }
+ catch (MalformedURLException e)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ try
+ {
+
+ URL url = FacesContext.getCurrentInstance().getExternalContext().getResource(libraryName);
+
+ if (url != null)
+ {
+ return true;
+ }
+ }
+ catch (MalformedURLException e)
+ {
+ return false;
+ }
+ }
+ return false;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/FacesServletMapping.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/FacesServletMapping.java
new file mode 100644
index 0000000..aaa6e09
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/FacesServletMapping.java
@@ -0,0 +1,159 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+/**
+ * Represents a mapping entry of the FacesServlet in the web.xml
+ * configuration file.
+ */
+public class FacesServletMapping
+{
+
+ /**
+ * The path ("/faces", for example) which has been specified in the
+ * url-pattern of the FacesServlet mapping.
+ */
+ private String prefix;
+
+ /**
+ * The extension (".jsf", for example) which has been specified in the
+ * url-pattern of the FacesServlet mapping.
+ */
+ private String extension;
+
+ /**
+ * Creates a new FacesServletMapping object using prefix mapping.
+ *
+ * @param path The path ("/faces", for example) which has been specified
+ * in the url-pattern of the FacesServlet mapping.
+ * @return a newly created FacesServletMapping
+ */
+ public static FacesServletMapping createPrefixMapping(String path)
+ {
+ FacesServletMapping mapping = new FacesServletMapping();
+ mapping.setPrefix(path);
+ return mapping;
+ }
+
+ /**
+ * Creates a new FacesServletMapping object using extension mapping.
+ *
+ * @param path The extension (".jsf", for example) which has been
+ * specified in the url-pattern of the FacesServlet mapping.
+ * @return a newly created FacesServletMapping
+ */
+ public static FacesServletMapping createExtensionMapping(
+ String extension)
+ {
+ FacesServletMapping mapping = new FacesServletMapping();
+ mapping.setExtension(extension);
+ return mapping;
+ }
+
+ /**
+ * Returns the path ("/faces", for example) which has been specified in
+ * the url-pattern of the FacesServlet mapping. If this mapping is based
+ * on an extension, <code>null</code> will be returned. Note that this
+ * path is not the same as the specified url-pattern as the trailing
+ * "/*" is omitted.
+ *
+ * @return the path which has been specified in the url-pattern
+ */
+ public String getPrefix()
+ {
+ return prefix;
+ }
+
+ /**
+ * Sets the path ("/faces/", for example) which has been specified in
+ * the url-pattern.
+ *
+ * @param path The path which has been specified in the url-pattern
+ */
+ public void setPrefix(String path)
+ {
+ this.prefix = path;
+ }
+
+ /**
+ * Returns the extension (".jsf", for example) which has been specified
+ * in the url-pattern of the FacesServlet mapping. If this mapping is
+ * not based on an extension, <code>null</code> will be returned.
+ *
+ * @return the extension which has been specified in the url-pattern
+ */
+ public String getExtension()
+ {
+ return extension;
+ }
+
+ /**
+ * Sets the extension (".jsf", for example) which has been specified in
+ * the url-pattern of the FacesServlet mapping.
+ *
+ * @param extension The extension which has been specified in the url-pattern
+ */
+ public void setExtension(String extension)
+ {
+ this.extension = extension;
+ }
+
+ /**
+ * Indicates whether this mapping is based on an extension (e.g.
+ * ".jsp").
+ *
+ * @return <code>true</code>, if this mapping is based is on an
+ * extension, <code>false</code> otherwise
+ */
+ public boolean isExtensionMapping()
+ {
+ return extension != null;
+ }
+
+ /**
+ * Indicates whether this mapping is based on a prefix (e.g.
+ * /faces/*").
+ *
+ * @return <code>true</code>, if this mapping is based is on a
+ * prefix, <code>false</code> otherwise
+ */
+ public boolean isPrefixMapping()
+ {
+ return prefix != null;
+ }
+
+ /**
+ * Returns the url-pattern entry for this servlet mapping.
+ *
+ * @return the url-pattern entry for this servlet mapping
+ */
+ public String getUrlPattern()
+ {
+ if (isExtensionMapping())
+ {
+ return "*" + extension;
+ }
+ else
+ {
+ return prefix + "/*";
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerCache.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerCache.java
new file mode 100644
index 0000000..5939464
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerCache.java
@@ -0,0 +1,241 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import javax.faces.application.ProjectStage;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class ResourceHandlerCache
+{
+ private static final Logger log = Logger
+ .getLogger(ResourceHandlerCache.class.getName());
+
+ private Boolean _resourceCacheEnabled = null;
+ private Map<ResourceKey, ResourceValue> _resourceCacheMap = null;
+
+ private static final String RESOURCE_HANDLER_CACHE_SIZE_ATTRIBUTE = "org.apache.myfaces.RESOURCE_HANDLER_CACHE_SIZE";
+ private static final int RESOURCE_HANDLER_CACHE_DEFAULT_SIZE = 500;
+
+ private static final String RESOURCE_HANDLER_CACHE_ENABLED_ATTRIBUTE = "org.apache.myfaces.RESOURCE_HANDLER_CACHE_ENABLED";
+ private static final boolean RESOURCE_HANDLER_CACHE_ENABLED_DEFAULT = true;
+
+ public ResourceValue getResource(String resourceName, String libraryName,
+ String contentType)
+ {
+ if (!isResourceCachingEnabled() || _resourceCacheMap == null)
+ return null;
+
+ if (log.isLoggable(Level.FINE))
+ log.log(Level.FINE, "Attemping to get resource from cache for "
+ + resourceName);
+
+ ResourceKey key = new ResourceKey(resourceName, libraryName,
+ contentType);
+
+ return _resourceCacheMap.get(key);
+ }
+
+ public boolean containsResource(String resourceName, String libraryName,
+ String contentType)
+ {
+ if (!isResourceCachingEnabled() || _resourceCacheMap == null)
+ return false;
+ ResourceKey key = new ResourceKey(resourceName, libraryName,
+ contentType);
+ return _resourceCacheMap.containsKey(key);
+ }
+
+ public void putResource(String resourceName, String libraryName,
+ String contentType, ResourceMeta resource, ResourceLoader loader)
+ {
+ if (!isResourceCachingEnabled())
+ return;
+
+ if (log.isLoggable(Level.FINE))
+ log.log(Level.FINE, "Attemping to put resource to cache for "
+ + resourceName);
+
+ if (_resourceCacheMap == null)
+ {
+ if (log.isLoggable(Level.FINE))
+ log.log(Level.FINE, "Initializing resource cache map");
+ _resourceCacheMap = Collections
+ .synchronizedMap(new _ResourceMap<ResourceKey, ResourceValue>(
+ getMaxSize()));
+ }
+
+ _resourceCacheMap.put(new ResourceKey(resourceName, libraryName,
+ contentType), new ResourceValue(resource, loader));
+ }
+
+ private boolean isResourceCachingEnabled()
+ {
+ if (_resourceCacheEnabled == null)
+ {
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+
+ //first, check to make sure that ProjectStage is production, if not, skip caching
+ if (!facesContext.isProjectStage(ProjectStage.Production))
+ {
+ return _resourceCacheEnabled = Boolean.FALSE;
+ }
+
+ ExternalContext externalContext = facesContext.getExternalContext();
+ if (externalContext == null)
+ return false; //don't cache right now, but don't disable it yet either
+
+ //if in production, make sure that the cache is not explicitly disabled via context param
+ String configParam = externalContext
+ .getInitParameter(ResourceHandlerCache.RESOURCE_HANDLER_CACHE_ENABLED_ATTRIBUTE);
+ _resourceCacheEnabled = configParam == null ? ResourceHandlerCache.RESOURCE_HANDLER_CACHE_ENABLED_DEFAULT
+ : Boolean.parseBoolean(configParam);
+
+ if (log.isLoggable(Level.FINE))
+ {
+ log.log(Level.FINE, "MyFaces Resource Caching Enabled="
+ + _resourceCacheEnabled);
+ }
+ }
+ return _resourceCacheEnabled;
+ }
+
+ private int getMaxSize()
+ {
+ ExternalContext externalContext = FacesContext.getCurrentInstance()
+ .getExternalContext();
+
+ String configParam = externalContext == null ? null : externalContext
+ .getInitParameter(RESOURCE_HANDLER_CACHE_SIZE_ATTRIBUTE);
+ return configParam == null ? RESOURCE_HANDLER_CACHE_DEFAULT_SIZE
+ : Integer.parseInt(configParam);
+ }
+
+ public static class ResourceKey
+ {
+ private String resourceName;
+ private String libraryName;
+ private String contentType;
+
+ public ResourceKey(String resourceName, String libraryName,
+ String contentType)
+ {
+ this.resourceName = resourceName;
+ this.libraryName = libraryName;
+ this.contentType = contentType;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((contentType == null) ? 0 : contentType.hashCode());
+ result = prime * result
+ + ((libraryName == null) ? 0 : libraryName.hashCode());
+ result = prime * result
+ + ((resourceName == null) ? 0 : resourceName.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ ResourceKey other = (ResourceKey) obj;
+ if (contentType == null)
+ {
+ if (other.contentType != null)
+ return false;
+ }
+ else if (!contentType.equals(other.contentType))
+ return false;
+ if (libraryName == null)
+ {
+ if (other.libraryName != null)
+ return false;
+ }
+ else if (!libraryName.equals(other.libraryName))
+ return false;
+ if (resourceName == null)
+ {
+ if (other.resourceName != null)
+ return false;
+ }
+ else if (!resourceName.equals(other.resourceName))
+ return false;
+ return true;
+ }
+ }
+
+ public static class ResourceValue
+ {
+ private ResourceMeta resourceMeta;
+
+ private ResourceLoader resourceLoader;
+
+ public ResourceValue(ResourceMeta resourceMeta,
+ ResourceLoader resourceLoader)
+ {
+ super();
+ this.resourceMeta = resourceMeta;
+ this.resourceLoader = resourceLoader;
+ }
+
+ public ResourceMeta getResourceMeta()
+ {
+ return resourceMeta;
+ }
+
+ public ResourceLoader getResourceLoader()
+ {
+ return resourceLoader;
+ }
+ }
+
+ private static class _ResourceMap<K, V> extends LinkedHashMap<K, V>
+ {
+ private static final long serialVersionUID = 1L;
+ private int maxCapacity;
+
+ public _ResourceMap(int cacheSize)
+ {
+ // create map at max capacity and 1.1 load factor to avoid rehashing
+ super(cacheSize + 1, 1.1f, true);
+ maxCapacity = cacheSize;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<K, V> eldest)
+ {
+ return size() > maxCapacity;
+ }
+ }
+}
\ No newline at end of file
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerImpl.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerImpl.java
new file mode 100644
index 0000000..5f3ed59
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerImpl.java
@@ -0,0 +1,567 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import org.apache.myfaces.extensions.scripting.core.util.ClassLoaderUtils;
+import org.apache.myfaces.renderkit.ErrorPageWriter;
+
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.context.FacesContext;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author Simon Lessard (latest modification by $Author: slessard $)
+ *
+ * @version $Revision: 696515 $ $Date: 2008-09-17 19:37:53 -0500 (mer., 17 sept. 2008) $
+ */
+public abstract class ResourceHandlerImpl extends ResourceHandler
+{
+
+ private static final String IS_RESOURCE_REQUEST = "org.apache.myfaces.IS_RESOURCE_REQUEST";
+
+ private ResourceHandlerSupport _resourceHandlerSupport;
+
+ private ResourceHandlerCache _resourceHandlerCache;
+
+ //private static final Log log = LogFactory.getLog(ResourceHandlerImpl.class);
+ private static final Logger log = Logger.getLogger(ResourceHandlerImpl.class.getName());
+
+ private static final int _BUFFER_SIZE = 2048;
+
+ @Override
+ public Resource createResource(String resourceName)
+ {
+ return createResource(resourceName, null);
+ }
+
+ @Override
+ public Resource createResource(String resourceName, String libraryName)
+ {
+ return createResource(resourceName, libraryName, null);
+ }
+
+ @Override
+ public Resource createResource(String resourceName, String libraryName,
+ String contentType)
+ {
+ Resource resource = null;
+
+ if (contentType == null)
+ {
+ //Resolve contentType using ExternalContext.getMimeType
+ contentType = FacesContext.getCurrentInstance().getExternalContext().getMimeType(resourceName);
+ }
+
+ if(getResourceLoaderCache().containsResource(resourceName, libraryName, contentType))
+ {
+ ResourceHandlerCache.ResourceValue resourceValue = getResourceLoaderCache().getResource(resourceName, libraryName, contentType);
+ resource = new ResourceImpl(resourceValue.getResourceMeta(), resourceValue.getResourceLoader(),
+ getResourceHandlerSupport(), contentType);
+ }
+ else
+ {
+ for (ResourceLoader loader : getResourceHandlerSupport()
+ .getResourceLoaders())
+ {
+ ResourceMeta resourceMeta = deriveResourceMeta(loader,
+ resourceName, libraryName);
+
+ if (resourceMeta != null)
+ {
+ resource = new ResourceImpl(resourceMeta, loader,
+ getResourceHandlerSupport(), contentType);
+
+ getResourceLoaderCache().putResource(resourceName, libraryName, contentType, resourceMeta, loader);
+ break;
+ }
+ }
+ }
+
+ return resource;
+ }
+
+ /**
+ * This method try to create a ResourceMeta for a specific resource
+ * loader. If no library, or resource is found, just return null,
+ * so the algorithm in createResource can continue checking with the
+ * next registered ResourceLoader.
+ */
+ protected ResourceMeta deriveResourceMeta(ResourceLoader resourceLoader,
+ String resourceName, String libraryName)
+ {
+ String localePrefix = getLocalePrefixForLocateResource();
+ String resourceVersion = null;
+ String libraryVersion = null;
+ ResourceMeta resourceId = null;
+
+ //1. Try to locate resource in a localized path
+ if (localePrefix != null)
+ {
+ if (null != libraryName)
+ {
+ String pathToLib = localePrefix + '/' + libraryName;
+ libraryVersion = resourceLoader.getLibraryVersion(pathToLib);
+
+ if (null != libraryVersion)
+ {
+ String pathToResource = localePrefix + '/'
+ + libraryName + '/' + libraryVersion + '/'
+ + resourceName;
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ }
+ else
+ {
+ String pathToResource = localePrefix + '/'
+ + libraryName + '/' + resourceName;
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ }
+
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
+ {
+ resourceId = resourceLoader.createResourceMeta(localePrefix, libraryName,
+ libraryVersion, resourceName, resourceVersion);
+ }
+ }
+ else
+ {
+ resourceVersion = resourceLoader
+ .getResourceVersion(localePrefix + '/'+ resourceName);
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
+ {
+ resourceId = resourceLoader.createResourceMeta(localePrefix, null, null,
+ resourceName, resourceVersion);
+ }
+ }
+
+ if (resourceId != null)
+ {
+ URL url = resourceLoader.getResourceURL(resourceId);
+ if (url == null)
+ {
+ resourceId = null;
+ }
+ }
+ }
+
+ //2. Try to localize resource in a non localized path
+ if (resourceId == null)
+ {
+ if (null != libraryName)
+ {
+ libraryVersion = resourceLoader.getLibraryVersion(libraryName);
+
+ if (null != libraryVersion)
+ {
+ String pathToResource = (libraryName + '/' + libraryVersion
+ + '/' + resourceName);
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ }
+ else
+ {
+ String pathToResource = (libraryName + '/'
+ + resourceName);
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ }
+
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
+ {
+ resourceId = resourceLoader.createResourceMeta(null, libraryName,
+ libraryVersion, resourceName, resourceVersion);
+ }
+ }
+ else
+ {
+ resourceVersion = resourceLoader
+ .getResourceVersion(resourceName);
+
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion)))
+ {
+ resourceId = resourceLoader.createResourceMeta(null, null, null,
+ resourceName, resourceVersion);
+ }
+ }
+
+ if (resourceId != null)
+ {
+ URL url = resourceLoader.getResourceURL(resourceId);
+ if (url == null)
+ {
+ resourceId = null;
+ }
+ }
+ }
+
+ return resourceId;
+ }
+
+ @Override
+ public String getRendererTypeForResourceName(String resourceName)
+ {
+ if (resourceName.endsWith(".js"))
+ return "javax.faces.resource.Script";
+ else if (resourceName.endsWith(".css"))
+ return "javax.faces.resource.Stylesheet";
+ return null;
+ }
+
+ /**
+ * Handle the resource request, writing in the output.
+ *
+ * This method implements an algorithm semantically identical to
+ * the one described on the javadoc of ResourceHandler.handleResourceRequest
+ */
+ @Override
+ public void handleResourceRequest(FacesContext facesContext) throws IOException
+ {
+ try
+ {
+ String resourceBasePath = getResourceHandlerSupport()
+ .calculateResourceBasePath(facesContext);
+
+ if (resourceBasePath == null)
+ {
+ // No base name could be calculated, so no further
+ //advance could be done here. HttpServletResponse.SC_NOT_FOUND
+ //cannot be returned since we cannot extract the
+ //resource base name
+ return;
+ }
+
+ // We neet to get an instance of HttpServletResponse, but sometimes
+ // the response object is wrapped by several instances of
+ // ServletResponseWrapper (like ResponseSwitch).
+ // Since we are handling a resource, we can expect to get an
+ // HttpServletResponse.
+ Object response = facesContext.getExternalContext().getResponse();
+
+ //TODO merge the servlet response determination in
+ HttpServletResponse httpServletResponse = (HttpServletResponse) response;
+ if (httpServletResponse == null)
+ {
+ throw new IllegalStateException("Could not obtain an instance of HttpServletResponse.");
+ }
+
+ if (isResourceIdentifierExcluded(facesContext, resourceBasePath))
+ {
+ httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ String resourceName = null;
+ if (resourceBasePath.startsWith(ResourceHandler.RESOURCE_IDENTIFIER))
+ {
+ resourceName = resourceBasePath
+ .substring(ResourceHandler.RESOURCE_IDENTIFIER.length() + 1);
+ }
+ else
+ {
+ //Does not have the conditions for be a resource call
+ httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ String libraryName = facesContext.getExternalContext()
+ .getRequestParameterMap().get("ln");
+
+ Resource resource = null;
+ if (libraryName != null)
+ {
+ //log.info("libraryName=" + libraryName);
+ resource = facesContext.getApplication().getResourceHandler().createResource(resourceName, libraryName);
+ }
+ else
+ {
+ resource = facesContext.getApplication().getResourceHandler().createResource(resourceName);
+ }
+
+ if (resource == null)
+ {
+ httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ return;
+ }
+
+ if (!resource.userAgentNeedsUpdate(facesContext))
+ {
+ httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ return;
+ }
+
+ httpServletResponse.setContentType(resource.getContentType());
+
+ Map<String, String> headers = resource.getResponseHeaders();
+
+ for (Map.Entry<String, String> entry : headers.entrySet())
+ {
+ httpServletResponse.setHeader(entry.getKey(), entry.getValue());
+ }
+
+ //serve up the bytes (taken from trinidad ResourceServlet)
+ try
+ {
+ InputStream in = resource.getInputStream();
+ OutputStream out = httpServletResponse.getOutputStream();
+ byte[] buffer = new byte[_BUFFER_SIZE];
+
+ try
+ {
+ int count = pipeBytes(in, out, buffer);
+ //set the content lenght
+ httpServletResponse.setContentLength(count);
+ }
+ finally
+ {
+ try
+ {
+ in.close();
+ }
+ finally
+ {
+ out.close();
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ //TODO: Log using a localized message (which one?)
+ if (log.isLoggable(Level.SEVERE))
+ log.severe("Error trying to load resource " + resourceName
+ + " with library " + libraryName + " :"
+ + e.getMessage());
+ httpServletResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
+ }
+ }
+ catch (Throwable ex)
+ {
+ // handle the Throwable accordingly. Maybe generate an error page.
+ // FIXME we are creating a html error page for a non html request here
+ // shouln't we do something better? -=Jakob Korherr=-
+ ErrorPageWriter.handleThrowable(facesContext, ex);
+ }
+ }
+
+ /**
+ * Reads the specified input stream into the provided byte array storage and
+ * writes it to the output stream.
+ */
+ private static int pipeBytes(InputStream in, OutputStream out, byte[] buffer)
+ throws IOException
+ {
+ int count = 0;
+ int length;
+
+ while ((length = (in.read(buffer))) >= 0)
+ {
+ out.write(buffer, 0, length);
+ count += length;
+ }
+ return count;
+ }
+
+ @Override
+ public boolean isResourceRequest(FacesContext facesContext)
+ {
+ // Since this method could be called many times we save it
+ //on request map so the first time is calculated it remains
+ //alive until the end of the request
+ Boolean value = (Boolean) facesContext.getAttributes().get(IS_RESOURCE_REQUEST);
+
+ if (value != null && value)
+ {
+ //return the saved value
+ return value;
+ }
+ else
+ {
+ String resourceBasePath = getResourceHandlerSupport()
+ .calculateResourceBasePath(facesContext);
+
+ if (resourceBasePath != null
+ && resourceBasePath.startsWith(ResourceHandler.RESOURCE_IDENTIFIER))
+ {
+ facesContext.getAttributes().put(IS_RESOURCE_REQUEST, Boolean.TRUE);
+ return true;
+ }
+ else
+ {
+ facesContext.getAttributes().put(IS_RESOURCE_REQUEST, Boolean.FALSE);
+ return false;
+ }
+ }
+ }
+
+ protected String getLocalePrefixForLocateResource()
+ {
+ String localePrefix = null;
+ FacesContext context = FacesContext.getCurrentInstance();
+
+ String bundleName = context.getApplication().getMessageBundle();
+
+ if (null != bundleName)
+ {
+ Locale locale = context.getApplication().getViewHandler()
+ .calculateLocale(context);
+
+ ResourceBundle bundle = ResourceBundle
+ .getBundle(bundleName, locale, ClassLoaderUtils.getDefaultClassLoader());
+
+ if (bundle != null)
+ {
+ try
+ {
+ localePrefix = bundle.getString(ResourceHandler.LOCALE_PREFIX);
+ }
+ catch (MissingResourceException e)
+ {
+ // Ignore it and return null
+ }
+ }
+ }
+ return localePrefix;
+ }
+
+ private static ResourceBundle getBundle(FacesContext facesContext, Locale locale, String bundleName)
+ {
+ try
+ {
+ // First we try the JSF implementation class loader
+ return ResourceBundle.getBundle(bundleName, locale, facesContext.getClass().getClassLoader());
+ }
+ catch (MissingResourceException ignore1)
+ {
+ try
+ {
+ // Next we try the JSF API class loader
+ return ResourceBundle.getBundle(bundleName, locale, ResourceHandlerImpl.class.getClassLoader());
+ }
+ catch (MissingResourceException ignore2)
+ {
+ try
+ {
+ // Last resort is the context class loader
+ return ResourceBundle.getBundle(bundleName, locale, ClassLoaderUtils.getDefaultClassLoader());
+ }
+ catch (MissingResourceException damned)
+ {
+ return null;
+ }
+ }
+ }
+ }
+
+ protected boolean isResourceIdentifierExcluded(FacesContext context,
+ String resourceIdentifier)
+ {
+ String value = context.getExternalContext().getInitParameter(
+ RESOURCE_EXCLUDES_PARAM_NAME);
+ if (value == null)
+ {
+ value = RESOURCE_EXCLUDES_DEFAULT_VALUE;
+ }
+ //TODO: optimize this code
+ String[] extensions = value.split("\\s+");
+ for (int i = 0; i < extensions.length; i++)
+ {
+ if (resourceIdentifier.endsWith(extensions[i]))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if a library exists or not. This is done delegating
+ * to each ResourceLoader used, because each one has a different
+ * prefix and way to load resources.
+ *
+ */
+ @Override
+ public boolean libraryExists(String libraryName)
+ {
+ String localePrefix = getLocalePrefixForLocateResource();
+
+ String pathToLib = null;
+
+ if (localePrefix != null)
+ {
+ //Check with locale
+ pathToLib = localePrefix + '/' + libraryName;
+
+ for (ResourceLoader loader : getResourceHandlerSupport()
+ .getResourceLoaders())
+ {
+ if (loader.libraryExists(pathToLib))
+ {
+ return true;
+ }
+ }
+ }
+
+ //Check without locale
+ for (ResourceLoader loader : getResourceHandlerSupport()
+ .getResourceLoaders())
+ {
+ if (loader.libraryExists(libraryName))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param resourceHandlerSupport
+ * the resourceHandlerSupport to set
+ */
+ public void setResourceHandlerSupport(
+ ResourceHandlerSupport resourceHandlerSupport)
+ {
+ _resourceHandlerSupport = resourceHandlerSupport;
+ }
+
+ /**
+ * @return the resourceHandlerSupport
+ */
+ protected abstract ResourceHandlerSupport getResourceHandlerSupport();
+
+ private ResourceHandlerCache getResourceLoaderCache()
+ {
+ if (_resourceHandlerCache == null)
+ _resourceHandlerCache = new ResourceHandlerCache();
+ return _resourceHandlerCache;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerSupport.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerSupport.java
new file mode 100644
index 0000000..c4863da
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceHandlerSupport.java
@@ -0,0 +1,86 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import javax.faces.context.FacesContext;
+
+/**
+ * A utility class to isolate a ResourceHandler implementation from its
+ * underlying implementation
+ *
+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
+ * @version $Revision: 891494 $ $Date: 2009-12-16 19:42:18 -0500 (Mié, 16 Dic 2009) $
+ */
+public abstract class ResourceHandlerSupport
+{
+
+ /**
+ * Calculate the resource base path.
+ *
+ * It should extract a string like:
+ *
+ * ResourceHandler.RESOURCE_IDENTIFIER + '/' + getResourceName()
+ *
+ * For example:
+ *
+ * /javax.faces.resource/image.jpg
+ *
+ * This is used on ResourceHandler.handleResourceRequest()
+ *
+ */
+ public abstract String calculateResourceBasePath(FacesContext facesContext);
+
+ /**
+ * Return an array of resource loaders used to find resources
+ * using the standard. The order of ResourceLoaders define
+ * its precedence.
+ *
+ * @return
+ */
+ public abstract ResourceLoader[] getResourceLoaders();
+
+ /**
+ * Check if the mapping used is done using extensions (.xhtml, .jsf)
+ * or if it is not (/faces/*)
+ * @return
+ */
+ public abstract boolean isExtensionMapping();
+
+ /**
+ * Get the mapping used as prefix(/faces) or sufix(.jsf)
+ *
+ * @return
+ */
+ public abstract String getMapping();
+
+ /**
+ * Return the time when the app started. This is useful to set the
+ * "Last-Modified" header in some specific cases.
+ *
+ * @return
+ */
+ public abstract long getStartupTime();
+
+ /**
+ * Return the time that should be set on "Expires" header in a resource.
+ *
+ * @return
+ */
+ public abstract long getMaxTimeExpires();
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceImpl.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceImpl.java
new file mode 100644
index 0000000..9a631e6
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceImpl.java
@@ -0,0 +1,312 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import javax.el.ELContext;
+import javax.el.ELException;
+import javax.el.ValueExpression;
+import javax.faces.application.ProjectStage;
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.context.FacesContext;
+import javax.faces.event.ExceptionQueuedEvent;
+import javax.faces.event.ExceptionQueuedEventContext;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.net.URL;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Default implementation for resources
+ *
+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
+ * @version $Revision: 957739 $ $Date: 2010-06-24 17:27:40 -0500 (Jue, 24 Jun 2010) $
+ */
+public class ResourceImpl extends Resource {
+
+
+ private ResourceMeta _resourceMeta;
+ private ResourceLoader _resourceLoader;
+ private ResourceHandlerSupport _resourceHandlerSupport;
+
+ public ResourceImpl(ResourceMeta resourceMeta,
+ ResourceLoader resourceLoader, ResourceHandlerSupport support, String contentType) {
+ _resourceMeta = resourceMeta;
+ _resourceLoader = resourceLoader;
+ _resourceHandlerSupport = support;
+ setLibraryName(resourceMeta.getLibraryName());
+ setResourceName(resourceMeta.getResourceName());
+ setContentType(contentType);
+ }
+
+ public ResourceLoader getResourceLoader() {
+ return _resourceLoader;
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ if (couldResourceContainValueExpressions()) {
+ return new ValueExpressionFilterInputStream(
+ getResourceLoader().getResourceInputStream(_resourceMeta));
+ } else {
+ return getResourceLoader().getResourceInputStream(_resourceMeta);
+ }
+ }
+
+ private boolean couldResourceContainValueExpressions() {
+ if (_resourceMeta.couldResourceContainValueExpressions()) {
+ return true;
+ } else {
+ //By default only css resource contain value expressions
+ String contentType = getContentType();
+
+ return ("text/css".equals(contentType));
+ }
+ }
+
+ private class ValueExpressionFilterInputStream extends InputStream {
+ private PushbackInputStream delegate;
+
+ public ValueExpressionFilterInputStream(InputStream in) {
+ super();
+ delegate = new PushbackInputStream(in, 255);
+ }
+
+ @Override
+ public int read() throws IOException {
+ int c1 = delegate.read();
+
+ if (c1 == -1) return -1;
+
+ if (((char) c1) == '#') {
+ int c2 = delegate.read();
+ if (c2 == -1) return -1;
+ if (((char) c2) == '{') {
+ //It is a value expression. We need
+ //to look for a occurrence of } to
+ //extract the expression and evaluate it,
+ //the result should be unread.
+ List<Integer> expressionList = new ArrayList<Integer>();
+ int c3 = delegate.read();
+ while (c3 != -1 && ((char) c3) != '}') {
+ expressionList.add(c3);
+ c3 = delegate.read();
+ }
+
+ if (c3 == -1) {
+ //get back the data, because we can't
+ //extract any value expression
+ for (int i = 0; i < expressionList.size(); i++) {
+ delegate.unread(expressionList.get(i));
+ }
+ delegate.unread(c2);
+ return c1;
+ } else {
+ //EL expression found. Evaluate it and pushback
+ //the result into the stream
+ FacesContext context = FacesContext.getCurrentInstance();
+ ELContext elContext = context.getELContext();
+ try {
+ ValueExpression ve = context.getApplication().
+ getExpressionFactory().createValueExpression(
+ elContext,
+ "#{" + convertToExpression(expressionList) + "}",
+ String.class);
+ String value = (String) ve.getValue(elContext);
+
+ for (int i = value.length() - 1; i >= 0; i--) {
+ delegate.unread((int) value.charAt(i));
+ }
+ } catch (ELException e) {
+ ExceptionQueuedEventContext equecontext = new ExceptionQueuedEventContext(context, e, null);
+ context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, equecontext);
+
+ Logger log = Logger.getLogger(ResourceImpl.class.getName());
+ if (log.isLoggable(Level.SEVERE))
+ log.severe("Cannot evaluate EL expression " + convertToExpression(expressionList) + " in resource " + getLibraryName() + ":" + getResourceName());
+
+ delegate.unread(c3);
+ for (int i = expressionList.size() - 1; i >= 0; i--) {
+ delegate.unread(expressionList.get(i));
+ }
+ delegate.unread(c2);
+ return c1;
+ }
+
+ //read again
+ return delegate.read();
+ }
+ } else {
+ delegate.unread(c2);
+ return c1;
+ }
+ } else {
+ //just continue
+ return c1;
+ }
+ }
+
+ private String convertToExpression(List<Integer> expressionList) {
+ char[] exprArray = new char[expressionList.size()];
+
+ for (int i = 0; i < expressionList.size(); i++) {
+ exprArray[i] = (char) expressionList.get(i).intValue();
+ }
+ return String.valueOf(exprArray);
+ }
+ }
+
+ @Override
+ public String getRequestPath() {
+ String path;
+ if (_resourceHandlerSupport.isExtensionMapping()) {
+ path = ResourceHandler.RESOURCE_IDENTIFIER + '/' +
+ getResourceName() + _resourceHandlerSupport.getMapping();
+ } else {
+ String mapping = _resourceHandlerSupport.getMapping();
+ path = ResourceHandler.RESOURCE_IDENTIFIER + '/' + getResourceName();
+ path = (mapping == null) ? path : mapping + path;
+ }
+
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+ String metadata = null;
+ boolean useAmp = false;
+ if (getLibraryName() != null) {
+ metadata = "?ln=" + getLibraryName();
+ path = path + metadata;
+ useAmp = true;
+
+ if (!facesContext.isProjectStage(ProjectStage.Production)
+ && ResourceLoaderUtils.JSF_JS_RESOURCE_NAME.equals(getResourceName())
+ && ResourceLoaderUtils.JAVAX_FACES_LIBRARY_NAME.equals(getLibraryName())) {
+ // append &stage=?? for all ProjectStages except Production
+ path = path + "&stage=" + facesContext.getApplication().getProjectStage().toString();
+ }
+ }
+
+ return facesContext.getApplication().getViewHandler().getResourceURL(facesContext, path);
+ }
+
+ @Override
+ public Map<String, String> getResponseHeaders() {
+ FacesContext facesContext = FacesContext.getCurrentInstance();
+
+ if (facesContext.getApplication().getResourceHandler().isResourceRequest(facesContext)) {
+ Map<String, String> headers = new HashMap<String, String>();
+
+ long lastModified;
+ try {
+ lastModified = ResourceLoaderUtils.getResourceLastModified(this.getURL());
+ } catch (IOException e) {
+ lastModified = -1;
+ }
+
+ // Here we have two cases: If the file could contain EL Expressions
+ // the last modified time is the greatest value between application startup and
+ // the value from file.
+ if (this.couldResourceContainValueExpressions() &&
+ lastModified < _resourceHandlerSupport.getStartupTime()) {
+ lastModified = _resourceHandlerSupport.getStartupTime();
+ } else if (_resourceMeta instanceof AliasResourceMetaImpl &&
+ lastModified < _resourceHandlerSupport.getStartupTime()) {
+ // If the resource meta is aliased, the last modified time is the greatest
+ // value between application startup and the value from file.
+ lastModified = _resourceHandlerSupport.getStartupTime();
+ }
+
+ if (lastModified >= 0) {
+ headers.put("Last-Modified", ResourceLoaderUtils.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() + _resourceHandlerSupport.getMaxTimeExpires();
+ }
+ headers.put("Expires", ResourceLoaderUtils.formatDateHeader(expires));
+ }
+
+ return headers;
+ } else {
+ //No need to return headers
+ return Collections.emptyMap();
+ }
+ }
+
+ @Override
+ public URL getURL() {
+ return getResourceLoader().getResourceURL(_resourceMeta);
+ }
+
+ @Override
+ public boolean userAgentNeedsUpdate(FacesContext context) {
+ // 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 = context.getExternalContext().getRequestHeaderMap().get("If-Modified-Since");
+
+ if (ifModifiedSinceString == null) {
+ return true;
+ }
+
+ Long ifModifiedSince = ResourceLoaderUtils.parseDateHeader(ifModifiedSinceString);
+
+ if (ifModifiedSince == null) {
+ return true;
+ }
+
+ Long lastModified;
+ try {
+ lastModified = ResourceLoaderUtils.getResourceLastModified(this.getURL());
+ } catch (IOException exception) {
+ lastModified = -1L;
+ }
+
+ if (lastModified >= 0) {
+ if (this.couldResourceContainValueExpressions() &&
+ lastModified < _resourceHandlerSupport.getStartupTime()) {
+ lastModified = _resourceHandlerSupport.getStartupTime();
+ }
+
+ // 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;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceLoader.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceLoader.java
new file mode 100644
index 0000000..3e9f1b3
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceLoader.java
@@ -0,0 +1,155 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Comparator;
+
+/**
+ * Base class for resource loaders. Resource loaders can lookup resources
+ * as URLs from arbitrary locations, including JAR files.
+ *
+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
+ * @version $Revision: 882702 $ $Date: 2009-11-20 15:16:07 -0500 (Vie, 20 Nov 2009) $
+ */
+public abstract class ResourceLoader
+{
+
+ public static final String VERSION_INVALID = "INVALID";
+
+ private String _prefix;
+
+ public ResourceLoader(String prefix)
+ {
+ _prefix = prefix;
+ }
+
+ public abstract String getResourceVersion(String path);
+
+ /**
+ * Return the max available version found (if exists) or
+ * return null if no version available.
+ */
+ public abstract String getLibraryVersion(String path);
+
+ /**
+ * Return the max available version found (if exists) or
+ * return null if no version available.
+ */
+ public abstract URL getResourceURL(ResourceMeta resourceMeta);
+
+ public abstract InputStream getResourceInputStream(ResourceMeta resourceMeta);
+
+ public abstract ResourceMeta createResourceMeta(String prefix, String libraryName, String libraryVersion,
+ String resourceName, String resourceVersion);
+
+ public abstract boolean libraryExists(String libraryName);
+
+ private Comparator<String> _versionComparator = null;
+
+ protected Comparator<String> getVersionComparator()
+ {
+ if (_versionComparator == null)
+ {
+ _versionComparator = new VersionComparator();
+ }
+ return _versionComparator;
+ }
+
+ protected void setVersionComparator(Comparator<String> versionComparator)
+ {
+ _versionComparator = versionComparator;
+ }
+
+ public class VersionComparator implements Comparator<String>
+ {
+
+ public int compare(String s1, String s2)
+ {
+ int n1 = 0;
+ int n2 = 0;
+ String o1 = s1;
+ String o2 = s2;
+
+ boolean p1 = true;
+ boolean p2 = true;
+
+ while (n1 == n2 && (p1 || p2))
+ {
+ int i1 = o1.indexOf('_');
+ int i2 = o2.indexOf('_');
+ if (i1 < 0)
+ {
+ if (o1.length() > 0)
+ {
+ p1 = false;
+ n1 = Integer.valueOf(o1);
+ o1 = "";
+ }
+ else
+ {
+ p1 = false;
+ n1 = 0;
+ }
+ }
+ else
+ {
+ n1 = Integer.valueOf(o1.substring(0, i1));
+ o1 = o1.substring(i1 + 1);
+ }
+ if (i2 < 0)
+ {
+ if (o2.length() > 0)
+ {
+ p2 = false;
+ n2 = Integer.valueOf(o2);
+ o2 = "";
+ }
+ else
+ {
+ p2 = false;
+ n2 = 0;
+ }
+ }
+ else
+ {
+ n2 = Integer.valueOf(o2.substring(0, i2));
+ o2 = o2.substring(i2 + 1);
+ }
+ }
+
+ if (n1 == n2)
+ {
+ return s1.length() - s2.length();
+ }
+ return n1 - n2;
+ }
+ }
+
+ public String getPrefix()
+ {
+ return _prefix;
+ }
+
+ public void setPrefix(String prefix)
+ {
+ _prefix = prefix;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceLoaderUtils.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceLoaderUtils.java
new file mode 100644
index 0000000..54762f8
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceLoaderUtils.java
@@ -0,0 +1,125 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public class ResourceLoaderUtils {
+
+ public final static String JAVAX_FACES_LIBRARY_NAME = "javax.faces";
+ public final static String JSF_JS_RESOURCE_NAME = "jsf.js";
+
+ public final static String MYFACES_JS_RESOURCE_NAME = "oamSubmit.js";
+ public final static String MYFACES_JS_RESOURCE_NAME_UNCOMPRESSED = "oamSubmit-uncompressed.js";
+
+ public final static String MYFACES_LIBRARY_NAME = "org.apache.myfaces";
+ private final static String RENDERED_MYFACES_JS = "org.apache.myfaces.RENDERED_MYFACES_JS";
+
+ // TODO: In tomcat and jetty it is implemented a Flyweight pattern when converting
+ // date headers. For now it is better keep this stuff simple.
+ 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");
+
+ 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));
+ }
+
+ 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) {
+ ;
+ }
+ }
+ if (date == null) {
+ return null;
+ }
+ return new Long(date.getTime());
+ }
+
+ //Taken from trinidad URLUtils
+ 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 trinidad URLUtils
+ 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;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceMeta.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceMeta.java
new file mode 100644
index 0000000..475bec2
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceMeta.java
@@ -0,0 +1,43 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+/**
+ * Contains the metadata information to reference a resource
+ *
+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
+ * @version $Revision: 946779 $ $Date: 2010-05-20 15:31:42 -0500 (Jue, 20 May 2010) $
+ */
+public abstract class ResourceMeta
+{
+
+ public abstract String getLibraryName();
+
+ public abstract String getResourceName();
+
+ public abstract String getLocalePrefix();
+
+ public abstract String getLibraryVersion();
+
+ public abstract String getResourceVersion();
+
+ public abstract String getResourceIdentifier();
+
+ public abstract boolean couldResourceContainValueExpressions();
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceMetaImpl.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceMetaImpl.java
new file mode 100644
index 0000000..60259b4
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/ResourceMetaImpl.java
@@ -0,0 +1,116 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+/**
+ * Contains the metadata information to reference a resource
+ *
+ * @author Leonardo Uribe (latest modification by $Author: lu4242 $)
+ * @version $Revision: 700544 $ $Date: 2008-09-30 13:44:02 -0500 (Mar, 30 Sep 2008) $
+ */
+public class ResourceMetaImpl extends ResourceMeta
+{
+
+ private final String _prefix;
+ private final String _libraryName;
+ private final String _libraryVersion;
+ private final String _resourceName;
+ private final String _resourceVersion;
+
+ public ResourceMetaImpl(String prefix, String libraryName, String libraryVersion,
+ String resourceName, String resourceVersion)
+ {
+ _prefix = prefix;
+ _libraryName = libraryName;
+ _libraryVersion = libraryVersion;
+ _resourceName = resourceName;
+ _resourceVersion = resourceVersion;
+ }
+
+ public String getLibraryName()
+ {
+ return _libraryName;
+ }
+
+ public String getResourceName()
+ {
+ return _resourceName;
+ }
+
+ public String getLocalePrefix()
+ {
+ return _prefix;
+ }
+
+ public String getLibraryVersion()
+ {
+ return _libraryVersion;
+ }
+
+ public String getResourceVersion()
+ {
+ return _resourceVersion;
+ }
+
+ @Override
+ public String getResourceIdentifier()
+ {
+ StringBuilder builder = new StringBuilder();
+ boolean firstSlashAdded = false;
+ if (_prefix != null && _prefix.length() > 0)
+ {
+ builder.append(_prefix);
+ firstSlashAdded = true;
+ }
+ if (_libraryName != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(_libraryName);
+ firstSlashAdded = true;
+ }
+ if (_libraryVersion != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(_libraryVersion);
+ firstSlashAdded = true;
+ }
+ if (_resourceName != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(_resourceName);
+ firstSlashAdded = true;
+ }
+ if (_resourceVersion != null)
+ {
+ if (firstSlashAdded) builder.append('/');
+ builder.append(_resourceVersion);
+ builder.append(
+ _resourceName.substring(_resourceName.lastIndexOf('.')));
+ firstSlashAdded = true;
+ }
+
+ return builder.toString();
+ }
+
+ @Override
+ public boolean couldResourceContainValueExpressions()
+ {
+ return false;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceHandler.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceHandler.java
new file mode 100644
index 0000000..33f58eb
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceHandler.java
@@ -0,0 +1,207 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import org.apache.myfaces.extensions.scripting.api.Decorated;
+
+import javax.faces.application.Resource;
+import javax.faces.application.ResourceHandler;
+import javax.faces.context.FacesContext;
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * A simple delegating resource handler
+ * which is supposed to pick up resources from a
+ * given location if the Weaving configuration
+ * has the parameter present
+ */
+public class SourceResourceHandler extends ResourceHandlerImpl implements Decorated {
+
+ ResourceHandler _delegate = null;
+ ResourceHandlerSupport _sourceHandlerSupport = null;
+
+ public SourceResourceHandler(ResourceHandler delegate) {
+ _delegate = delegate;
+ }
+
+ public Resource createResource(String resourceName) {
+ Resource retVal = super.createResource(resourceName);
+ if (retVal != null)
+ return retVal;
+
+ return _delegate.createResource(resourceName);
+ }
+
+ public Resource createResource(String resourceName, String libraryName) {
+ Resource retVal = super.createResource(resourceName, libraryName);
+ if (retVal != null)
+ return retVal;
+
+ return _delegate.createResource(resourceName, libraryName);
+ }
+
+ public Resource createResource(String resourceName, String libraryName, String contentType) {
+ Resource retVal = super.createResource(resourceName, libraryName, contentType);
+
+ if (retVal != null)
+ return retVal;
+
+ return _delegate.createResource(resourceName, libraryName, contentType);
+ }
+
+ public String getRendererTypeForResourceName(String resourceName) {
+
+ String retVal = super.getRendererTypeForResourceName(resourceName);
+ if (retVal != null)
+ return retVal;
+
+ return _delegate.getRendererTypeForResourceName(resourceName);
+ }
+
+ public void handleResourceRequest(FacesContext context) throws IOException {
+ super.handleResourceRequest(context);
+ }
+
+ public boolean isResourceRequest(FacesContext context) {
+ return _delegate.isResourceRequest(context);
+ }
+
+ public boolean libraryExists(String libraryName) {
+ return _delegate.libraryExists(libraryName);
+ }
+
+ public ResourceHandler getDelegate() {
+ return _delegate;
+ }
+
+ @Override
+ /**
+ * central override of this class it provides a new handler
+ * support class which allows source pickups after the
+ * JSF2 specified restful algorithms
+ *
+ * @return A support instance which allows also source pickups from resources additionally to the
+ * default deployment and jar pickups
+ */
+ protected ResourceHandlerSupport getResourceHandlerSupport() {
+ if (_sourceHandlerSupport == null) {
+ _sourceHandlerSupport = new SourceResourceHandlerSupport();
+ }
+ return _sourceHandlerSupport;
+ }
+
+ protected ResourceMeta deriveResourceMeta(ResourceLoader resourceLoader,
+ String resourceName, String libraryName) {
+ String localePrefix = getLocalePrefixForLocateResource();
+ String resourceVersion = null;
+ String libraryVersion = null;
+ ResourceMeta resourceId = null;
+
+ //1. Try to locate resource in a localized path
+ if (localePrefix != null) {
+ if (null != libraryName) {
+ String pathToLib = localePrefix + '/' + libraryName;
+ libraryVersion = resourceLoader.getLibraryVersion(pathToLib);
+
+ if (null != libraryVersion) {
+ String pathToResource = localePrefix + '/'
+ + libraryName + '/' + libraryVersion + '/'
+ + resourceName;
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ } else {
+ String pathToResource = localePrefix + '/'
+ + libraryName + '/' + resourceName;
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ }
+
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion))) {
+ resourceId = resourceLoader.createResourceMeta(localePrefix, libraryName,
+ libraryVersion, resourceName, resourceVersion);
+ }
+ } else {
+ resourceVersion = resourceLoader
+ .getResourceVersion(localePrefix + '/' + resourceName);
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion))) {
+ resourceId = resourceLoader.createResourceMeta(localePrefix, null, null,
+ resourceName, resourceVersion);
+ } else if (resourceVersion == ResourceLoader.VERSION_INVALID) {
+ resourceId = resourceLoader.createResourceMeta(null, libraryName,
+ null, resourceName, null);
+ }
+ }
+
+ if (resourceId != null) {
+ URL url = resourceLoader.getResourceURL(resourceId);
+ if (url == null) {
+ resourceId = null;
+ }
+ }
+ }
+
+ //2. Try to localize resource in a non localized path
+ if (resourceId == null) {
+ if (null != libraryName) {
+ libraryVersion = resourceLoader.getLibraryVersion(libraryName);
+
+ if (null != libraryVersion) {
+ String pathToResource = (libraryName + '/' + libraryVersion
+ + '/' + resourceName);
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ } else {
+ String pathToResource = (libraryName + '/'
+ + resourceName);
+ resourceVersion = resourceLoader
+ .getResourceVersion(pathToResource);
+ }
+
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion))) {
+ resourceId = resourceLoader.createResourceMeta(null, libraryName,
+ libraryVersion, resourceName, resourceVersion);
+ } else if (resourceVersion == ResourceLoader.VERSION_INVALID) {
+ resourceId = resourceLoader.createResourceMeta(null, libraryName,
+ libraryVersion, resourceName, null);
+ }
+ } else {
+ resourceVersion = resourceLoader
+ .getResourceVersion(resourceName);
+
+ if (!(resourceVersion != null && ResourceLoader.VERSION_INVALID.equals(resourceVersion))) {
+ resourceId = resourceLoader.createResourceMeta(null, null, null,
+ resourceName, resourceVersion);
+ } else if (resourceVersion == ResourceLoader.VERSION_INVALID) {
+ resourceId = resourceLoader.createResourceMeta(null, null,
+ null, resourceName, null);
+ }
+ }
+
+ if (resourceId != null) {
+ URL url = resourceLoader.getResourceURL(resourceId);
+ if (url == null) {
+ resourceId = null;
+ }
+ }
+ }
+
+ return resourceId;
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceHandlerSupport.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceHandlerSupport.java
new file mode 100644
index 0000000..d4445e1
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceHandlerSupport.java
@@ -0,0 +1,82 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import javax.faces.context.FacesContext;
+
+/**
+ * impl specific handler support which attaches
+ * the source resource loader upfront before
+ * checking the other ones
+ * <p/>
+ * a delegate is used to limit the binding between
+ * the "parent" class and the child class
+ * in this case it simply makes sense to avoid any internal sideeffects
+ * between getResourceLoaders and the other methods
+ */
+public class SourceResourceHandlerSupport extends ResourceHandlerSupport {
+
+ private ResourceLoader[] _supportResourceLoaders;
+ private BaseResourceHandlerSupport _defaultSupport = new BaseResourceHandlerSupport();
+
+ @Override
+ /**
+ * Delivers a list of resource loaders in a binding order
+ * of the resource lookup algorithms
+ *
+ * @return a list of resource loaders with following order,
+ * <ul>
+ * <li>source lookup paths if present</li>
+ * <li>/resources directory</li>
+ * <li>META-INF/resources directory</li>
+ * </ul>
+ */
+ public ResourceLoader[] getResourceLoaders() {
+ if (_supportResourceLoaders == null) {
+ //The ExternalContextResourceLoader has precedence over
+ //ClassLoaderResourceLoader, so it goes first.
+ _supportResourceLoaders = new ResourceLoader[]{
+ new SourceResourceLoader("/resources"),
+ new ExternalContextResourceLoader("/resources"),
+ new ClassLoaderResourceLoader("META-INF/resources")
+ };
+ }
+ return _supportResourceLoaders;
+ }
+
+ public String calculateResourceBasePath(FacesContext facesContext) {
+ return _defaultSupport.calculateResourceBasePath(facesContext);
+ }
+
+ public boolean isExtensionMapping() {
+ return _defaultSupport.isExtensionMapping();
+ }
+
+ public String getMapping() {
+ return _defaultSupport.getMapping();
+ }
+
+ public long getStartupTime() {
+ return _defaultSupport.getStartupTime();
+ }
+
+ public long getMaxTimeExpires() {
+ return _defaultSupport.getMaxTimeExpires();
+ }
+}
diff --git a/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceLoader.java b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceLoader.java
new file mode 100644
index 0000000..8423f4e
--- /dev/null
+++ b/extscript-core-root/extscript-core/src/main/java/rewrite/org/apache/myfaces/extensions/scripting/jsf/resources/SourceResourceLoader.java
@@ -0,0 +1,103 @@
+/*
+ * 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 rewrite.org.apache.myfaces.extensions.scripting.jsf.resources;
+
+import org.apache.myfaces.extensions.scripting.core.util.WeavingContext;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * internal resource loader to be used with our custom resource handler
+ * the resource loader is added to the list of available loaders
+ * so that the resource gets loaded properly from our source path
+ * instead of the web context if present, the source paths as usual
+ * are picked up by our context params.
+ */
+public class SourceResourceLoader extends ExternalContextResourceLoader {
+
+ public SourceResourceLoader(String prefix) {
+ super(prefix);
+ }
+
+ @Override
+ protected Set<String> getResourcePaths(String path) {
+ List<String> resourceRoots = WeavingContext.getConfiguration().getResourceDirs();
+ if (resourceRoots == null || resourceRoots.isEmpty()) {
+ return Collections.EMPTY_SET;
+ }
+ Set<String> retVals = new HashSet<String>(resourceRoots.size());
+ //for (String resourceRoot : resourceRoots) {
+ retVals.add(getPrefix() + "/" + path);
+ //}
+ return retVals;
+ }
+
+ @Override
+ public URL getResourceURL(ResourceMeta resourceMeta) {
+ try {
+ List<String> resourceRoots = WeavingContext.getConfiguration().getResourceDirs();
+ if (resourceRoots == null || resourceRoots.isEmpty()) {
+ return super.getResourceURL(resourceMeta);
+ }
+
+ for (String resourceRoot : resourceRoots) {
+ File resourceFile = new File(resourceRoot + getPrefix() + "/" + resourceMeta.getLibraryName() + "/"+ resourceMeta.getResourceName());
+ if (resourceFile.exists()) {
+ return resourceFile.toURI().toURL();
+ }
+ }
+
+ return super.getResourceURL(resourceMeta);
+ }
+ catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public InputStream getResourceInputStream(ResourceMeta resourceMeta) {
+ try {
+ List<String> resourceRoots = WeavingContext.getConfiguration().getResourceDirs();
+ if (resourceRoots == null || resourceRoots.isEmpty()) {
+ return super.getResourceInputStream(resourceMeta);
+ }
+
+ for (String resourceRoot : resourceRoots) {
+ File resourceFile = new File(resourceRoot + getPrefix() + "/" +resourceMeta.getLibraryName() + "/"+ resourceMeta.getResourceName());
+ if (resourceFile.exists()) {
+ return new FileInputStream(resourceFile);
+ }
+ }
+
+ return super.getResourceInputStream(resourceMeta);
+ }
+ catch (IOException e) {
+ return null;
+ }
+ }
+}