001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018 /*
019 * $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $
020 */
021 package org.apache.xalan.xslt;
022
023 import java.io.File;
024 import java.io.FileWriter;
025 import java.io.PrintWriter;
026 import java.lang.reflect.Field;
027 import java.lang.reflect.Method;
028 import java.util.Enumeration;
029 import java.util.Hashtable;
030 import java.util.StringTokenizer;
031 import java.util.Vector;
032
033 import org.w3c.dom.Document;
034 import org.w3c.dom.Element;
035 import org.w3c.dom.Node;
036
037 /**
038 * Utility class to report simple information about the environment.
039 * Simplistic reporting about certain classes found in your JVM may
040 * help answer some FAQs for simple problems.
041 *
042 * <p>Usage-command line:
043 * <code>
044 * java org.apache.xalan.xslt.EnvironmentCheck [-out outFile]
045 * </code></p>
046 *
047 * <p>Usage-from program:
048 * <code>
049 * boolean environmentOK =
050 * (new EnvironmentCheck()).checkEnvironment(yourPrintWriter);
051 * </code></p>
052 *
053 * <p>Usage-from stylesheet:
054 * <code><pre>
055 * <?xml version="1.0"?>
056 * <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
057 * xmlns:xalan="http://xml.apache.org/xalan"
058 * exclude-result-prefixes="xalan">
059 * <xsl:output indent="yes"/>
060 * <xsl:template match="/">
061 * <xsl:copy-of select="xalan:checkEnvironment()"/>
062 * </xsl:template>
063 * </xsl:stylesheet>
064 * </pre></code></p>
065 *
066 * <p>Xalan users reporting problems are encouraged to use this class
067 * to see if there are potential problems with their actual
068 * Java environment <b>before</b> reporting a bug. Note that you
069 * should both check from the JVM/JRE's command line as well as
070 * temporarily calling checkEnvironment() directly from your code,
071 * since the classpath may differ (especially for servlets, etc).</p>
072 *
073 * <p>Also see http://xml.apache.org/xalan-j/faq.html</p>
074 *
075 * <p>Note: This class is pretty simplistic:
076 * results are not necessarily definitive nor will it find all
077 * problems related to environment setup. Also, you should avoid
078 * calling this in deployed production code, both because it is
079 * quite slow and because it forces classes to get loaded.</p>
080 *
081 * <p>Note: This class explicitly has very limited compile-time
082 * dependencies to enable easy compilation and usage even when
083 * Xalan, DOM/SAX/JAXP, etc. are not present.</p>
084 *
085 * <p>Note: for an improved version of this utility, please see
086 * the xml-commons' project Which utility which does the same kind
087 * of thing but in a much simpler manner.</p>
088 *
089 * @author Shane_Curcuru@us.ibm.com
090 * @version $Id: EnvironmentCheck.java 468646 2006-10-28 06:57:58Z minchau $
091 */
092 public class EnvironmentCheck
093 {
094
095 /**
096 * Command line runnability: checks for [-out outFilename] arg.
097 * <p>Command line entrypoint; Sets output and calls
098 * {@link #checkEnvironment(PrintWriter)}.</p>
099 * @param args command line args
100 */
101 public static void main(String[] args)
102 {
103 // Default to System.out, autoflushing
104 PrintWriter sendOutputTo = new PrintWriter(System.out, true);
105
106 // Read our simplistic input args, if supplied
107 for (int i = 0; i < args.length; i++)
108 {
109 if ("-out".equalsIgnoreCase(args[i]))
110 {
111 i++;
112
113 if (i < args.length)
114 {
115 try
116 {
117 sendOutputTo = new PrintWriter(new FileWriter(args[i], true));
118 }
119 catch (Exception e)
120 {
121 System.err.println("# WARNING: -out " + args[i] + " threw "
122 + e.toString());
123 }
124 }
125 else
126 {
127 System.err.println(
128 "# WARNING: -out argument should have a filename, output sent to console");
129 }
130 }
131 }
132
133 EnvironmentCheck app = new EnvironmentCheck();
134 app.checkEnvironment(sendOutputTo);
135 }
136
137 /**
138 * Programmatic entrypoint: Report on basic Java environment
139 * and CLASSPATH settings that affect Xalan.
140 *
141 * <p>Note that this class is not advanced enough to tell you
142 * everything about the environment that affects Xalan, and
143 * sometimes reports errors that will not actually affect
144 * Xalan's behavior. Currently, it very simplistically
145 * checks the JVM's environment for some basic properties and
146 * logs them out; it will report a problem if it finds a setting
147 * or .jar file that is <i>likely</i> to cause problems.</p>
148 *
149 * <p>Advanced users can peruse the code herein to help them
150 * investigate potential environment problems found; other users
151 * may simply send the output from this tool along with any bugs
152 * they submit to help us in the debugging process.</p>
153 *
154 * @param pw PrintWriter to send output to; can be sent to a
155 * file that will look similar to a Properties file; defaults
156 * to System.out if null
157 * @return true if your environment appears to have no major
158 * problems; false if potential environment problems found
159 * @see #getEnvironmentHash()
160 */
161 public boolean checkEnvironment(PrintWriter pw)
162 {
163
164 // Use user-specified output writer if non-null
165 if (null != pw)
166 outWriter = pw;
167
168 // Setup a hash to store various environment information in
169 Hashtable hash = getEnvironmentHash();
170
171 // Check for ERROR keys in the hashtable, and print report
172 boolean environmentHasErrors = writeEnvironmentReport(hash);
173
174 if (environmentHasErrors)
175 {
176 // Note: many logMsg calls have # at the start to
177 // fake a property-file like output
178 logMsg("# WARNING: Potential problems found in your environment!");
179 logMsg("# Check any 'ERROR' items above against the Xalan FAQs");
180 logMsg("# to correct potential problems with your classes/jars");
181 logMsg("# http://xml.apache.org/xalan-j/faq.html");
182 if (null != outWriter)
183 outWriter.flush();
184 return false;
185 }
186 else
187 {
188 logMsg("# YAHOO! Your environment seems to be OK.");
189 if (null != outWriter)
190 outWriter.flush();
191 return true;
192 }
193 }
194
195 /**
196 * Fill a hash with basic environment settings that affect Xalan.
197 *
198 * <p>Worker method called from various places.</p>
199 * <p>Various system and CLASSPATH, etc. properties are put into
200 * the hash as keys with a brief description of the current state
201 * of that item as the value. Any serious problems will be put in
202 * with a key that is prefixed with {@link #ERROR 'ERROR.'} so it
203 * stands out in any resulting report; also a key with just that
204 * constant will be set as well for any error.</p>
205 * <p>Note that some legitimate cases are flaged as potential
206 * errors - namely when a developer recompiles xalan.jar on their
207 * own - and even a non-error state doesn't guaruntee that
208 * everything in the environment is correct. But this will help
209 * point out the most common classpath and system property
210 * problems that we've seen.</p>
211 *
212 * @return Hashtable full of useful environment info about Xalan
213 * and related system properties, etc.
214 */
215 public Hashtable getEnvironmentHash()
216 {
217 // Setup a hash to store various environment information in
218 Hashtable hash = new Hashtable();
219
220 // Call various worker methods to fill in the hash
221 // These are explicitly separate for maintenance and so
222 // advanced users could call them standalone
223 checkJAXPVersion(hash);
224 checkProcessorVersion(hash);
225 checkParserVersion(hash);
226 checkAntVersion(hash);
227 checkDOMVersion(hash);
228 checkSAXVersion(hash);
229 checkSystemProperties(hash);
230
231 return hash;
232 }
233
234 /**
235 * Dump a basic Xalan environment report to outWriter.
236 *
237 * <p>This dumps a simple header and then each of the entries in
238 * the Hashtable to our PrintWriter; it does special processing
239 * for entries that are .jars found in the classpath.</p>
240 *
241 * @param h Hashtable of items to report on; presumably
242 * filled in by our various check*() methods
243 * @return true if your environment appears to have no major
244 * problems; false if potential environment problems found
245 * @see #appendEnvironmentReport(Node, Document, Hashtable)
246 * for an equivalent that appends to a Node instead
247 */
248 protected boolean writeEnvironmentReport(Hashtable h)
249 {
250
251 if (null == h)
252 {
253 logMsg("# ERROR: writeEnvironmentReport called with null Hashtable");
254 return false;
255 }
256
257 boolean errors = false;
258
259 logMsg(
260 "#---- BEGIN writeEnvironmentReport($Revision: 468646 $): Useful stuff found: ----");
261
262 // Fake the Properties-like output
263 for (Enumeration keys = h.keys();
264 keys.hasMoreElements();
265 /* no increment portion */
266 )
267 {
268 Object key = keys.nextElement();
269 String keyStr = (String) key;
270 try
271 {
272 // Special processing for classes found..
273 if (keyStr.startsWith(FOUNDCLASSES))
274 {
275 Vector v = (Vector) h.get(keyStr);
276 errors |= logFoundJars(v, keyStr);
277 }
278 // ..normal processing for all other entries
279 else
280 {
281 // Note: we could just check for the ERROR key by itself,
282 // since we now set that, but since we have to go
283 // through the whole hash anyway, do it this way,
284 // which is safer for maintenance
285 if (keyStr.startsWith(ERROR))
286 {
287 errors = true;
288 }
289 logMsg(keyStr + "=" + h.get(keyStr));
290 }
291 }
292 catch (Exception e)
293 {
294 logMsg("Reading-" + key + "= threw: " + e.toString());
295 }
296 }
297
298 logMsg(
299 "#----- END writeEnvironmentReport: Useful properties found: -----");
300
301 return errors;
302 }
303
304 /** Prefixed to hash keys that signify serious problems. */
305 public static final String ERROR = "ERROR.";
306
307 /** Added to descriptions that signify potential problems. */
308 public static final String WARNING = "WARNING.";
309
310 /** Value for any error found. */
311 public static final String ERROR_FOUND = "At least one error was found!";
312
313 /** Prefixed to hash keys that signify version numbers. */
314 public static final String VERSION = "version.";
315
316 /** Prefixed to hash keys that signify .jars found in classpath. */
317 public static final String FOUNDCLASSES = "foundclasses.";
318
319 /** Marker that a class or .jar was found. */
320 public static final String CLASS_PRESENT = "present-unknown-version";
321
322 /** Marker that a class or .jar was not found. */
323 public static final String CLASS_NOTPRESENT = "not-present";
324
325 /** Listing of common .jar files that include Xalan-related classes. */
326 public String[] jarNames =
327 {
328 "xalan.jar", "xalansamples.jar", "xalanj1compat.jar", "xalanservlet.jar",
329 "serializer.jar", // Serializer (shared between Xalan & Xerces)
330 "xerces.jar", // Xerces-J 1.x
331 "xercesImpl.jar", // Xerces-J 2.x
332 "testxsl.jar",
333 "crimson.jar",
334 "lotusxsl.jar",
335 "jaxp.jar", "parser.jar", "dom.jar", "sax.jar", "xml.jar",
336 "xml-apis.jar",
337 "xsltc.jar"
338 };
339
340 /**
341 * Print out report of .jars found in a classpath.
342 *
343 * Takes the information encoded from a checkPathForJars()
344 * call and dumps it out to our PrintWriter.
345 *
346 * @param v Vector of Hashtables of .jar file info
347 * @param desc description to print out in header
348 *
349 * @return false if OK, true if any .jars were reported
350 * as having errors
351 * @see #checkPathForJars(String, String[])
352 */
353 protected boolean logFoundJars(Vector v, String desc)
354 {
355
356 if ((null == v) || (v.size() < 1))
357 return false;
358
359 boolean errors = false;
360
361 logMsg("#---- BEGIN Listing XML-related jars in: " + desc + " ----");
362
363 for (int i = 0; i < v.size(); i++)
364 {
365 Hashtable subhash = (Hashtable) v.elementAt(i);
366
367 for (Enumeration keys = subhash.keys();
368 keys.hasMoreElements();
369 /* no increment portion */
370 )
371 {
372 Object key = keys.nextElement();
373 String keyStr = (String) key;
374 try
375 {
376 if (keyStr.startsWith(ERROR))
377 {
378 errors = true;
379 }
380 logMsg(keyStr + "=" + subhash.get(keyStr));
381
382 }
383 catch (Exception e)
384 {
385 errors = true;
386 logMsg("Reading-" + key + "= threw: " + e.toString());
387 }
388 }
389 }
390
391 logMsg("#----- END Listing XML-related jars in: " + desc + " -----");
392
393 return errors;
394 }
395
396 /**
397 * Stylesheet extension entrypoint: Dump a basic Xalan
398 * environment report from getEnvironmentHash() to a Node.
399 *
400 * <p>Copy of writeEnvironmentReport that creates a Node suitable
401 * for other processing instead of a properties-like text output.
402 * </p>
403 * @param container Node to append our report to
404 * @param factory Document providing createElement, etc. services
405 * @param h Hash presumably from {@link #getEnvironmentHash()}
406 * @see #writeEnvironmentReport(Hashtable)
407 * for an equivalent that writes to a PrintWriter instead
408 */
409 public void appendEnvironmentReport(Node container, Document factory, Hashtable h)
410 {
411 if ((null == container) || (null == factory))
412 {
413 return;
414 }
415
416 try
417 {
418 Element envCheckNode = factory.createElement("EnvironmentCheck");
419 envCheckNode.setAttribute("version", "$Revision: 468646 $");
420 container.appendChild(envCheckNode);
421
422 if (null == h)
423 {
424 Element statusNode = factory.createElement("status");
425 statusNode.setAttribute("result", "ERROR");
426 statusNode.appendChild(factory.createTextNode("appendEnvironmentReport called with null Hashtable!"));
427 envCheckNode.appendChild(statusNode);
428 return;
429 }
430
431 boolean errors = false;
432
433 Element hashNode = factory.createElement("environment");
434 envCheckNode.appendChild(hashNode);
435
436 for (Enumeration keys = h.keys();
437 keys.hasMoreElements();
438 /* no increment portion */
439 )
440 {
441 Object key = keys.nextElement();
442 String keyStr = (String) key;
443 try
444 {
445 // Special processing for classes found..
446 if (keyStr.startsWith(FOUNDCLASSES))
447 {
448 Vector v = (Vector) h.get(keyStr);
449 // errors |= logFoundJars(v, keyStr);
450 errors |= appendFoundJars(hashNode, factory, v, keyStr);
451 }
452 // ..normal processing for all other entries
453 else
454 {
455 // Note: we could just check for the ERROR key by itself,
456 // since we now set that, but since we have to go
457 // through the whole hash anyway, do it this way,
458 // which is safer for maintenance
459 if (keyStr.startsWith(ERROR))
460 {
461 errors = true;
462 }
463 Element node = factory.createElement("item");
464 node.setAttribute("key", keyStr);
465 node.appendChild(factory.createTextNode((String)h.get(keyStr)));
466 hashNode.appendChild(node);
467 }
468 }
469 catch (Exception e)
470 {
471 errors = true;
472 Element node = factory.createElement("item");
473 node.setAttribute("key", keyStr);
474 node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString()));
475 hashNode.appendChild(node);
476 }
477 } // end of for...
478
479 Element statusNode = factory.createElement("status");
480 statusNode.setAttribute("result", (errors ? "ERROR" : "OK" ));
481 envCheckNode.appendChild(statusNode);
482 }
483 catch (Exception e2)
484 {
485 System.err.println("appendEnvironmentReport threw: " + e2.toString());
486 e2.printStackTrace();
487 }
488 }
489
490 /**
491 * Print out report of .jars found in a classpath.
492 *
493 * Takes the information encoded from a checkPathForJars()
494 * call and dumps it out to our PrintWriter.
495 *
496 * @param container Node to append our report to
497 * @param factory Document providing createElement, etc. services
498 * @param v Vector of Hashtables of .jar file info
499 * @param desc description to print out in header
500 *
501 * @return false if OK, true if any .jars were reported
502 * as having errors
503 * @see #checkPathForJars(String, String[])
504 */
505 protected boolean appendFoundJars(Node container, Document factory,
506 Vector v, String desc)
507 {
508
509 if ((null == v) || (v.size() < 1))
510 return false;
511
512 boolean errors = false;
513
514 for (int i = 0; i < v.size(); i++)
515 {
516 Hashtable subhash = (Hashtable) v.elementAt(i);
517
518 for (Enumeration keys = subhash.keys();
519 keys.hasMoreElements();
520 /* no increment portion */
521 )
522 {
523 Object key = keys.nextElement();
524 try
525 {
526 String keyStr = (String) key;
527 if (keyStr.startsWith(ERROR))
528 {
529 errors = true;
530 }
531 Element node = factory.createElement("foundJar");
532 node.setAttribute("name", keyStr.substring(0, keyStr.indexOf("-")));
533 node.setAttribute("desc", keyStr.substring(keyStr.indexOf("-") + 1));
534 node.appendChild(factory.createTextNode((String)subhash.get(keyStr)));
535 container.appendChild(node);
536 }
537 catch (Exception e)
538 {
539 errors = true;
540 Element node = factory.createElement("foundJar");
541 node.appendChild(factory.createTextNode(ERROR + " Reading " + key + " threw: " + e.toString()));
542 container.appendChild(node);
543 }
544 }
545 }
546 return errors;
547 }
548
549 /**
550 * Fillin hash with info about SystemProperties.
551 *
552 * Logs java.class.path and other likely paths; then attempts
553 * to search those paths for .jar files with Xalan-related classes.
554 *
555 * //@todo NOTE: We don't actually search java.ext.dirs for
556 * // *.jar files therein! This should be updated
557 *
558 * @param h Hashtable to put information in
559 * @see #jarNames
560 * @see #checkPathForJars(String, String[])
561 */
562 protected void checkSystemProperties(Hashtable h)
563 {
564
565 if (null == h)
566 h = new Hashtable();
567
568 // Grab java version for later use
569 try
570 {
571 String javaVersion = System.getProperty("java.version");
572
573 h.put("java.version", javaVersion);
574 }
575 catch (SecurityException se)
576 {
577
578 // For applet context, etc.
579 h.put(
580 "java.version",
581 "WARNING: SecurityException thrown accessing system version properties");
582 }
583
584 // Printout jar files on classpath(s) that may affect operation
585 // Do this in order
586 try
587 {
588
589 // This is present in all JVM's
590 String cp = System.getProperty("java.class.path");
591
592 h.put("java.class.path", cp);
593
594 Vector classpathJars = checkPathForJars(cp, jarNames);
595
596 if (null != classpathJars)
597 h.put(FOUNDCLASSES + "java.class.path", classpathJars);
598
599 // Also check for JDK 1.2+ type classpaths
600 String othercp = System.getProperty("sun.boot.class.path");
601
602 if (null != othercp)
603 {
604 h.put("sun.boot.class.path", othercp);
605
606 classpathJars = checkPathForJars(othercp, jarNames);
607
608 if (null != classpathJars)
609 h.put(FOUNDCLASSES + "sun.boot.class.path", classpathJars);
610 }
611
612 //@todo NOTE: We don't actually search java.ext.dirs for
613 // *.jar files therein! This should be updated
614 othercp = System.getProperty("java.ext.dirs");
615
616 if (null != othercp)
617 {
618 h.put("java.ext.dirs", othercp);
619
620 classpathJars = checkPathForJars(othercp, jarNames);
621
622 if (null != classpathJars)
623 h.put(FOUNDCLASSES + "java.ext.dirs", classpathJars);
624 }
625
626 //@todo also check other System properties' paths?
627 // v2 = checkPathForJars(System.getProperty("sun.boot.library.path"), jarNames); // ?? may not be needed
628 // v3 = checkPathForJars(System.getProperty("java.library.path"), jarNames); // ?? may not be needed
629 }
630 catch (SecurityException se2)
631 {
632 // For applet context, etc.
633 h.put(
634 "java.class.path",
635 "WARNING: SecurityException thrown accessing system classpath properties");
636 }
637 }
638
639 /**
640 * Cheap-o listing of specified .jars found in the classpath.
641 *
642 * cp should be separated by the usual File.pathSeparator. We
643 * then do a simplistic search of the path for any requested
644 * .jar filenames, and return a listing of their names and
645 * where (apparently) they came from.
646 *
647 * @param cp classpath to search
648 * @param jars array of .jar base filenames to look for
649 *
650 * @return Vector of Hashtables filled with info about found .jars
651 * @see #jarNames
652 * @see #logFoundJars(Vector, String)
653 * @see #appendFoundJars(Node, Document, Vector, String )
654 * @see #getApparentVersion(String, long)
655 */
656 protected Vector checkPathForJars(String cp, String[] jars)
657 {
658
659 if ((null == cp) || (null == jars) || (0 == cp.length())
660 || (0 == jars.length))
661 return null;
662
663 Vector v = new Vector();
664 StringTokenizer st = new StringTokenizer(cp, File.pathSeparator);
665
666 while (st.hasMoreTokens())
667 {
668
669 // Look at each classpath entry for each of our requested jarNames
670 String filename = st.nextToken();
671
672 for (int i = 0; i < jars.length; i++)
673 {
674 if (filename.indexOf(jars[i]) > -1)
675 {
676 File f = new File(filename);
677
678 if (f.exists())
679 {
680
681 // If any requested jarName exists, report on
682 // the details of that .jar file
683 try
684 {
685 Hashtable h = new Hashtable(2);
686 // Note "-" char is looked for in appendFoundJars
687 h.put(jars[i] + "-path", f.getAbsolutePath());
688
689 // We won't bother reporting on the xalan.jar apparent version
690 // since this requires knowing the jar size of the xalan.jar
691 // before we build it.
692 // For other jars, eg. xml-apis.jar and xercesImpl.jar, we
693 // report the apparent version of the file we've found
694 if (!("xalan.jar".equalsIgnoreCase(jars[i]))) {
695 h.put(jars[i] + "-apparent.version",
696 getApparentVersion(jars[i], f.length()));
697 }
698 v.addElement(h);
699 }
700 catch (Exception e)
701 {
702
703 /* no-op, don't add it */
704 }
705 }
706 else
707 {
708 Hashtable h = new Hashtable(2);
709 // Note "-" char is looked for in appendFoundJars
710 h.put(jars[i] + "-path", WARNING + " Classpath entry: "
711 + filename + " does not exist");
712 h.put(jars[i] + "-apparent.version", CLASS_NOTPRESENT);
713 v.addElement(h);
714 }
715 }
716 }
717 }
718
719 return v;
720 }
721
722 /**
723 * Cheap-o method to determine the product version of a .jar.
724 *
725 * Currently does a lookup into a local table of some recent
726 * shipped Xalan builds to determine where the .jar probably
727 * came from. Note that if you recompile Xalan or Xerces
728 * yourself this will likely report a potential error, since
729 * we can't certify builds other than the ones we ship.
730 * Only reports against selected posted Xalan-J builds.
731 *
732 * //@todo actually look up version info in manifests
733 *
734 * @param jarName base filename of the .jarfile
735 * @param jarSize size of the .jarfile
736 *
737 * @return String describing where the .jar file probably
738 * came from
739 */
740 protected String getApparentVersion(String jarName, long jarSize)
741 {
742 // If we found a matching size and it's for our
743 // jar, then return it's description
744 // Lookup in static jarVersions Hashtable
745 String foundSize = (String) jarVersions.get(new Long(jarSize));
746
747 if ((null != foundSize) && (foundSize.startsWith(jarName)))
748 {
749 return foundSize;
750 }
751 else
752 {
753 if ("xerces.jar".equalsIgnoreCase(jarName)
754 || "xercesImpl.jar".equalsIgnoreCase(jarName))
755 // || "xalan.jar".equalsIgnoreCase(jarName))
756 {
757
758 // For xalan.jar and xerces.jar/xercesImpl.jar, which we ship together:
759 // The jar is not from a shipped copy of xalan-j, so
760 // it's up to the user to ensure that it's compatible
761 return jarName + " " + WARNING + CLASS_PRESENT;
762 }
763 else
764 {
765
766 // Otherwise, it's just a jar we don't have the version info calculated for
767 return jarName + " " + CLASS_PRESENT;
768 }
769 }
770 }
771
772 /**
773 * Report version information about JAXP interfaces.
774 *
775 * Currently distinguishes between JAXP 1.0.1 and JAXP 1.1,
776 * and not found; only tests the interfaces, and does not
777 * check for reference implementation versions.
778 *
779 * @param h Hashtable to put information in
780 */
781 protected void checkJAXPVersion(Hashtable h)
782 {
783
784 if (null == h)
785 h = new Hashtable();
786
787 final Class noArgs[] = new Class[0];
788 Class clazz = null;
789
790 try
791 {
792 final String JAXP1_CLASS = "javax.xml.parsers.DocumentBuilder";
793 final String JAXP11_METHOD = "getDOMImplementation";
794
795 clazz = ObjectFactory.findProviderClass(
796 JAXP1_CLASS, ObjectFactory.findClassLoader(), true);
797
798 Method method = clazz.getMethod(JAXP11_METHOD, noArgs);
799
800 // If we succeeded, we at least have JAXP 1.1 available
801 h.put(VERSION + "JAXP", "1.1 or higher");
802 }
803 catch (Exception e)
804 {
805 if (null != clazz)
806 {
807
808 // We must have found the class itself, just not the
809 // method, so we (probably) have JAXP 1.0.1
810 h.put(ERROR + VERSION + "JAXP", "1.0.1");
811 h.put(ERROR, ERROR_FOUND);
812 }
813 else
814 {
815 // We couldn't even find the class, and don't have
816 // any JAXP support at all, or only have the
817 // transform half of it
818 h.put(ERROR + VERSION + "JAXP", CLASS_NOTPRESENT);
819 h.put(ERROR, ERROR_FOUND);
820 }
821 }
822 }
823
824 /**
825 * Report product version information from Xalan-J.
826 *
827 * Looks for version info in xalan.jar from Xalan-J products.
828 *
829 * @param h Hashtable to put information in
830 */
831 protected void checkProcessorVersion(Hashtable h)
832 {
833
834 if (null == h)
835 h = new Hashtable();
836
837 try
838 {
839 final String XALAN1_VERSION_CLASS =
840 "org.apache.xalan.xslt.XSLProcessorVersion";
841
842 Class clazz = ObjectFactory.findProviderClass(
843 XALAN1_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
844
845 // Found Xalan-J 1.x, grab it's version fields
846 StringBuffer buf = new StringBuffer();
847 Field f = clazz.getField("PRODUCT");
848
849 buf.append(f.get(null));
850 buf.append(';');
851
852 f = clazz.getField("LANGUAGE");
853
854 buf.append(f.get(null));
855 buf.append(';');
856
857 f = clazz.getField("S_VERSION");
858
859 buf.append(f.get(null));
860 buf.append(';');
861 h.put(VERSION + "xalan1", buf.toString());
862 }
863 catch (Exception e1)
864 {
865 h.put(VERSION + "xalan1", CLASS_NOTPRESENT);
866 }
867
868 try
869 {
870 // NOTE: This is the old Xalan 2.0, 2.1, 2.2 version class,
871 // is being replaced by class below
872 final String XALAN2_VERSION_CLASS =
873 "org.apache.xalan.processor.XSLProcessorVersion";
874
875 Class clazz = ObjectFactory.findProviderClass(
876 XALAN2_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
877
878 // Found Xalan-J 2.x, grab it's version fields
879 StringBuffer buf = new StringBuffer();
880 Field f = clazz.getField("S_VERSION");
881 buf.append(f.get(null));
882
883 h.put(VERSION + "xalan2x", buf.toString());
884 }
885 catch (Exception e2)
886 {
887 h.put(VERSION + "xalan2x", CLASS_NOTPRESENT);
888 }
889 try
890 {
891 // NOTE: This is the new Xalan 2.2+ version class
892 final String XALAN2_2_VERSION_CLASS =
893 "org.apache.xalan.Version";
894 final String XALAN2_2_VERSION_METHOD = "getVersion";
895 final Class noArgs[] = new Class[0];
896
897 Class clazz = ObjectFactory.findProviderClass(
898 XALAN2_2_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
899
900 Method method = clazz.getMethod(XALAN2_2_VERSION_METHOD, noArgs);
901 Object returnValue = method.invoke(null, new Object[0]);
902
903 h.put(VERSION + "xalan2_2", (String)returnValue);
904 }
905 catch (Exception e2)
906 {
907 h.put(VERSION + "xalan2_2", CLASS_NOTPRESENT);
908 }
909 }
910
911 /**
912 * Report product version information from common parsers.
913 *
914 * Looks for version info in xerces.jar/xercesImpl.jar/crimson.jar.
915 *
916 * //@todo actually look up version info in crimson manifest
917 *
918 * @param h Hashtable to put information in
919 */
920 protected void checkParserVersion(Hashtable h)
921 {
922
923 if (null == h)
924 h = new Hashtable();
925
926 try
927 {
928 final String XERCES1_VERSION_CLASS = "org.apache.xerces.framework.Version";
929
930 Class clazz = ObjectFactory.findProviderClass(
931 XERCES1_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
932
933 // Found Xerces-J 1.x, grab it's version fields
934 Field f = clazz.getField("fVersion");
935 String parserVersion = (String) f.get(null);
936
937 h.put(VERSION + "xerces1", parserVersion);
938 }
939 catch (Exception e)
940 {
941 h.put(VERSION + "xerces1", CLASS_NOTPRESENT);
942 }
943
944 // Look for xerces1 and xerces2 parsers separately
945 try
946 {
947 final String XERCES2_VERSION_CLASS = "org.apache.xerces.impl.Version";
948
949 Class clazz = ObjectFactory.findProviderClass(
950 XERCES2_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
951
952 // Found Xerces-J 2.x, grab it's version fields
953 Field f = clazz.getField("fVersion");
954 String parserVersion = (String) f.get(null);
955
956 h.put(VERSION + "xerces2", parserVersion);
957 }
958 catch (Exception e)
959 {
960 h.put(VERSION + "xerces2", CLASS_NOTPRESENT);
961 }
962
963 try
964 {
965 final String CRIMSON_CLASS = "org.apache.crimson.parser.Parser2";
966
967 Class clazz = ObjectFactory.findProviderClass(
968 CRIMSON_CLASS, ObjectFactory.findClassLoader(), true);
969
970 //@todo determine specific crimson version
971 h.put(VERSION + "crimson", CLASS_PRESENT);
972 }
973 catch (Exception e)
974 {
975 h.put(VERSION + "crimson", CLASS_NOTPRESENT);
976 }
977 }
978
979 /**
980 * Report product version information from Ant.
981 *
982 * @param h Hashtable to put information in
983 */
984 protected void checkAntVersion(Hashtable h)
985 {
986
987 if (null == h)
988 h = new Hashtable();
989
990 try
991 {
992 final String ANT_VERSION_CLASS = "org.apache.tools.ant.Main";
993 final String ANT_VERSION_METHOD = "getAntVersion"; // noArgs
994 final Class noArgs[] = new Class[0];
995
996 Class clazz = ObjectFactory.findProviderClass(
997 ANT_VERSION_CLASS, ObjectFactory.findClassLoader(), true);
998
999 Method method = clazz.getMethod(ANT_VERSION_METHOD, noArgs);
1000 Object returnValue = method.invoke(null, new Object[0]);
1001
1002 h.put(VERSION + "ant", (String)returnValue);
1003 }
1004 catch (Exception e)
1005 {
1006 h.put(VERSION + "ant", CLASS_NOTPRESENT);
1007 }
1008 }
1009
1010 /**
1011 * Report version info from DOM interfaces.
1012 *
1013 * Currently distinguishes between pre-DOM level 2, the DOM
1014 * level 2 working draft, the DOM level 2 final draft,
1015 * and not found.
1016 *
1017 * @param h Hashtable to put information in
1018 */
1019 protected void checkDOMVersion(Hashtable h)
1020 {
1021
1022 if (null == h)
1023 h = new Hashtable();
1024
1025 final String DOM_LEVEL2_CLASS = "org.w3c.dom.Document";
1026 final String DOM_LEVEL2_METHOD = "createElementNS"; // String, String
1027 final String DOM_LEVEL2WD_CLASS = "org.w3c.dom.Node";
1028 final String DOM_LEVEL2WD_METHOD = "supported"; // String, String
1029 final String DOM_LEVEL2FD_CLASS = "org.w3c.dom.Node";
1030 final String DOM_LEVEL2FD_METHOD = "isSupported"; // String, String
1031 final Class twoStringArgs[] = { java.lang.String.class,
1032 java.lang.String.class };
1033
1034 try
1035 {
1036 Class clazz = ObjectFactory.findProviderClass(
1037 DOM_LEVEL2_CLASS, ObjectFactory.findClassLoader(), true);
1038
1039 Method method = clazz.getMethod(DOM_LEVEL2_METHOD, twoStringArgs);
1040
1041 // If we succeeded, we have loaded interfaces from a
1042 // level 2 DOM somewhere
1043 h.put(VERSION + "DOM", "2.0");
1044
1045 try
1046 {
1047 // Check for the working draft version, which is
1048 // commonly found, but won't work anymore
1049 clazz = ObjectFactory.findProviderClass(
1050 DOM_LEVEL2WD_CLASS, ObjectFactory.findClassLoader(), true);
1051
1052 method = clazz.getMethod(DOM_LEVEL2WD_METHOD, twoStringArgs);
1053
1054 h.put(ERROR + VERSION + "DOM.draftlevel", "2.0wd");
1055 h.put(ERROR, ERROR_FOUND);
1056 }
1057 catch (Exception e2)
1058 {
1059 try
1060 {
1061 // Check for the final draft version as well
1062 clazz = ObjectFactory.findProviderClass(
1063 DOM_LEVEL2FD_CLASS, ObjectFactory.findClassLoader(), true);
1064
1065 method = clazz.getMethod(DOM_LEVEL2FD_METHOD, twoStringArgs);
1066
1067 h.put(VERSION + "DOM.draftlevel", "2.0fd");
1068 }
1069 catch (Exception e3)
1070 {
1071 h.put(ERROR + VERSION + "DOM.draftlevel", "2.0unknown");
1072 h.put(ERROR, ERROR_FOUND);
1073 }
1074 }
1075 }
1076 catch (Exception e)
1077 {
1078 h.put(ERROR + VERSION + "DOM",
1079 "ERROR attempting to load DOM level 2 class: " + e.toString());
1080 h.put(ERROR, ERROR_FOUND);
1081 }
1082
1083 //@todo load an actual DOM implmementation and query it as well
1084 //@todo load an actual DOM implmementation and check if
1085 // isNamespaceAware() == true, which is needed to parse
1086 // xsl stylesheet files into a DOM
1087 }
1088
1089 /**
1090 * Report version info from SAX interfaces.
1091 *
1092 * Currently distinguishes between SAX 2, SAX 2.0beta2,
1093 * SAX1, and not found.
1094 *
1095 * @param h Hashtable to put information in
1096 */
1097 protected void checkSAXVersion(Hashtable h)
1098 {
1099
1100 if (null == h)
1101 h = new Hashtable();
1102
1103 final String SAX_VERSION1_CLASS = "org.xml.sax.Parser";
1104 final String SAX_VERSION1_METHOD = "parse"; // String
1105 final String SAX_VERSION2_CLASS = "org.xml.sax.XMLReader";
1106 final String SAX_VERSION2_METHOD = "parse"; // String
1107 final String SAX_VERSION2BETA_CLASSNF = "org.xml.sax.helpers.AttributesImpl";
1108 final String SAX_VERSION2BETA_METHODNF = "setAttributes"; // Attributes
1109 final Class oneStringArg[] = { java.lang.String.class };
1110 // Note this introduces a minor compile dependency on SAX...
1111 final Class attributesArg[] = { org.xml.sax.Attributes.class };
1112
1113 try
1114 {
1115 // This method was only added in the final SAX 2.0 release;
1116 // see changes.html "Changes from SAX 2.0beta2 to SAX 2.0prerelease"
1117 Class clazz = ObjectFactory.findProviderClass(
1118 SAX_VERSION2BETA_CLASSNF, ObjectFactory.findClassLoader(), true);
1119
1120 Method method = clazz.getMethod(SAX_VERSION2BETA_METHODNF, attributesArg);
1121
1122 // If we succeeded, we have loaded interfaces from a
1123 // real, final SAX version 2.0 somewhere
1124 h.put(VERSION + "SAX", "2.0");
1125 }
1126 catch (Exception e)
1127 {
1128 // If we didn't find the SAX 2.0 class, look for a 2.0beta2
1129 h.put(ERROR + VERSION + "SAX",
1130 "ERROR attempting to load SAX version 2 class: " + e.toString());
1131 h.put(ERROR, ERROR_FOUND);
1132
1133 try
1134 {
1135 Class clazz = ObjectFactory.findProviderClass(
1136 SAX_VERSION2_CLASS, ObjectFactory.findClassLoader(), true);
1137
1138 Method method = clazz.getMethod(SAX_VERSION2_METHOD, oneStringArg);
1139
1140 // If we succeeded, we have loaded interfaces from a
1141 // SAX version 2.0beta2 or earlier; these might work but
1142 // you should really have the final SAX 2.0
1143 h.put(VERSION + "SAX-backlevel", "2.0beta2-or-earlier");
1144 }
1145 catch (Exception e2)
1146 {
1147 // If we didn't find the SAX 2.0beta2 class, look for a 1.0 one
1148 h.put(ERROR + VERSION + "SAX",
1149 "ERROR attempting to load SAX version 2 class: " + e.toString());
1150 h.put(ERROR, ERROR_FOUND);
1151
1152 try
1153 {
1154 Class clazz = ObjectFactory.findProviderClass(
1155 SAX_VERSION1_CLASS, ObjectFactory.findClassLoader(), true);
1156
1157 Method method = clazz.getMethod(SAX_VERSION1_METHOD, oneStringArg);
1158
1159 // If we succeeded, we have loaded interfaces from a
1160 // SAX version 1.0 somewhere; which won't work very
1161 // well for JAXP 1.1 or beyond!
1162 h.put(VERSION + "SAX-backlevel", "1.0");
1163 }
1164 catch (Exception e3)
1165 {
1166 // If we didn't find the SAX 2.0 class, look for a 1.0 one
1167 // Note that either 1.0 or no SAX are both errors
1168 h.put(ERROR + VERSION + "SAX-backlevel",
1169 "ERROR attempting to load SAX version 1 class: " + e3.toString());
1170
1171 }
1172 }
1173 }
1174 }
1175
1176 /**
1177 * Manual table of known .jar sizes.
1178 * Only includes shipped versions of certain projects.
1179 * key=jarsize, value=jarname ' from ' distro name
1180 * Note assumption: two jars cannot have the same size!
1181 *
1182 * @see #getApparentVersion(String, long)
1183 */
1184 private static Hashtable jarVersions = new Hashtable();
1185
1186 /**
1187 * Static initializer for jarVersions table.
1188 * Doing this just once saves time and space.
1189 *
1190 * @see #getApparentVersion(String, long)
1191 */
1192 static
1193 {
1194 // Note: hackish Hashtable, this could use improvement
1195 jarVersions.put(new Long(857192), "xalan.jar from xalan-j_1_1");
1196 jarVersions.put(new Long(440237), "xalan.jar from xalan-j_1_2");
1197 jarVersions.put(new Long(436094), "xalan.jar from xalan-j_1_2_1");
1198 jarVersions.put(new Long(426249), "xalan.jar from xalan-j_1_2_2");
1199 jarVersions.put(new Long(702536), "xalan.jar from xalan-j_2_0_0");
1200 jarVersions.put(new Long(720930), "xalan.jar from xalan-j_2_0_1");
1201 jarVersions.put(new Long(732330), "xalan.jar from xalan-j_2_1_0");
1202 jarVersions.put(new Long(872241), "xalan.jar from xalan-j_2_2_D10");
1203 jarVersions.put(new Long(882739), "xalan.jar from xalan-j_2_2_D11");
1204 jarVersions.put(new Long(923866), "xalan.jar from xalan-j_2_2_0");
1205 jarVersions.put(new Long(905872), "xalan.jar from xalan-j_2_3_D1");
1206 jarVersions.put(new Long(906122), "xalan.jar from xalan-j_2_3_0");
1207 jarVersions.put(new Long(906248), "xalan.jar from xalan-j_2_3_1");
1208 jarVersions.put(new Long(983377), "xalan.jar from xalan-j_2_4_D1");
1209 jarVersions.put(new Long(997276), "xalan.jar from xalan-j_2_4_0");
1210 jarVersions.put(new Long(1031036), "xalan.jar from xalan-j_2_4_1");
1211 // Stop recording xalan.jar sizes as of Xalan Java 2.5.0
1212
1213 jarVersions.put(new Long(596540), "xsltc.jar from xalan-j_2_2_0");
1214 jarVersions.put(new Long(590247), "xsltc.jar from xalan-j_2_3_D1");
1215 jarVersions.put(new Long(589914), "xsltc.jar from xalan-j_2_3_0");
1216 jarVersions.put(new Long(589915), "xsltc.jar from xalan-j_2_3_1");
1217 jarVersions.put(new Long(1306667), "xsltc.jar from xalan-j_2_4_D1");
1218 jarVersions.put(new Long(1328227), "xsltc.jar from xalan-j_2_4_0");
1219 jarVersions.put(new Long(1344009), "xsltc.jar from xalan-j_2_4_1");
1220 jarVersions.put(new Long(1348361), "xsltc.jar from xalan-j_2_5_D1");
1221 // Stop recording xsltc.jar sizes as of Xalan Java 2.5.0
1222
1223 jarVersions.put(new Long(1268634), "xsltc.jar-bundled from xalan-j_2_3_0");
1224
1225 jarVersions.put(new Long(100196), "xml-apis.jar from xalan-j_2_2_0 or xalan-j_2_3_D1");
1226 jarVersions.put(new Long(108484), "xml-apis.jar from xalan-j_2_3_0, or xalan-j_2_3_1 from xml-commons-1.0.b2");
1227 jarVersions.put(new Long(109049), "xml-apis.jar from xalan-j_2_4_0 from xml-commons RIVERCOURT1 branch");
1228 jarVersions.put(new Long(113749), "xml-apis.jar from xalan-j_2_4_1 from factoryfinder-build of xml-commons RIVERCOURT1");
1229 jarVersions.put(new Long(124704), "xml-apis.jar from tck-jaxp-1_2_0 branch of xml-commons");
1230 jarVersions.put(new Long(124724), "xml-apis.jar from tck-jaxp-1_2_0 branch of xml-commons, tag: xml-commons-external_1_2_01");
1231 jarVersions.put(new Long(194205), "xml-apis.jar from head branch of xml-commons, tag: xml-commons-external_1_3_02");
1232
1233 // If the below were more common I would update it to report
1234 // errors better; but this is so old hardly anyone has it
1235 jarVersions.put(new Long(424490), "xalan.jar from Xerces Tools releases - ERROR:DO NOT USE!");
1236
1237 jarVersions.put(new Long(1591855), "xerces.jar from xalan-j_1_1 from xerces-1...");
1238 jarVersions.put(new Long(1498679), "xerces.jar from xalan-j_1_2 from xerces-1_2_0.bin");
1239 jarVersions.put(new Long(1484896), "xerces.jar from xalan-j_1_2_1 from xerces-1_2_1.bin");
1240 jarVersions.put(new Long(804460), "xerces.jar from xalan-j_1_2_2 from xerces-1_2_2.bin");
1241 jarVersions.put(new Long(1499244), "xerces.jar from xalan-j_2_0_0 from xerces-1_2_3.bin");
1242 jarVersions.put(new Long(1605266), "xerces.jar from xalan-j_2_0_1 from xerces-1_3_0.bin");
1243 jarVersions.put(new Long(904030), "xerces.jar from xalan-j_2_1_0 from xerces-1_4.bin");
1244 jarVersions.put(new Long(904030), "xerces.jar from xerces-1_4_0.bin");
1245 jarVersions.put(new Long(1802885), "xerces.jar from xerces-1_4_2.bin");
1246 jarVersions.put(new Long(1734594), "xerces.jar from Xerces-J-bin.2.0.0.beta3");
1247 jarVersions.put(new Long(1808883), "xerces.jar from xalan-j_2_2_D10,D11,D12 or xerces-1_4_3.bin");
1248 jarVersions.put(new Long(1812019), "xerces.jar from xalan-j_2_2_0");
1249 jarVersions.put(new Long(1720292), "xercesImpl.jar from xalan-j_2_3_D1");
1250 jarVersions.put(new Long(1730053), "xercesImpl.jar from xalan-j_2_3_0 or xalan-j_2_3_1 from xerces-2_0_0");
1251 jarVersions.put(new Long(1728861), "xercesImpl.jar from xalan-j_2_4_D1 from xerces-2_0_1");
1252 jarVersions.put(new Long(972027), "xercesImpl.jar from xalan-j_2_4_0 from xerces-2_1");
1253 jarVersions.put(new Long(831587), "xercesImpl.jar from xalan-j_2_4_1 from xerces-2_2");
1254 jarVersions.put(new Long(891817), "xercesImpl.jar from xalan-j_2_5_D1 from xerces-2_3");
1255 jarVersions.put(new Long(895924), "xercesImpl.jar from xerces-2_4");
1256 jarVersions.put(new Long(1010806), "xercesImpl.jar from Xerces-J-bin.2.6.2");
1257 jarVersions.put(new Long(1203860), "xercesImpl.jar from Xerces-J-bin.2.7.1");
1258
1259 jarVersions.put(new Long(37485), "xalanj1compat.jar from xalan-j_2_0_0");
1260 jarVersions.put(new Long(38100), "xalanj1compat.jar from xalan-j_2_0_1");
1261
1262 jarVersions.put(new Long(18779), "xalanservlet.jar from xalan-j_2_0_0");
1263 jarVersions.put(new Long(21453), "xalanservlet.jar from xalan-j_2_0_1");
1264 jarVersions.put(new Long(24826), "xalanservlet.jar from xalan-j_2_3_1 or xalan-j_2_4_1");
1265 jarVersions.put(new Long(24831), "xalanservlet.jar from xalan-j_2_4_1");
1266 // Stop recording xalanservlet.jar sizes as of Xalan Java 2.5.0; now a .war file
1267
1268 // For those who've downloaded JAXP from sun
1269 jarVersions.put(new Long(5618), "jaxp.jar from jaxp1.0.1");
1270 jarVersions.put(new Long(136133), "parser.jar from jaxp1.0.1");
1271 jarVersions.put(new Long(28404), "jaxp.jar from jaxp-1.1");
1272 jarVersions.put(new Long(187162), "crimson.jar from jaxp-1.1");
1273 jarVersions.put(new Long(801714), "xalan.jar from jaxp-1.1");
1274 jarVersions.put(new Long(196399), "crimson.jar from crimson-1.1.1");
1275 jarVersions.put(new Long(33323), "jaxp.jar from crimson-1.1.1 or jakarta-ant-1.4.1b1");
1276 jarVersions.put(new Long(152717), "crimson.jar from crimson-1.1.2beta2");
1277 jarVersions.put(new Long(88143), "xml-apis.jar from crimson-1.1.2beta2");
1278 jarVersions.put(new Long(206384), "crimson.jar from crimson-1.1.3 or jakarta-ant-1.4.1b1");
1279
1280 // jakarta-ant: since many people use ant these days
1281 jarVersions.put(new Long(136198), "parser.jar from jakarta-ant-1.3 or 1.2");
1282 jarVersions.put(new Long(5537), "jaxp.jar from jakarta-ant-1.3 or 1.2");
1283 }
1284
1285 /** Simple PrintWriter we send output to; defaults to System.out. */
1286 protected PrintWriter outWriter = new PrintWriter(System.out, true);
1287
1288 /**
1289 * Bottleneck output: calls outWriter.println(s).
1290 * @param s String to print
1291 */
1292 protected void logMsg(String s)
1293 {
1294 outWriter.println(s);
1295 }
1296 }