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