001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.catalina.util;
018:
019: import java.io.File;
020: import java.io.FileInputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.NoSuchElementException;
027: import java.util.ResourceBundle;
028: import java.util.StringTokenizer;
029: import java.util.jar.JarInputStream;
030: import java.util.jar.Manifest;
031:
032: import javax.naming.Binding;
033: import javax.naming.NamingEnumeration;
034: import javax.naming.NamingException;
035: import javax.naming.directory.DirContext;
036:
037: import org.apache.catalina.core.StandardContext;
038: import org.apache.naming.resources.Resource;
039:
040: /**
041: * Ensures that all extension dependies are resolved for a WEB application
042: * are met. This class builds a master list of extensions available to an
043: * applicaiton and then validates those extensions.
044: *
045: * See http://java.sun.com/j2se/1.4/docs/guide/extensions/spec.html for
046: * a detailed explanation of the extension mechanism in Java.
047: *
048: * @author Greg Murray
049: * @author Justyna Horwat
050: * @version $Revision: 1.12 $ $Date: 2004/04/16 11:01:11 $
051: *
052: */
053: public final class ExtensionValidator {
054:
055: private static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory
056: .getLog(ExtensionValidator.class);
057:
058: /**
059: * The string resources for this package.
060: */
061: private static StringManager sm = StringManager
062: .getManager("org.apache.catalina.util");
063:
064: private static HashMap containerAvailableExtensions = null;
065: private static ArrayList containerManifestResources = new ArrayList();
066: private static ResourceBundle messages = null;
067:
068: // ----------------------------------------------------- Static Initializer
069:
070: /**
071: * This static initializer loads the container level extensions that are
072: * available to all web applications. This method scans all extension
073: * directories available via the "java.ext.dirs" System property.
074: *
075: * The System Class-Path is also scanned for jar files that may contain
076: * available extensions.
077: */
078: static {
079:
080: // check for container level optional packages
081: String systemClasspath = System.getProperty("java.class.path");
082:
083: StringTokenizer strTok = new StringTokenizer(systemClasspath,
084: File.pathSeparator);
085:
086: // build a list of jar files in the classpath
087: while (strTok.hasMoreTokens()) {
088: String classpathItem = strTok.nextToken();
089: if (classpathItem.toLowerCase().endsWith(".jar")) {
090: File item = new File(classpathItem);
091: if (item.exists()) {
092: try {
093: addSystemResource(item);
094: } catch (IOException e) {
095: log
096: .error(sm.getString(
097: "extensionValidator.failload",
098: item), e);
099: }
100: }
101: }
102: }
103:
104: // add specified folders to the list
105: addFolderList("java.ext.dirs");
106: addFolderList("catalina.ext.dirs");
107:
108: }
109:
110: // --------------------------------------------------------- Public Methods
111:
112: /**
113: * Runtime validation of a Web Applicaiton.
114: *
115: * This method uses JNDI to look up the resources located under a
116: * <code>DirContext</code>. It locates Web Application MANIFEST.MF
117: * file in the /META-INF/ directory of the application and all
118: * MANIFEST.MF files in each JAR file located in the WEB-INF/lib
119: * directory and creates an <code>ArrayList</code> of
120: * <code>ManifestResorce<code> objects. These objects are then passed
121: * to the validateManifestResources method for validation.
122: *
123: * @param dirContext The JNDI root of the Web Application
124: * @param context The context from which the Logger and path to the
125: * application
126: *
127: * @return true if all required extensions satisfied
128: */
129: public static synchronized boolean validateApplication(
130: DirContext dirContext, StandardContext context)
131: throws IOException {
132:
133: String appName = context.getPath();
134: ArrayList appManifestResources = new ArrayList();
135: ManifestResource appManifestResource = null;
136: // If the application context is null it does not exist and
137: // therefore is not valid
138: if (dirContext == null)
139: return false;
140: // Find the Manifest for the Web Applicaiton
141: InputStream inputStream = null;
142: try {
143: NamingEnumeration wne = dirContext
144: .listBindings("/META-INF/");
145: Binding binding = (Binding) wne.nextElement();
146: if (binding.getName().toUpperCase().equals("MANIFEST.MF")) {
147: Resource resource = (Resource) dirContext
148: .lookup("/META-INF/" + binding.getName());
149: inputStream = resource.streamContent();
150: Manifest manifest = new Manifest(inputStream);
151: inputStream.close();
152: inputStream = null;
153: ManifestResource mre = new ManifestResource(
154: sm
155: .getString("extensionValidator.web-application-manifest"),
156: manifest, ManifestResource.WAR);
157: appManifestResources.add(mre);
158: }
159: } catch (NamingException nex) {
160: // Application does not contain a MANIFEST.MF file
161: } catch (NoSuchElementException nse) {
162: // Application does not contain a MANIFEST.MF file
163: } finally {
164: if (inputStream != null) {
165: try {
166: inputStream.close();
167: } catch (Throwable t) {
168: // Ignore
169: }
170: }
171: }
172:
173: // Locate the Manifests for all bundled JARs
174: NamingEnumeration ne = null;
175: try {
176: if (dirContext != null) {
177: ne = dirContext.listBindings("WEB-INF/lib/");
178: }
179: while ((ne != null) && ne.hasMoreElements()) {
180: Binding binding = (Binding) ne.nextElement();
181: if (!binding.getName().toLowerCase().endsWith(".jar")) {
182: continue;
183: }
184: Resource resource = (Resource) dirContext
185: .lookup("/WEB-INF/lib/" + binding.getName());
186: Manifest jmanifest = getManifest(resource
187: .streamContent());
188: if (jmanifest != null) {
189: ManifestResource mre = new ManifestResource(binding
190: .getName(), jmanifest,
191: ManifestResource.APPLICATION);
192: appManifestResources.add(mre);
193: }
194: }
195: } catch (NamingException nex) {
196: // Jump out of the check for this application because it
197: // has no resources
198: }
199:
200: return validateManifestResources(appName, appManifestResources);
201: }
202:
203: /**
204: * Checks to see if the given system JAR file contains a MANIFEST, and adds
205: * it to the container's manifest resources.
206: *
207: * @param jarFile The system JAR whose manifest to add
208: */
209: public static void addSystemResource(File jarFile)
210: throws IOException {
211: Manifest manifest = getManifest(new FileInputStream(jarFile));
212: if (manifest != null) {
213: ManifestResource mre = new ManifestResource(jarFile
214: .getAbsolutePath(), manifest,
215: ManifestResource.SYSTEM);
216: containerManifestResources.add(mre);
217: }
218: }
219:
220: // -------------------------------------------------------- Private Methods
221:
222: /**
223: * Validates a <code>ArrayList</code> of <code>ManifestResource</code>
224: * objects. This method requires an application name (which is the
225: * context root of the application at runtime).
226: *
227: * <code>false</false> is returned if the extension dependencies
228: * represented by any given <code>ManifestResource</code> objects
229: * is not met.
230: *
231: * This method should also provide static validation of a Web Applicaiton
232: * if provided with the necessary parameters.
233: *
234: * @param appName The name of the Application that will appear in the
235: * error messages
236: * @param resources A list of <code>ManifestResource</code> objects
237: * to be validated.
238: *
239: * @return true if manifest resource file requirements are met
240: */
241: private static boolean validateManifestResources(String appName,
242: ArrayList resources) {
243: boolean passes = true;
244: int failureCount = 0;
245: HashMap availableExtensions = null;
246:
247: Iterator it = resources.iterator();
248: while (it.hasNext()) {
249: ManifestResource mre = (ManifestResource) it.next();
250: ArrayList requiredList = mre.getRequiredExtensions();
251: if (requiredList == null) {
252: continue;
253: }
254:
255: // build the list of available extensions if necessary
256: if (availableExtensions == null) {
257: availableExtensions = buildAvailableExtensionsMap(resources);
258: }
259:
260: // load the container level resource map if it has not been built
261: // yet
262: if (containerAvailableExtensions == null) {
263: containerAvailableExtensions = buildAvailableExtensionsMap(containerManifestResources);
264: }
265:
266: // iterate through the list of required extensions
267: Iterator rit = requiredList.iterator();
268: while (rit.hasNext()) {
269: Extension requiredExt = (Extension) rit.next();
270: String extId = requiredExt.getUniqueId();
271: // check the applicaion itself for the extension
272: if (availableExtensions != null
273: && availableExtensions.containsKey(extId)) {
274: Extension targetExt = (Extension) availableExtensions
275: .get(extId);
276: if (targetExt.isCompatibleWith(requiredExt)) {
277: requiredExt.setFulfilled(true);
278: }
279: // check the container level list for the extension
280: } else if (containerAvailableExtensions != null
281: && containerAvailableExtensions
282: .containsKey(extId)) {
283: Extension targetExt = (Extension) containerAvailableExtensions
284: .get(extId);
285: if (targetExt.isCompatibleWith(requiredExt)) {
286: requiredExt.setFulfilled(true);
287: }
288: } else {
289: // Failure
290: log
291: .info(sm
292: .getString(
293: "extensionValidator.extension-not-found-error",
294: appName, mre
295: .getResourceName(),
296: requiredExt
297: .getExtensionName()));
298: passes = false;
299: failureCount++;
300: }
301: }
302: }
303:
304: if (!passes) {
305: log.info(sm.getString(
306: "extensionValidator.extension-validation-error",
307: appName, failureCount + ""));
308: }
309:
310: return passes;
311: }
312:
313: /*
314: * Build this list of available extensions so that we do not have to
315: * re-build this list every time we iterate through the list of required
316: * extensions. All available extensions in all of the
317: * <code>MainfestResource</code> objects will be added to a
318: * <code>HashMap</code> which is returned on the first dependency list
319: * processing pass.
320: *
321: * The key is the name + implementation version.
322: *
323: * NOTE: A list is built only if there is a dependency that needs
324: * to be checked (performance optimization).
325: *
326: * @param resources A list of <code>ManifestResource</code> objects
327: *
328: * @return HashMap Map of available extensions
329: */
330: private static HashMap buildAvailableExtensionsMap(
331: ArrayList resources) {
332:
333: HashMap availableMap = null;
334:
335: Iterator it = resources.iterator();
336: while (it.hasNext()) {
337: ManifestResource mre = (ManifestResource) it.next();
338: HashMap map = mre.getAvailableExtensions();
339: if (map != null) {
340: Iterator values = map.values().iterator();
341: while (values.hasNext()) {
342: Extension ext = (Extension) values.next();
343: if (availableMap == null) {
344: availableMap = new HashMap();
345: availableMap.put(ext.getUniqueId(), ext);
346: } else if (!availableMap.containsKey(ext
347: .getUniqueId())) {
348: availableMap.put(ext.getUniqueId(), ext);
349: }
350: }
351: }
352: }
353:
354: return availableMap;
355: }
356:
357: /**
358: * Return the Manifest from a jar file or war file
359: *
360: * @param inStream Input stream to a WAR or JAR file
361: * @return The WAR's or JAR's manifest
362: */
363: private static Manifest getManifest(InputStream inStream)
364: throws IOException {
365:
366: Manifest manifest = null;
367: JarInputStream jin = null;
368:
369: try {
370: jin = new JarInputStream(inStream);
371: manifest = jin.getManifest();
372: jin.close();
373: jin = null;
374: } finally {
375: if (jin != null) {
376: try {
377: jin.close();
378: } catch (Throwable t) {
379: // Ignore
380: }
381: }
382: }
383:
384: return manifest;
385: }
386:
387: /**
388: * Add the JARs specified to the extension list.
389: */
390: private static void addFolderList(String property) {
391:
392: // get the files in the extensions directory
393: String extensionsDir = System.getProperty(property);
394: if (extensionsDir != null) {
395: StringTokenizer extensionsTok = new StringTokenizer(
396: extensionsDir, File.pathSeparator);
397: while (extensionsTok.hasMoreTokens()) {
398: File targetDir = new File(extensionsTok.nextToken());
399: if (!targetDir.exists() || !targetDir.isDirectory()) {
400: continue;
401: }
402: File[] files = targetDir.listFiles();
403: for (int i = 0; i < files.length; i++) {
404: if (files[i].getName().toLowerCase().endsWith(
405: ".jar")) {
406: try {
407: addSystemResource(files[i]);
408: } catch (IOException e) {
409: log.error(sm.getString(
410: "extensionValidator.failload",
411: files[i]), e);
412: }
413: }
414: }
415: }
416: }
417:
418: }
419:
420: }
|