/*
* Copyright 1999,2004 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.web.tomcat.service.jasper;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import javax.servlet.ServletContext;
import org.apache.jasper.Constants;
import org.apache.jasper.JasperException;
import org.apache.jasper.compiler.Localizer;
import org.apache.jasper.compiler.TldLocationsCache;
import org.apache.jasper.xmlparser.ParserUtils;
import org.apache.jasper.xmlparser.TreeNode;
import org.jboss.logging.Logger;
/**
* A prototype TagLibCache that allows one to obtain shared tlds from the
* jbossweb sar conf/tlds directory.
* @author Scott.Stark@jboss.org
* @version $Revision: 85945 $
*/
public class TagLibCache extends TldLocationsCache
{
private static final String WEB_XML = "/WEB-INF/web.xml";
private static final String JAR_FILE_SUFFIX = ".jar";
private static Logger log = Logger.getLogger(TagLibCache.class);
private ServletContext ctx;
private HashMap mappings;
private ArrayList tagLibJars;
public TagLibCache(ServletContext ctx, ArrayList tagLibJars)
{
super(ctx, true);
this.ctx = ctx;
this.tagLibJars = tagLibJars;
}
/**
* Gets the 'location' of the TLD associated with the given taglib 'uri'.
*
* Returns null if the uri is not associated with any tag library 'exposed'
* in the web application. A tag library is 'exposed' either explicitly in
* web.xml or implicitly via the uri tag in the TLD of a taglib deployed in a
* jar file (WEB-INF/lib).
* @param uri The taglib uri
* @return An array of two Strings: The first element denotes the real path
* to the TLD. If the path to the TLD points to a jar file, then the
* second element denotes the name of the TLD entry in the jar file.
* Returns null if the uri is not associated with any tag library
* 'exposed' in the web application.
*/
public String[] getLocation(String uri) throws JasperException
{
if (mappings == null)
init();
String[] locations = (String[]) mappings.get(uri);
return locations;
}
private synchronized void init() throws JasperException
{
if (mappings != null)
{
return;
}
HashMap tmpMappings = null;
try
{
tmpMappings = new HashMap();
processWebDotXml(tmpMappings);
loadStandardTlds(tmpMappings);
processTldsInFileSystem("/WEB-INF/", tmpMappings);
}
catch (Exception ex)
{
String msg = Localizer.getMessage("jsp.error.internal.tldinit", ex.getMessage());
throw new JasperException(msg, ex);
}
finally
{
mappings = tmpMappings;
}
}
/*
* Populates taglib map described in web.xml.
*/
protected void processWebDotXml(Map tmpMappings) throws Exception
{
InputStream is = null;
try
{
// Acquire input stream to web application deployment descriptor
String altDDName = (String) ctx.getAttribute(Constants.ALT_DD_ATTR);
if (altDDName != null)
{
try
{
is = new FileInputStream(altDDName);
}
catch (FileNotFoundException e)
{
log.warn(Localizer.getMessage("jsp.error.internal.filenotfound",
altDDName));
}
}
else
{
is = ctx.getResourceAsStream(WEB_XML);
if (is == null)
{
log.warn(Localizer.getMessage("jsp.error.internal.filenotfound",
WEB_XML));
}
}
if (is == null)
{
return;
}
// Parse the web application deployment descriptor
TreeNode webtld = null;
// altDDName is the absolute path of the DD
if (altDDName != null)
{
webtld = new ParserUtils().parseXMLDocument(altDDName, is);
}
else
{
webtld = new ParserUtils().parseXMLDocument(WEB_XML, is);
}
// Allow taglib to be an element of the root or jsp-config (JSP2.0)
TreeNode jspConfig = webtld.findChild("jsp-config");
if (jspConfig != null)
{
webtld = jspConfig;
}
Iterator taglibs = webtld.findChildren("taglib");
while (taglibs.hasNext())
{
// Parse the next <taglib> element
TreeNode taglib = (TreeNode) taglibs.next();
String tagUri = null;
String tagLoc = null;
TreeNode child = taglib.findChild("taglib-uri");
if (child != null)
tagUri = child.getBody();
child = taglib.findChild("taglib-location");
if (child != null)
tagLoc = child.getBody();
// Save this location if appropriate
if (tagLoc == null)
continue;
if (uriType(tagLoc) == NOROOT_REL_URI)
tagLoc = "/WEB-INF/" + tagLoc;
String tagLoc2 = null;
if (tagLoc.endsWith(JAR_FILE_SUFFIX))
{
tagLoc = ctx.getResource(tagLoc).toString();
tagLoc2 = "META-INF/taglib.tld";
}
tmpMappings.put(tagUri, new String[]{tagLoc, tagLoc2}); // SYNC
}
}
finally
{
if (is != null)
{
try
{
is.close();
}
catch (Throwable t)
{
}
}
}
}
protected void loadStandardTlds(Map tmpMappings) throws MalformedURLException // SYNC
{
if( tagLibJars.size() == 0 )
return;
// Locate the conf/web.xml
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL web = loader.getResource("server.xml");
URL sarURL = new URL(web, ".");
for(int n = 0; n < tagLibJars.size(); n ++)
{
String jarPath = (String) tagLibJars.get(n);
try
{
URL url = new URL(sarURL, jarPath);
String resourcePath = url.toString();
log.debug("Scanning for tlds in: "+resourcePath);
URLConnection conn = url.openConnection();
conn.setUseCaches(false);
scanJar(conn, resourcePath, true, tmpMappings); // SYNC
}
catch (Exception e)
{
log.debug("Failed to scan: "+jarPath, e);
}
}
}
/*
* Searches the filesystem under /WEB-INF for any TLD files, and adds
* an implicit map entry to the taglib map for any TLD that has a <uri>
* element.
*/
protected void processTldsInFileSystem(String startPath, Map tmpMappings) // SYNC
throws Exception
{
Set dirList = ctx.getResourcePaths(startPath);
if (dirList != null)
{
Iterator it = dirList.iterator();
while (it.hasNext())
{
String path = (String) it.next();
if (path.endsWith("/"))
{
processTldsInFileSystem(path, tmpMappings); // SYNC
}
if( path.endsWith(".jar") )
{
URL resURL = ctx.getResource(path);
URLConnection conn = resURL.openConnection();
conn.setUseCaches(false);
this.scanJar(conn, resURL.toString(), false, tmpMappings); // SYNC
}
else if ( path.endsWith(".tld") == true )
{
InputStream stream = ctx.getResourceAsStream(path);
String uri = null;
try
{
uri = getUriFromTld(path, stream);
}
finally
{
if (stream != null)
{
try
{
stream.close();
}
catch (Throwable t)
{
// do nothing
}
}
}
// Add implicit map entry only if its uri is not already
// present in the map
if (uri != null && tmpMappings.get(uri) == null) // SYNC
{
tmpMappings.put(uri, new String[]{path, null}); // SYNC
}
}
}
}
}
/**
* Scans the given JarInputStream for TLD files located in META-INF (or a
* subdirectory of it), adding an implicit map entry to the taglib map for
* any TLD that has a <uri> element.
* @param conn - the
* @param ignore true if any exceptions raised when processing the given JAR
* should be ignored, false otherwise
*/
private void scanJar(URLConnection conn, String resourcePath, boolean ignore, Map tmpMappings) // SYNC
throws JasperException, IOException
{
InputStream connIS = conn.getInputStream();
JarInputStream jis = new JarInputStream(connIS);
try
{
JarEntry entry = jis.getNextJarEntry();
while( entry != null )
{
String name = entry.getName();
if( name.endsWith(".tld") == false )
{
entry = jis.getNextJarEntry();
continue;
}
EntryInputStream eis = new EntryInputStream(jis);
String uri = getUriFromTld(resourcePath, eis);
// Add implicit map entry only if its uri is not already
// present in the map
if (uri != null && tmpMappings.get(uri) == null) // SYNC
{
tmpMappings.put(uri, new String[]{resourcePath, name}); // SYNC
}
entry = jis.getNextJarEntry();
}
}
catch (Exception ex)
{
if (!ignore)
{
throw new JasperException(ex);
}
}
finally
{
if( jis != null )
{
try
{
jis.close();
}
catch (Throwable t)
{
// ignore
}
}
if (connIS != null)
{
try
{
connIS.close();
}
catch (Throwable t)
{
// ignore
}
}
}
}
/*
* Returns the value of the uri element of the given TLD, or null if the
* given TLD does not contain any such element.
*/
private String getUriFromTld(String resourcePath, InputStream in)
throws JasperException
{
// Parse the tag library descriptor at the specified resource path
TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath, in);
TreeNode uri = tld.findChild("uri");
if (uri != null)
{
String body = uri.getBody();
if (body != null)
return body;
}
return null;
}
/**
* Used to ignore the close on the jar entry input stream since this
* closes the jar stream, not just the entry.
*/
static class EntryInputStream extends InputStream
{
private JarInputStream jis;
EntryInputStream(JarInputStream jis)
{
this.jis = jis;
}
public int read() throws IOException
{
return jis.read();
}
public int available() throws IOException
{
return jis.available();
}
public void close() throws IOException
{
}
public void reset() throws IOException
{
jis.reset();
}
public boolean markSupported()
{
return jis.markSupported();
}
public synchronized void mark(int readlimit)
{
jis.mark(readlimit);
}
public long skip(long n) throws IOException
{
return jis.skip(n);
}
public int read(byte b[], int off, int len) throws IOException
{
return jis.read(b, off, len);
}
}
}