1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.catalina.util;
19
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.NoSuchElementException;
27 import java.util.StringTokenizer;
28 import java.util.jar.JarInputStream;
29 import java.util.jar.Manifest;
30
31 import javax.naming.Binding;
32 import javax.naming.NamingEnumeration;
33 import javax.naming.NamingException;
34 import javax.naming.directory.DirContext;
35
36 import org.apache.catalina.core.StandardContext;
37 import org.apache.naming.resources.Resource;
38
39
40 /**
41 * Ensures that all extension dependies are resolved for a WEB application
42 * are met. This class builds a master list of extensions available to an
43 * applicaiton and then validates those extensions.
44 *
45 * See http://java.sun.com/j2se/1.4/docs/guide/extensions/spec.html for
46 * a detailed explanation of the extension mechanism in Java.
47 *
48 * @author Greg Murray
49 * @author Justyna Horwat
50 * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
51 *
52 */
53 public final class ExtensionValidator {
54
55 private static org.apache.juli.logging.Log log=
56 org.apache.juli.logging.LogFactory.getLog(ExtensionValidator.class);
57
58 /**
59 * The string resources for this package.
60 */
61 private static StringManager sm =
62 StringManager.getManager("org.apache.catalina.util");
63
64 private static ArrayList containerAvailableExtensions = null;
65 private static ArrayList containerManifestResources = new ArrayList();
66
67
68 // ----------------------------------------------------- Static Initializer
69
70
71 /**
72 * This static initializer loads the container level extensions that are
73 * available to all web applications. This method scans all extension
74 * directories available via the "java.ext.dirs" System property.
75 *
76 * The System Class-Path is also scanned for jar files that may contain
77 * available extensions.
78 */
79 static {
80
81 // check for container level optional packages
82 String systemClasspath = System.getProperty("java.class.path");
83
84 StringTokenizer strTok = new StringTokenizer(systemClasspath,
85 File.pathSeparator);
86
87 // build a list of jar files in the classpath
88 while (strTok.hasMoreTokens()) {
89 String classpathItem = strTok.nextToken();
90 if (classpathItem.toLowerCase().endsWith(".jar")) {
91 File item = new File(classpathItem);
92 if (item.exists()) {
93 try {
94 addSystemResource(item);
95 } catch (IOException e) {
96 log.error(sm.getString
97 ("extensionValidator.failload", item), e);
98 }
99 }
100 }
101 }
102
103 // add specified folders to the list
104 addFolderList("java.ext.dirs");
105 addFolderList("catalina.ext.dirs");
106
107 }
108
109
110 // --------------------------------------------------------- Public Methods
111
112
113 /**
114 * Runtime validation of a Web Applicaiton.
115 *
116 * This method uses JNDI to look up the resources located under a
117 * <code>DirContext</code>. It locates Web Application MANIFEST.MF
118 * file in the /META-INF/ directory of the application and all
119 * MANIFEST.MF files in each JAR file located in the WEB-INF/lib
120 * directory and creates an <code>ArrayList</code> of
121 * <code>ManifestResorce<code> objects. These objects are then passed
122 * to the validateManifestResources method for validation.
123 *
124 * @param dirContext The JNDI root of the Web Application
125 * @param context The context from which the Logger and path to the
126 * application
127 *
128 * @return true if all required extensions satisfied
129 */
130 public static synchronized boolean validateApplication(
131 DirContext dirContext,
132 StandardContext context)
133 throws IOException {
134
135 String appName = context.getPath();
136 ArrayList appManifestResources = new ArrayList();
137 // If the application context is null it does not exist and
138 // therefore is not valid
139 if (dirContext == null) return false;
140 // Find the Manifest for the Web Applicaiton
141 InputStream inputStream = null;
142 try {
143 NamingEnumeration wne = dirContext.listBindings("/META-INF/");
144 Binding binding = (Binding) wne.nextElement();
145 if (binding.getName().toUpperCase().equals("MANIFEST.MF")) {
146 Resource resource = (Resource)dirContext.lookup
147 ("/META-INF/" + binding.getName());
148 inputStream = resource.streamContent();
149 Manifest manifest = new Manifest(inputStream);
150 inputStream.close();
151 inputStream = null;
152 ManifestResource mre = new ManifestResource
153 (sm.getString("extensionValidator.web-application-manifest"),
154 manifest, ManifestResource.WAR);
155 appManifestResources.add(mre);
156 }
157 } catch (NamingException nex) {
158 // Application does not contain a MANIFEST.MF file
159 } catch (NoSuchElementException nse) {
160 // Application does not contain a MANIFEST.MF file
161 } finally {
162 if (inputStream != null) {
163 try {
164 inputStream.close();
165 } catch (Throwable t) {
166 // Ignore
167 }
168 }
169 }
170
171 // Locate the Manifests for all bundled JARs
172 NamingEnumeration ne = null;
173 try {
174 if (dirContext != null) {
175 ne = dirContext.listBindings("WEB-INF/lib/");
176 }
177 while ((ne != null) && ne.hasMoreElements()) {
178 Binding binding = (Binding)ne.nextElement();
179 if (!binding.getName().toLowerCase().endsWith(".jar")) {
180 continue;
181 }
182 Resource resource = (Resource)dirContext.lookup
183 ("/WEB-INF/lib/" + binding.getName());
184 Manifest jmanifest = getManifest(resource.streamContent());
185 if (jmanifest != null) {
186 ManifestResource mre = new ManifestResource(
187 binding.getName(),
188 jmanifest,
189 ManifestResource.APPLICATION);
190 appManifestResources.add(mre);
191 }
192 }
193 } catch (NamingException nex) {
194 // Jump out of the check for this application because it
195 // has no resources
196 }
197
198 return validateManifestResources(appName, appManifestResources);
199 }
200
201
202 /**
203 * Checks to see if the given system JAR file contains a MANIFEST, and adds
204 * it to the container's manifest resources.
205 *
206 * @param jarFile The system JAR whose manifest to add
207 */
208 public static void addSystemResource(File jarFile) throws IOException {
209 Manifest manifest = getManifest(new FileInputStream(jarFile));
210 if (manifest != null) {
211 ManifestResource mre
212 = new ManifestResource(jarFile.getAbsolutePath(),
213 manifest,
214 ManifestResource.SYSTEM);
215 containerManifestResources.add(mre);
216 }
217 }
218
219
220 // -------------------------------------------------------- Private Methods
221
222
223 /**
224 * Validates a <code>ArrayList</code> of <code>ManifestResource</code>
225 * objects. This method requires an application name (which is the
226 * context root of the application at runtime).
227 *
228 * <code>false</false> is returned if the extension dependencies
229 * represented by any given <code>ManifestResource</code> objects
230 * is not met.
231 *
232 * This method should also provide static validation of a Web Applicaiton
233 * if provided with the necessary parameters.
234 *
235 * @param appName The name of the Application that will appear in the
236 * error messages
237 * @param resources A list of <code>ManifestResource</code> objects
238 * to be validated.
239 *
240 * @return true if manifest resource file requirements are met
241 */
242 private static boolean validateManifestResources(String appName,
243 ArrayList resources) {
244 boolean passes = true;
245 int failureCount = 0;
246 ArrayList availableExtensions = null;
247
248 Iterator it = resources.iterator();
249 while (it.hasNext()) {
250 ManifestResource mre = (ManifestResource)it.next();
251 ArrayList requiredList = mre.getRequiredExtensions();
252 if (requiredList == null) {
253 continue;
254 }
255
256 // build the list of available extensions if necessary
257 if (availableExtensions == null) {
258 availableExtensions = buildAvailableExtensionsList(resources);
259 }
260
261 // load the container level resource map if it has not been built
262 // yet
263 if (containerAvailableExtensions == null) {
264 containerAvailableExtensions
265 = buildAvailableExtensionsList(containerManifestResources);
266 }
267
268 // iterate through the list of required extensions
269 Iterator rit = requiredList.iterator();
270 while (rit.hasNext()) {
271 boolean found = false;
272 Extension requiredExt = (Extension)rit.next();
273 // check the applicaion itself for the extension
274 if (availableExtensions != null) {
275 Iterator ait = availableExtensions.iterator();
276 while (ait.hasNext()) {
277 Extension targetExt = (Extension) ait.next();
278 if (targetExt.isCompatibleWith(requiredExt)) {
279 requiredExt.setFulfilled(true);
280 found = true;
281 break;
282 }
283 }
284 }
285 // check the container level list for the extension
286 if (!found && containerAvailableExtensions != null) {
287 Iterator cit = containerAvailableExtensions.iterator();
288 while (cit.hasNext()) {
289 Extension targetExt = (Extension) cit.next();
290 if (targetExt.isCompatibleWith(requiredExt)) {
291 requiredExt.setFulfilled(true);
292 found = true;
293 break;
294 }
295 }
296 }
297 if (!found) {
298 // Failure
299 log.info(sm.getString(
300 "extensionValidator.extension-not-found-error",
301 appName, mre.getResourceName(),
302 requiredExt.getExtensionName()));
303 passes = false;
304 failureCount++;
305 }
306 }
307 }
308
309 if (!passes) {
310 log.info(sm.getString(
311 "extensionValidator.extension-validation-error", appName,
312 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(ArrayList resources) {
336
337 ArrayList availableList = null;
338
339 Iterator it = resources.iterator();
340 while (it.hasNext()) {
341 ManifestResource mre = (ManifestResource)it.next();
342 ArrayList list = mre.getAvailableExtensions();
343 if (list != null) {
344 Iterator values = list.iterator();
345 while (values.hasNext()) {
346 Extension ext = (Extension) values.next();
347 if (availableList == null) {
348 availableList = new ArrayList();
349 availableList.add(ext);
350 } else {
351 availableList.add(ext);
352 }
353 }
354 }
355 }
356
357 return availableList;
358 }
359
360 /**
361 * Return the Manifest from a jar file or war file
362 *
363 * @param inStream Input stream to a WAR or JAR file
364 * @return The WAR's or JAR's manifest
365 */
366 private static Manifest getManifest(InputStream inStream)
367 throws IOException {
368
369 Manifest manifest = null;
370 JarInputStream jin = null;
371
372 try {
373 jin = new JarInputStream(inStream);
374 manifest = jin.getManifest();
375 jin.close();
376 jin = null;
377 } finally {
378 if (jin != null) {
379 try {
380 jin.close();
381 } catch (Throwable t) {
382 // Ignore
383 }
384 }
385 }
386
387 return manifest;
388 }
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
400 = new StringTokenizer(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(".jar")) {
409 try {
410 addSystemResource(files[i]);
411 } catch (IOException e) {
412 log.error
413 (sm.getString
414 ("extensionValidator.failload", files[i]), e);
415 }
416 }
417 }
418 }
419 }
420
421 }
422
423
424 }