1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 package org.apache.river.tool.envcheck; 19 20 import org.apache.river.resource.Service; 21 22 import java.io.ByteArrayOutputStream; 23 import java.io.File; 24 import java.io.InputStream; 25 import java.io.IOException; 26 import java.io.ObjectInputStream; 27 import java.io.PrintStream; 28 29 import java.lang.reflect.Modifier; 30 31 import java.net.MalformedURLException; 32 import java.net.URL; 33 import java.net.URLClassLoader; 34 35 import java.util.ArrayList; 36 import java.util.Enumeration; 37 import java.util.Iterator; 38 import java.util.Properties; 39 import java.util.ResourceBundle; 40 41 import net.jini.config.Configuration; 42 import net.jini.config.ConfigurationException; 43 import net.jini.config.ConfigurationProvider; 44 45 import org.apache.river.start.ServiceDescriptor; 46 import org.apache.river.start.NonActivatableServiceDescriptor; 47 import org.apache.river.start.SharedActivatableServiceDescriptor; 48 import org.apache.river.start.SharedActivationGroupDescriptor; 49 50 import org.apache.river.tool.envcheck.Reporter.Message; 51 import org.apache.river.api.net.RFC3986URLClassLoader; 52 53 /** 54 * Tool used to perform validity checks on the run-time environment of a client 55 * or service. The output of this tool is a report; command-line options 56 * control the verbosity and severity level at which report entries are 57 * generated. A simple plugin architecture is implemented; a set of plugins 58 * implementing a variety of checks is bundled with the tool, and support is 59 * provided to allow additional plugins to be supplied by the user. 60 * <p> 61 * The following items are discussed below: 62 * <ul> 63 * <li><a href="#running">Running the Tool</a> 64 * <li><a href="#processing">Processing Options</a> 65 * <li><a href="#examples">Examples</a> 66 * <li><a href="#plugins">Bundled Plugins</a> 67 * </ul> 68 * 69 * <a name="running"></a> 70 * <h3>Running the Tool</h3> 71 * 72 * This tool primarily validates the system properties and configuration 73 * files used when starting the target client or service. This is accomplished 74 * by providing the unmodified command line for launching the component as 75 * arguments to the tool. Thus, for a service designed to be run by the 76 * service starter having the hypothetical original command line: 77 * <blockquote><pre> 78 * java -Djava.security.policy=<var><b>my_policy</b></var> \ 79 * -jar <var><b>install_dir</b></var>/lib/start.jar mystart.config 80 * </pre></blockquote> 81 * the simplest invocation of the tool would be: 82 * <blockquote><pre> 83 * java -jar <var><b>install_dir</b></var>/lib/envcheck.jar \ 84 * java -Djava.security.policy=<var><b>my_policy</b></var> \ 85 * -jar <var><b>install_dir</b></var>/lib/start.jar mystart.config 86 * </pre></blockquote> 87 * Note that the entire command line, including the <code>java</code> command, 88 * is supplied as arguments to the tool. The <code>java</code> command used to 89 * run the tool may be different than the <code>java</code> command invoked by 90 * the command line under test. The first token in the command line being 91 * analyzed must not begin with a '-' and must end with the string "java". 92 * 93 * <a name="processing"></a> 94 * <h3>Processing Options</h3> 95 * <p> 96 * <dl> 97 * <dt><b><code>-traces</code></b> 98 * <dd>The implementation of a validity check may detect success or failure by 99 * handling an expected exception. By default, an error message will be 100 * generated in these cases, but the stack trace will be inhibited. This 101 * option is a hint that stack traces are desired. It is the responsibility 102 * of the individual plugin implementation to honor this option. 103 * </dd> 104 * 105 * <dt><b><code>-explain</code></b> 106 * <dd>By default, the output of a validity check will be a short message with 107 * enough detail to allow a knowledgeable user to interpret it; however, it 108 * may not be understandable to a novice user. The <code>-explain</code> 109 * option is a hint that may result in the generation of additional output 110 * describing the purpose and context of the check. An explanation is output 111 * the first time its associated message is output, and is not repeated. It 112 * is the responsibility of the individual plugin implementation to honor 113 * this option. 114 * </dd> 115 * 116 * <dt><b><code>-level</code> <var>info|warning|error</var></b> 117 * <dd>The tool supports three severity levels for message generation. 118 * <p> 119 * <dl> 120 * <dt><var>info</var> 121 * <dd>'success' messages or other non-failure oriented configuration data 122 * <dt><var>warning</var> 123 * <dd>a condition or value has been detected that is 'legal', but which 124 * could result in unintended behavior. An example is the use of 125 * <code>localhost</code> in a codebase annotation. 126 * <dt><var>error</var> 127 * <dd>a condition has been detected that is likely due to an error 128 * on the command line or in a configuration file. An example is 129 * assigning the value of a non-existant file name to the 130 * <code>java.util.logging.config.file</code> system property. 131 * </dl> 132 * <p> 133 * This option is used to set the level at which message records are 134 * generated. The default value is <var>warning</var>. 135 * </dd> 136 * 137 * <dt><b><code>-plugin</code> <var>file</var></b> 138 139 * <dd>Identifies a JAR file containing user supplied plugins that will be run 140 * after the standard plugins are run. All of the necessary support classes 141 * and resources required to support the plugins must be included in this 142 * file. The file must also include a resource named 143 * <code>META-INF/services/org.apache.river.tool.envcheck.Plugin</code>, which 144 * contains the class names of the plugins to run listed one per line. 145 * Every class listed must implement the 146 * <code>org.apache.river.tool.envcheck.Plugin</code> interface. This option 147 * may be supplied zero or more times. 148 * </dd> 149 * 150 * <dt><b><code>-security</code></b> 151 * <dd>A plugin specific option that is recognized by one of the bundled 152 * plugins. Specifying this option will activate a number of JAAS and JSSE 153 * checks. User supplied plugins wishing to recognize this option must 154 * implement the <code>isPlugOption</code> method of the 155 * <code>Plugin</code> interface. 156 * </dd> 157 * </dl> 158 * <p> 159 * <a name="examples"></a> 160 * <h3>Examples</h3> 161 162 * The following example will analyze a command line used to start a user 163 * service. The command being analyzed defines a classpath and two system 164 * properties, and names a class containing a <code>main</code> method: 165 166 * <p> 167 * <blockquote><pre> 168 * java -jar <var><b>install_dir</b></var>/lib/envcheck.jar -level info -explain -traces \ 169 * java -cp <var><b>mylib_dir</b></var>/myservice.jar:/jini/lib/jsk-platform.jar \ 170 * -Djava.security.policy=<var><b>my_policy</b></var> \ 171 * -Djava.server.rmi.codebase=http://myhost/myservice-dl.jar \ 172 * myservice.MyServiceImpl 173 * </pre></blockquote> 174 * In this case, the tool is limited to performing validity checks on the 175 * classpath, policy, and codebase values identified by the system properties 176 * and options provided on the service command line. The <code>-level</code>, 177 * <code>-explain</code>, and <code>-traces</code> options supplied will result 178 * in the most verbose output possible from the tool. 179 * <p> 180 * The following example will analyze a command line used to start reggie using 181 * the service starter. The command being analyzed uses a policy and service 182 * starter configuration located in the working directory: 183 * <p> 184 * <blockquote><pre> 185 * java -jar <var><b>install_dir</b></var>/lib/envcheck.jar -level error \ 186 * java -Djava.security.policy=<var><b>my_starterpolicy</b></var> \ 187 * -jar <var><b>install_dir</b></var>/lib/start.jar reggie.config 188 * </pre></blockquote> 189 * The tool can perform many more checks in this case because the 190 * bundled plugins include built-in knowledge about the service starter 191 * and its public configuration entries. The tool options used will minimize 192 * the output produced by the tool. 193 * <p> 194 * <a name="plugins"></a> 195 * <h3>Bundled Plugins</h3> 196 * A set of plugins are loaded automatically by the tool to perform some 197 * basic analysis. 198 * <p> 199 * If the command line being analyzed invokes the service starter, the tool will 200 * create a <code>Configuration</code> from the arguments of the command line 201 * being analyzed. Failure to create the <code>Configuration</code> will result 202 * in termination of the tool, otherwise the 203 * <code>org.apache.river.start.ServiceDescriptor</code>s provided by the 204 * <code>Configuration</code> are examined. The following checks are done for 205 * each <code>ServiceDescriptor</code>: 206 * <ul> 207 * <li>Verify that the <code>getPolicy</code> method returns a reference to a 208 * policy file that is valid and accessible 209 * <li>Check whether that policy grants <code>AllPermissions</code> to all 210 * protection domains 211 * </ul> 212 * The following checks are done for each 213 * <code>NonActivatableServiceDescriptor</code> and each 214 * <code>SharedActivatableServiceDescriptor</code> 215 * <ul> 216 * <li>Verify that calling <code>getServerConfigArgs</code> does not return 217 * <code>null</code> or an empty array 218 * <li>Verify that a <code>Configuration</code> can be constructed from those 219 * args 220 * <li>Verify that any entry in that <code>Configuration</code> named 221 * <code>initialLookupGroups</code> does not have a value of 222 * <code>ALL_GROUPS</code> 223 * <li>Verify that the export codebase is defined. For each component in the 224 * export codebase: 225 * <ul> 226 * <li>Verify that the URL is not malformed 227 * <li>If it is an HTTPMD URL, verify that the necessary protocol handler 228 * is installed 229 * <li>Check that domain names are fully qualified 230 * <li>Warn if an md5 HTTPMD URL is being used (md5 has a security hole) 231 * <li>Verify that the host name in the URL can be resolved 232 * <li>Verify that the host name does not resolve to a loopback address 233 * <li>Verify that it's possible to open a connection using the URL 234 * </ul> 235 * <li>If the <code>-security</code> option was specified: 236 * <ul> 237 * <li>Verify that <code>javax.net.ssl.trustStore</code> is defined and 238 * that the trust store it references is accessible 239 * <li>Check whether <code>org.apache.river.discovery.x500.trustStore</code> 240 * is defined, and if so that the trust store it references is 241 * accessible 242 * <li>Check whether <code>javax.net.ssl.keyStore</code> is defined, and 243 * if so that the key store it references is accessible 244 * <li>Verify that a login configuration is defined and that it is 245 * accessible and syntactically correct 246 * </ul> 247 * </ul> 248 * The following checks are done for each 249 * <code>SharedActivatableServiceDescriptor</code>: 250 * <ul> 251 * <li>Verify that any entry in that <code>Configuration</code> named 252 * <code>persistenceDirectory</code> refers to either an empty directory 253 * or a non-existant directory 254 * </ul> 255 * The following checks are done for each 256 * <code>SharedActivationGroupDescriptor</code>: 257 * <ul> 258 * <li>Verify that the activation system is running 259 * <li>Verify that the virtual machine (VM) created by that command is at 260 * least version 1.4 261 * <li>Verify that <code>jsk-policy.jar</code> is loaded from the extensions 262 * directory 263 * 264 * <li>Verify that <code>jsk-platform.jar</code> is in the classpath 265 * 266 * 267 * <li>If <code>java.util.logging.config.file</code> is defined in the 268 * properties returned by calling <code>getServerProperties</code> then 269 * verify that the file it references is accessible 270 * </ul> 271 * A subset of these checks are performed on the command line being analyzed: 272 * <ul> 273 * <li>Codebase/URL checks based on the value of 274 * <code>java.rmi.server.codebase</code> 275 * <li>Policy file checks based on the value of 276 * <code>java.security.policy</code> 277 * <li>Check for <code>jsk-policy</code> being loaded by the extension 278 * class loader 279 * <li>Check for <code>jsk-platform</code> in the classpath 280 * <li>Security checks if <code>-security</code> was specified 281 * <li>The logging config file check 282 * </ul> 283 * In all cases, check that the local host name does not resolve to the 284 * loopback address. 285 */ 286 287 public class EnvCheck { 288 289 /** the list of plugins instances to run */ 290 private ArrayList pluginList = new ArrayList(); 291 292 /** flag controlling the display of stack traces in the output */ 293 private boolean printStackTraces = false; 294 295 /** the command line arguments of the command being analyzed */ 296 private String[] args; 297 298 /** the <code>ServiceDescriptor</code>s obtained from the starter config */ 299 private ServiceDescriptor[] descriptors = new ServiceDescriptor[0]; 300 301 /** the localization resource bundle */ 302 private static ResourceBundle bundle; 303 304 /** the java command on the command line being checked */ 305 private String javaCmd; 306 307 /** the options on the command line being checked (never null) */ 308 String[] options = new String[0]; 309 310 /** the properties on the command line being checked (never null) */ 311 Properties properties = new Properties(); 312 313 /** the classpath on the command line being checked */ 314 String classpath = null; 315 316 /** the main class on the command line being checked */ 317 String mainClass = null; 318 319 /** the executable JAR file on the command line being checked */ 320 String jarToRun = null; 321 322 /** the list of plugins supplied via the -plugin option */ 323 static ArrayList pluginJarList = new ArrayList(); 324 325 /** the class loader for loading plugins */ 326 ClassLoader pluginLoader = EnvCheck.class.getClassLoader(); 327 328 /** the classpath of the tool, including the plugins (updated in run) */ 329 static String combinedClasspath = System.getProperty("java.class.path"); 330 331 /** 332 * The entry point for the tool. The localization resource bundle is 333 * located, the plugins are loaded, and the checks are performed. The system 334 * property <code>java.protocol.handler.pkgs</code> for the tool VM is set 335 * to <code>net.jini.url</code> to ensure that the tool can manipulate 336 * HTTPMD URLs. 337 * 338 * @param args the command line arguments 339 */ 340 public static void main(String[] args) { 341 System.setProperty("java.protocol.handler.pkgs", "net.jini.url"); 342 bundle = Util.getResourceBundle(EnvCheck.class); 343 if (args.length == 0) { 344 usage(); 345 System.exit(1); 346 } 347 findPlugins(args); 348 new EnvCheck().run(args); 349 } 350 351 /** 352 * Output the usage message. 353 */ 354 private static void usage(){ 355 System.err.println(Util.getString("envcheck.usage", bundle)); 356 } 357 358 /** 359 * Helper to print a localized string 360 * 361 * @param key the resource key 362 */ 363 private String getString(String key) { 364 return Util.getString(key, bundle); 365 } 366 367 /** 368 * Helper to print a localized string 369 * 370 * @param key the resource key 371 * @param val the value parameter expected by the message 372 */ 373 private String getString(String key, String val) { 374 return Util.getString(key, bundle, val); 375 } 376 377 /** 378 * Search the command line for user supplied plugin definitions 379 * and place them in the internal plugin list. 380 * 381 * @param cmdLine the original command line args 382 */ 383 private static void findPlugins(String[] cmdLine) { 384 int index = 0; 385 String arg; 386 while ((arg = cmdLine[index++]).startsWith("-")) { 387 if (arg.equals("-plugin")) { 388 String pluginName = cmdLine[index++]; 389 if (!(new File(pluginName).exists())) { 390 System.err.println(Util.getString("envcheck.noplugin", 391 bundle, 392 pluginName)); 393 System.exit(1); 394 } 395 combinedClasspath += File.pathSeparator + pluginName; 396 try { 397 pluginJarList.add(new URL("file:" + pluginName)); 398 } catch (MalformedURLException e) { // should never happen 399 e.printStackTrace(); 400 System.exit(1); 401 } 402 } else if (arg.equals("-level")) { 403 index++; 404 } 405 } 406 } 407 408 /** 409 * Parse the command line, identifying options for the tool and parsing 410 * the VM, system properties, options, JAR/main class and arguments 411 * for the command line being tested. The <code>-plugin</code> option 412 * is ignored by this parser since they must have been processed 413 * earlier. 414 * 415 * @param cmdLine the original command line arguments 416 */ 417 private void parseArgs(String[] cmdLine) { 418 int index = 0; 419 try { 420 while (true) { 421 String arg = cmdLine[index++]; 422 if (arg.equals("-traces")) { 423 printStackTraces = true; 424 Reporter.setPrintTraces(true); 425 } else if (arg.equals("-explain")) { 426 Reporter.setExplanation(true); 427 } else if (arg.equals("-level")) { 428 String val = cmdLine[index++]; 429 if (val.equals("info")) { 430 Reporter.setLevel(Reporter.INFO); 431 } else if (val.equals("warning")) { 432 Reporter.setLevel(Reporter.WARNING); 433 } else if (val.equals("error")) { 434 Reporter.setLevel(Reporter.ERROR); 435 } else { 436 System.err.println(getString("envcheck.badlevel")); 437 usage(); 438 System.exit(1); 439 } 440 } else if (arg.equals("-plugin")) { 441 index++; // already processed the plugin 442 } else if (isPluginOption(arg)) { //no additional work to do 443 } else if (arg.startsWith("-")) { 444 System.err.println(getString("envcheck.illegalopt", arg)); 445 usage(); 446 System.exit(1); 447 } else if (arg.endsWith("java")) { 448 javaCmd = arg; 449 break; 450 } else { 451 System.err.println(getString("envcheck.nojavacmd")); 452 usage(); 453 System.exit(1); 454 } 455 } 456 } catch (ArrayIndexOutOfBoundsException e) { 457 System.err.println(getString("envcheck.nocmdargs")); 458 usage(); 459 System.exit(1); 460 } 461 try { 462 ArrayList optList = new ArrayList(); 463 while (true) { 464 String opt = cmdLine[index++]; 465 if (!opt.startsWith("-")) { 466 mainClass = opt; 467 break; 468 } else if (opt.startsWith("-D")) { 469 int valueIndex = opt.indexOf("="); 470 String key = opt.substring(2, valueIndex); 471 String value = opt.substring(valueIndex + 1); 472 properties.put(key, value); 473 } else if (opt.equals("-cp") || opt.equals("-classpath")) { 474 classpath = cmdLine[index++]; 475 } else if (opt.equals("-jar")) { 476 jarToRun = cmdLine[index++]; 477 break; 478 } else { 479 optList.add(opt); 480 } 481 } 482 options = 483 (String[]) optList.toArray(new String[optList.size()]); 484 } catch (ArrayIndexOutOfBoundsException e) { 485 System.err.println(getString("noexecutable")); 486 usage(); 487 System.exit(1); 488 } 489 ArrayList argList = new ArrayList(); 490 while (index < cmdLine.length) { 491 argList.add(cmdLine[index++]); 492 } 493 args = (String[]) argList.toArray(new String[argList.size()]); 494 } 495 496 /** 497 * Return the <code>ServiceDescriptor</code>s contained in the service 498 * starter configuration. If the command being analyzed does not invoke the 499 * service starter, a zero-length array will be returned. 500 * 501 * @return the descriptors in the starter configuration or an empty array 502 */ 503 public ServiceDescriptor[] getDescriptors() { 504 return descriptors; 505 } 506 507 /** 508 * Return the <code>SharedActivationGroupDescriptor</code> contained in the 509 * service starter configuration. Returns <code>null</code> if there is no 510 * such descriptor, or if the command being analyzed does not invoke the 511 * service starter. 512 * 513 * @return the <code>SharedActivationGroupDescriptor</code> or 514 * <code>null</code> 515 */ 516 public SharedActivationGroupDescriptor getGroupDescriptor() { 517 for (int i = 0; i < descriptors.length; i++) { 518 if (descriptors[i] instanceof SharedActivationGroupDescriptor) { 519 return (SharedActivationGroupDescriptor) descriptors[i]; 520 } 521 } 522 return null; 523 } 524 525 /** 526 * Perform the runtime checks. If any user plugins were supplied, construct 527 * a class loader capable of loading them and modify 528 * <code>combinedClasspath</code> to make the classes available to subtasks. 529 * Load the plugins and parse the command line. The plugins must be 530 * available to the parser so that plugin specific options can be checked 531 * for. Load the service starter configuration if the service starter is 532 * being invoked. Execute all of the plugin classes in the order loaded. 533 * 534 * @param cmdLine the original command line arguments 535 */ 536 private void run(String[] cmdLine) { 537 if (pluginJarList.size() > 0) { 538 URL[] urls = 539 (URL[]) pluginJarList.toArray(new URL[pluginJarList.size()]); 540 pluginLoader = new RFC3986URLClassLoader(urls, pluginLoader); 541 } 542 loadPlugins(); 543 parseArgs(cmdLine); 544 if (jarToRun == null && classpath == null) { 545 System.err.println(getString("envcheck.missingclasspath")); 546 System.exit(1); 547 } 548 loadConfiguration(); 549 Iterator plugins = pluginList.iterator(); 550 while (plugins.hasNext()) { 551 Plugin plugin = (Plugin) plugins.next(); 552 try { 553 plugin.run(this); 554 } catch (SecurityException e) { // limp on if permission check fails 555 e.printStackTrace(); 556 } 557 } 558 int warningCount = Reporter.getWarningCount(); 559 int errorCount = Reporter.getErrorCount(); 560 if ((warningCount + errorCount) > 0) { 561 System.out.println(); 562 System.out.println(); 563 System.out.println(getString("envcheck.summaryseparator")); 564 } 565 if (warningCount > 0) { 566 System.out.println(getString("envcheck.warningheader") 567 + warningCount); 568 } 569 if (errorCount > 0) { 570 System.out.println(getString("envcheck.errorheader") + errorCount); 571 } 572 573 } 574 575 /** 576 * Check whether <code>arg</code> is a plugin specific option. The 577 * <code>isPluginOption</code> method of every plugin is called in 578 * case an option is shared among multiple plugins. 579 * 580 * @param arg the argument to check 581 * @return <code>true</code> if any plugin's <code>isPluginOption</code> 582 * method returns <code>true</code> 583 */ 584 private boolean isPluginOption(String arg) { 585 boolean gotOne = false; 586 Iterator plugins = pluginList.iterator(); 587 while (plugins.hasNext()) { 588 Plugin plugin = (Plugin) plugins.next(); 589 if (plugin.isPluginOption(arg)) { 590 gotOne = true; 591 } 592 } 593 return gotOne; 594 } 595 596 /** 597 * Instantiate the service starter configuration if the command line being 598 * analyzed runs the service starter. This is assumed to be true if the 599 * command line specified an executable JAR file named <code>start.jar</code> and 600 * if that file resides in the same directory as a file named 601 * <code>jsk-platform.jar</code>. Extract and save all of the service 602 * descriptors and service destructors from the configuration. If a 603 * <code>ConfigurationException</code> is thrown the tool will exit. If 604 * there are no <code>ServiceDescriptor</code> or 605 * <code>ServiceDestructor</code> entries supplied by the configuration a 606 * error is generated and a normal return is done. The instantiation of 607 * the configuration is done in a child process using the VM, properties, 608 * options and arguments supplied in the command line under test. 609 */ 610 private void loadConfiguration() { 611 if (jarToRun != null) { 612 if (!jarToRun.endsWith("start.jar")) { 613 return; 614 } 615 //XXX do existence check in parser 616 File starterFile = new File(jarToRun); 617 File jskLibDir = starterFile.getParentFile(); 618 File jskplatform = new File(jskLibDir, "jsk-platform.jar"); 619 if (!jskplatform.exists()) { 620 return; // presumably a user file coincidentally named start.jar 621 } 622 } else { 623 if (!mainClass.equals("org.apache.river.start.ServiceStarter")) { 624 return; 625 } 626 } 627 //XXX need to check validity of javaCmd and arg list length 628 Object lobj = 629 launch("org.apache.river.tool.envcheck.EnvCheck$GetDescriptors", args); 630 if (lobj instanceof ServiceDescriptor[]) { 631 descriptors = (ServiceDescriptor[]) lobj; 632 if (descriptors.length == 0) { 633 Reporter.print(new Message(Reporter.ERROR, 634 getString("envcheck.emptyconfig"), 635 null)); 636 } 637 } else if (lobj instanceof ConfigurationException) { 638 System.err.println(getString("envcheck.ssdescfailed")); 639 ((Throwable) lobj).printStackTrace(); 640 System.exit(1); 641 } else { 642 System.err.println(getString("envcheck.subtaskex", lobj.toString())); 643 ((Throwable) lobj).printStackTrace(); 644 System.exit(1); 645 } 646 } 647 648 /** 649 * Load the plugin classes. All of the resources named 650 * <code>META-INF/services/org.apache.river.tool.envcheck.Plugin</code> are 651 * read; each such resource is expected to contain a list of plugin class 652 * names, one per line (white space is trimmed). The corresponding classes 653 * are loaded and saved. Any other exceptions thrown while processing 654 * the plugin JAR files will cause the tool to exit. 655 */ 656 private void loadPlugins() { 657 Package pkg = getClass().getPackage(); 658 String pkgName = ""; 659 if (pkg != null) { 660 pkgName = pkg.getName(); 661 } 662 pkgName = pkgName.replace('.', '/'); 663 if (pkgName.length() > 0) { 664 pkgName += "/"; 665 } 666 Iterator plugins = 667 Service.providers(org.apache.river.tool.envcheck.Plugin.class, 668 pluginLoader); 669 while (plugins.hasNext()) { 670 pluginList.add(plugins.next()); 671 } 672 } 673 674 /** 675 * Get the command line arguments of the command being analyzed. 676 * 677 * @return the args 678 */ 679 // unused, but supplied to support user developed plugins 680 public String[] getArgs() { 681 return args; 682 } 683 684 /** 685 * Return the flag indicating whether to output stack traces that 686 * result from a check. 687 */ 688 public boolean printStacks() { 689 return printStackTraces; 690 } 691 692 /** 693 * Launch a child VM using the <code>java</code> command, properties, and 694 * options supplied on the command line being analyzed. If an executable JAR 695 * file was specified, the classpath of the child VM consists of the JAR 696 * file name augmented with the classpath of the tool and plugins. If a main 697 * class was specified, the classpath of the child VM consists of the 698 * <code>-cp/-classpath</code> option value of the command line being 699 * analyzed augmented with the classpath of the tool and plugins. 700 * 701 * @param task the class name of the task to launch, which must implement 702 * the <code>SubVMTask</code> interface 703 * @param args the arguments to pass to the main method of the task 704 * @return the result or exception returned by the subtask supplied as a 705 * serialized object written on the subtask's 706 * <code>System.out</code> stream. 707 */ 708 public Object launch(String task, String[] args) { 709 String cp; 710 if (jarToRun != null) { 711 cp = jarToRun + File.pathSeparator; 712 } else { 713 cp = classpath + File.pathSeparator; 714 } 715 cp += combinedClasspath; 716 String[] opts = new String[options.length + 2]; 717 opts[0] = "-cp"; 718 opts[1] = cp; 719 System.arraycopy(options, 0, opts, 2, options.length); 720 if (args == null) { 721 args = new String[0]; 722 } 723 String[] subvmArgs = new String[args.length + 1]; 724 subvmArgs[0] = task; 725 System.arraycopy(args, 0, subvmArgs, 1, args.length); 726 return launch(javaCmd, properties, opts, subvmArgs); 727 } 728 729 /** 730 * Launch a child VM using the <code>java</code> command, properties, and 731 * options supplied on the command line. If an executable JAR file was 732 * specified, the classpath of the child VM consists of the JAR file name 733 * augmented with the classpath of the tool and plugins. If a main class was 734 * specified, the classpath of the child VM consists of the 735 * <code>-cp/-classpath</code> option value of the command line being 736 * analyzed augmented with the classpath of the tool and plugins. 737 * 738 * @param task the class name of the task to launch, which must implement 739 * the <code>SubVMTask</code> interface 740 * @return the result or exception returned by the subtask supplied as a 741 * serialized object written on the subtask's 742 * <code>System.out</code> stream. 743 */ 744 public Object launch(String task) { 745 return launch(task, null); 746 } 747 748 /** 749 * Return a property value that was specified on the command line being 750 * analyzed. Only properties explicitly defined on the command line will 751 * resolve to a value. 752 * 753 * @param key the name of the property 754 * @return the property value, or <code>null</code> if undefined 755 */ 756 public String getProperty(String key) { 757 return properties.getProperty(key); 758 } 759 760 /** 761 * Return a copy of the properties that were specified on the 762 * command line being analyzed. The caller may modify the returned 763 * properties object. 764 * 765 * @return the properties, which may be empty 766 */ 767 public Properties getProperties() { 768 return (Properties) properties.clone(); 769 } 770 771 /** 772 * Launch a subtask VM using the <code>java</code> command given by 773 * <code>javaCmd</code>. If <code>javaCmd</code> is <code>null</code>, the 774 * command to run is derived from the value of the <code>java.home</code> 775 * property of the tool VM. The first value in <code>args</code> must name 776 * a class that implements the <code>SubVMTask</code> interface. The 777 * <code>props</code> and <code>opts</code> arrays must contain fully 778 * formatted command line values for properties and options 779 * (i.e. "-Dfoo=bar" or "-opt"). <code>opts</code> must include a 780 * <code>-cp</code> or <code>-classpath</code> option and its value must 781 * completely specify the classpath required to run the subtask. 782 * 783 * @param javaCmd the <code>java</code> command to execute, or 784 * <code>null</code> to create another instance of the 785 * tool VM 786 * @param props properties to define, which may be <code>null</code> or 787 * empty 788 * @param opts options to define, which must include a classpath 789 * definition 790 * @param args arguments to pass to the child VM 791 * @return the result or exception returned by the subtask supplied 792 * as a serialized object written on the subtask's 793 * <code>System.out</code> stream. 794 * 795 * @throws IllegalArgumentException if <code>opts</code> does not 796 * include a classpath definition, or if <code>args[0]</code> 797 * does not contain the name of a class that implements 798 * <code>SubVMTask</code> 799 */ 800 public Object launch(String javaCmd, 801 Properties props, 802 String[] opts, 803 String[] args) 804 { 805 if (args == null || args.length == 0) { 806 throw new IllegalArgumentException("No class name in args[0]"); 807 } 808 // make sure subtask is valid to avoid obscure failures at exec time 809 String taskName = args[0]; 810 try { 811 Class taskClass = Class.forName(taskName, true, pluginLoader); 812 if (!SubVMTask.class.isAssignableFrom(taskClass)) { 813 throw new IllegalArgumentException(taskName 814 + " does not implement " 815 + "SubVMTask"); 816 } 817 // make sure inner classes are static 818 if (taskClass.getDeclaringClass() != null) { 819 if ((taskClass.getModifiers() & Modifier.STATIC) == 0) { 820 throw new IllegalArgumentException(taskName 821 + " must be a static class"); 822 } 823 } 824 } catch (ClassNotFoundException e) { 825 throw new IllegalArgumentException("Class not found: " + taskName); 826 } 827 if (javaCmd == null) { 828 javaCmd = System.getProperty("java.home"); 829 javaCmd += File.separator + "bin" + File.separator + "java"; 830 } 831 ArrayList cmdList = new ArrayList(); 832 cmdList.add(javaCmd); 833 boolean gotClasspath = false; 834 if (opts != null) { 835 for (int i = 0; i < opts.length; i++) { 836 if (opts[i].equals("-cp") || opts[i].equals("-classpath")) 837 if (i + 1 == opts.length) { 838 throw new IllegalArgumentException("classpath option " 839 + "does not have " 840 + "a value"); 841 } 842 gotClasspath = true; 843 cmdList.add(opts[i]); 844 } 845 } 846 if (!gotClasspath) { 847 throw new IllegalArgumentException("no classpath defined"); 848 } 849 if (props != null) { 850 Enumeration en = props.propertyNames(); 851 while (en.hasMoreElements()) { 852 String key = (String) en.nextElement(); 853 String value = props.getProperty(key); 854 cmdList.add("-D" + key + "=" + value); 855 } 856 } 857 cmdList.add("org.apache.river.tool.envcheck.SubVM"); 858 for (int i = 0; i < args.length; i++) { 859 cmdList.add(args[i]); 860 } 861 try { 862 String[] argList = 863 (String[]) cmdList.toArray(new String[cmdList.size()]); 864 // for (int i = 0; i < argList.length; i++) { 865 // System.out.print(argList[i] + " "); 866 // } 867 // System.out.println(); 868 Process p = Runtime.getRuntime().exec(argList); 869 Pipe pipe = new Pipe("errorPipe", 870 p.getErrorStream(), 871 System.err, 872 "Child VM: "); 873 ObjectInputStream s = new ObjectInputStream(p.getInputStream()); 874 Object o = s.readObject(); 875 pipe.waitTillEmpty(5000); 876 return o; 877 } catch (Exception e) { 878 return e; 879 } 880 } 881 882 /** 883 * Launch a subtask using the environment defined by the given service 884 * descriptors. Calling this method is equivalent to calling 885 * <code>launch(d, g, taskName, null)</code>. 886 * 887 * @param d the services descriptor, which may be <code>null</code> 888 * @param gd the group descriptor, which may be <code>null</code> 889 * @param taskName the name of the subtask to run 890 * @return the result or exception returned by the subtask supplied as a 891 * serialized object written on the subtask's 892 * <code>System.out</code> stream. 893 */ 894 public Object launch(NonActivatableServiceDescriptor d, 895 SharedActivationGroupDescriptor gd, 896 String taskName) 897 { 898 return launch(d, gd, taskName, null); 899 } 900 901 /** 902 * Launch a subtask using the environment defined by the given service 903 * descriptors. 904 * <p> 905 * If <code>d</code> and <code>gd</code> are both <code>null</code>, then 906 * calling this method is equivalent to calling 907 * <code>launch(taskName, args)</code>. 908 * <p> 909 910 * If <code>d</code> is <code>null</code> and <code>gd</code> is 911 * non-<code>null</code> then the properties are taken from 912 * <code>gd.getServerProperties()</code> and the 913 * <code>java.security.policy</code> property is added or replaced with the 914 * value of <code>gd.getPolicy()</code>. The options are taken from 915 * <code>gd.getServerOptions()</code>, but any <code>-cp/-classpath</code> 916 * option is discarded; a <code>-cp</code> option is added that is the value 917 * of <code>gd.getClasspath()</code> augmented with the classpath of the 918 * tool and plugins. If <code>gd.getServerCommand()</code> is 919 * non-<code>null</code>, its value is used to invoke the child VM; 920 * otherwise the <code>java</code> command of the command line being 921 * analyzed is used. The arguments passed to the child VM consist of an 922 * array whose first element is <code>taskName</code> and whose remaining 923 * elements are taken from <code>args</code>. 924 925 * <p> 926 927 * If <code>d</code> is not <code>null</code>, but <code>gd</code> is 928 * <code>null</code>, then if <code>d</code> is an instance of 929 * <code>SharedActivatableServiceDescriptor</code> an 930 * <code>IllegalArgumentException</code> is thrown. Otherwise the properties 931 * and options are taken from the command line being analyzed. The 932 * <code>java.security.policy</code> property is added or replaced using the 933 * value of <code>d.getPolicy()</code>. The <code>-cp/-classpath</code> 934 * option is replaced with the value of <code>d.getImportCodebase()</code> 935 * augmented with the classpath of the tool and plugins. The arguments 936 * passed to the child VM consist of <code>taskName</code> followed by 937 * <code>args</code> if <code>args</code> is non-<code>null</code>, or 938 * followed by <code>d.getServerConfigArgs()</code> otherwise. The VM is 939 * invoked using the <code>java</code> command of the command line being 940 * analyzed. 941 942 * <p> 943 944 * if <code>d</code> and <code>gd</code> are both non-<code>null</code> then 945 * if <code>d</code> is an instance of 946 * <code>SharedActivatableServiceDescriptor</code> then the properties, 947 * options, and <code>java</code> command are taken from 948 * <code>gd.getServerProperties()</code>, 949 * <code>gd.getServerOptions()</code>, and 950 * <code>gd.getServerCommand()</code>; however, if the value of 951 * <code>gd.getServerCommand()</code> is <code>null</code>, the 952 * <code>java</code> command is taken from the command line being 953 * analysed. If <code>d</code> is not an instance of 954 * <code>SharedActivatableServiceDescriptor</code> then the properties, 955 * options, and <code>java</code> command are taken from the command line 956 * being analyzed. In all cases the <code>java.security.policy</code> 957 * property is added or replaced using the value of 958 * <code>d.getPolicy()</code>. The <code>-cp/-classpath</code> option is 959 * added or replaced with the value of <code>d.getImportCodebase()</code> 960 * augmented with the value of the classpath of the tool and plugins. The 961 * arguments passed to the child VM consist of <code>taskName</code> 962 * followed by <code>args</code> if <code>args</code> is 963 * non-<code>null</code>, or followed by 964 * <code>d.getServerConfigArgs()</code> otherwise. 965 966 * <p> 967 * 968 * @param d the service descriptor, which may be <code>null</code> 969 * @param gd the group descriptor, which may be <code>null</code> 970 * @param taskName the name of the subtask to run 971 * @param args the arguments to pass to the child VM, which may be 972 * <code>null</code> 973 * @return the result or exception returned by the subtask supplied as a 974 * serialized object written on the subtask's 975 * <code>System.out</code> stream. 976 */ 977 public Object launch(NonActivatableServiceDescriptor d, 978 SharedActivationGroupDescriptor gd, 979 String taskName, 980 String[] args) 981 { 982 if (d == null && gd == null) { 983 return launch(taskName, args); 984 } 985 //build taskArgs array 986 if (args == null) { 987 if (d != null) { 988 args = d.getServerConfigArgs(); 989 if (args == null || args.length == 0) { 990 return new IllegalArgumentException("No configuration args " 991 + "in descriptor"); 992 } 993 } else { 994 args = new String[0]; 995 } 996 } 997 String[] taskArgs = new String[args.length + 1]; 998 taskArgs[0] = taskName; 999 System.arraycopy(args, 0, taskArgs, 1, args.length); 1000 1001 if (d == null && gd != null) { 1002 Properties props = gd.getServerProperties(); 1003 props.put("java.security.policy", gd.getPolicy()); 1004 ArrayList l = new ArrayList(); 1005 String[] opts = gd.getServerOptions(); 1006 if (opts != null) { 1007 for (int i = 0; i < opts.length; i++ ) { 1008 if (opts[i].equals("-cp")) { 1009 i++; // bump past value 1010 } else { 1011 l.add(opts[i]); 1012 } 1013 } 1014 } 1015 l.add("-cp"); 1016 l.add(gd.getClasspath() + File.pathSeparator + combinedClasspath); 1017 opts = (String[]) l.toArray(new String[l.size()]); 1018 String cmd = gd.getServerCommand(); 1019 if (cmd == null) { 1020 cmd = javaCmd; 1021 } 1022 return launch(cmd, props, opts, taskArgs); 1023 } else if (d != null && gd == null) { 1024 if (d instanceof SharedActivatableServiceDescriptor) { 1025 throw new IllegalArgumentException("no group for service"); 1026 } 1027 Properties props = getProperties(); 1028 props.put("java.security.policy", d.getPolicy()); 1029 ArrayList l = new ArrayList(); 1030 for (int i = 0; i < options.length; i++ ) { 1031 if (options[i].equals("-cp") 1032 || options[i].equals("-classpath")) { 1033 i++; // bump past value 1034 } else { 1035 l.add(options[i]); 1036 } 1037 } 1038 l.add("-cp"); 1039 l.add(d.getImportCodebase() 1040 + File.pathSeparator 1041 + combinedClasspath); 1042 String[] opts = (String[]) l.toArray(new String[l.size()]); 1043 return launch(javaCmd, props, opts, taskArgs); 1044 } else if (d != null && gd != null) { 1045 Properties props = getProperties(); 1046 String[] opts = options; 1047 String vm = null; 1048 if (d instanceof SharedActivatableServiceDescriptor) { 1049 props = gd.getServerProperties(); 1050 if (props == null) { 1051 props = new Properties(); 1052 } 1053 opts = gd.getServerOptions(); 1054 vm = gd.getServerCommand(); 1055 } 1056 if (vm == null) { 1057 vm = javaCmd; 1058 } 1059 props.put("java.security.policy", d.getPolicy()); 1060 ArrayList l = new ArrayList(); 1061 if (opts != null) { 1062 for (int i = 0; i < opts.length; i++ ) { 1063 if (opts[i].equals("-cp") 1064 || opts[i].equals("-classpath")) { 1065 i++; // bump past value 1066 } else { 1067 l.add(opts[i]); 1068 } 1069 } 1070 } 1071 l.add("-cp"); 1072 l.add(d.getImportCodebase() 1073 + File.pathSeparator 1074 + combinedClasspath); 1075 opts = (String[]) l.toArray(new String[l.size()]); 1076 return launch(vm, props, opts, taskArgs); 1077 } else { 1078 throw new IllegalStateException("Should never get here"); 1079 } 1080 } 1081 1082 /** 1083 * Return the <code>java</code> command for the command line being analyzed. 1084 * 1085 * @return the <code>java</code> command 1086 */ 1087 public String getJavaCmd() { 1088 return javaCmd; 1089 } 1090 1091 /** 1092 * Check for the existence of a file identified by a property 1093 * supplied on the command line being analyzed. 1094 * 1095 * @param prop the name of the property 1096 * @param desc a brief description of the file 1097 * @return the property value, or <code>null</code> if undefined 1098 */ 1099 public String checkFile(String prop, String desc) { 1100 String name = getProperty(prop); 1101 if (name == null) { 1102 return getString("util.undef", desc); 1103 } 1104 return Util.checkFileName(name, desc); 1105 } 1106 1107 /** 1108 * Return the name of the executable JAR file supplied on the 1109 * command line being analyzed. 1110 * 1111 * @return the JAR file name, or <code>null</code> if the command line 1112 * did not specify one. 1113 */ 1114 public String getJarToRun() { 1115 return jarToRun; 1116 } 1117 1118 /** 1119 * Get the classpath provided by the command line being analyzed. If 1120 * <code>getJarToRun()</code> returns a non-<code>null</code> value then 1121 * its value is returned. Otherwise the value supplied by the command 1122 * line <code>-cp/-classpath</code> option is returned. 1123 * 1124 * @return the classpath supplied on the command line being analyzed. 1125 */ 1126 public String getClasspath() { 1127 if (jarToRun != null) { 1128 return jarToRun; 1129 } 1130 return classpath; 1131 } 1132 1133 /** 1134 * A subtask which returns the service descriptors and service 1135 * destructors supplied by the service starter configuration constructed 1136 * from <code>args</code>. 1137 */ 1138 static class GetDescriptors implements SubVMTask { 1139 1140 public Object run(String[] args) { 1141 Configuration starterConfig = null; 1142 try { 1143 starterConfig = ConfigurationProvider.getInstance(args); 1144 } catch (ConfigurationException e) { 1145 return e; 1146 } 1147 try { 1148 ServiceDescriptor[] d1 = (ServiceDescriptor[]) 1149 starterConfig.getEntry("org.apache.river.start", 1150 "serviceDescriptors", 1151 ServiceDescriptor[].class, 1152 new ServiceDescriptor[0]); 1153 ServiceDescriptor[] d2 = (ServiceDescriptor[]) 1154 starterConfig.getEntry("org.apache.river.start", 1155 "serviceDestructors", 1156 ServiceDescriptor[].class, 1157 new ServiceDescriptor[0]); 1158 ServiceDescriptor[] descriptors = 1159 new ServiceDescriptor[d1.length + d2.length]; 1160 System.arraycopy(d1, 0, descriptors, 0, d1.length); 1161 System.arraycopy(d2, 0, descriptors, d1.length, d2.length); 1162 return descriptors; 1163 } catch (ConfigurationException e) { 1164 return e; 1165 } 1166 } 1167 } 1168 1169 /** 1170 * An I/O redirection pipe. A daemon thread copies data from an input 1171 * stream to an output stream. An optional annotation may be provided 1172 * which will prefix each line of the copied data with a label which 1173 * can be used to identify the source. 1174 */ 1175 private class Pipe implements Runnable { 1176 1177 /** the line separator character */ 1178 private final static byte SEPARATOR = (byte) '\n'; 1179 1180 /** output line buffer */ 1181 private ByteArrayOutputStream bufOut = new ByteArrayOutputStream(); 1182 1183 /** the input stream */ 1184 private InputStream in; 1185 1186 /** the output PrintStream */ 1187 private PrintStream stream; 1188 1189 /** the output stream annotation */ 1190 private String annotation; 1191 1192 /** the thread to process the data */ 1193 private Thread outThread; 1194 1195 /** 1196 * Create a new Pipe object and start the thread to handle the data. 1197 * 1198 * @param name the name to assign to the thread 1199 * @param in input stream from which pipe input flows 1200 * @param stream the stream to which output will be sent 1201 * @param a the annotation for prepending text to logged lines 1202 */ 1203 Pipe(String name, 1204 InputStream in, 1205 PrintStream stream, 1206 String a) 1207 { 1208 this.in = in; 1209 this.stream = stream; 1210 this.annotation = a; 1211 outThread = new Thread(this, name); 1212 outThread.setDaemon(true); 1213 outThread.start(); 1214 } 1215 1216 /** 1217 * Wait until the run method terminates due to reading EOF on input 1218 * 1219 * @param timeout max time to wait for the thread to terminate 1220 */ 1221 void waitTillEmpty(int timeout) { 1222 try { 1223 outThread.join(timeout); 1224 } catch (InterruptedException ignore) { 1225 } 1226 } 1227 1228 /** 1229 * Read and write data until EOF is detected. Flush any remaining data to 1230 * the output steam and return, terminating the thread. 1231 */ 1232 public void run() { 1233 byte[] buf = new byte[256]; 1234 int count; 1235 try { 1236 /* read bytes till there are no more. */ 1237 while ((count = in.read(buf)) != -1) { 1238 write(buf, count); 1239 } 1240 1241 /* If annotating, flush internal buffer... may not have ended on a 1242 * line separator, we also need a last annotation if 1243 * something was left. 1244 */ 1245 String lastInBuffer = bufOut.toString(); 1246 bufOut.reset(); 1247 if (lastInBuffer.length() > 0) { 1248 if (annotation != null) { 1249 stream.print(annotation); 1250 } 1251 stream.println(lastInBuffer); 1252 } 1253 } catch (IOException e) { 1254 } 1255 } 1256 1257 /** 1258 * Write each byte in the give byte array. 1259 * 1260 * @param b the array of input bytes 1261 * @param len the number data bytes in the array 1262 */ 1263 private void write(byte b[], int len) throws IOException { 1264 if (len < 0) { 1265 throw new ArrayIndexOutOfBoundsException(len); 1266 } 1267 for (int i = 0; i < len; i++) { 1268 write(b[i]); 1269 } 1270 } 1271 1272 /** 1273 * If not annotated, write the byte to the stream immediately. Otherwise, 1274 * write a byte of data to the internal buffer. If we have matched a line 1275 * separator, then the currently buffered line is sent to the output writer 1276 * with a prepended annotation string. 1277 */ 1278 private void write(byte b) throws IOException { 1279 1280 bufOut.write(b); 1281 1282 // write buffered line if line separator detected 1283 if (b == SEPARATOR) { 1284 String s = bufOut.toString(); 1285 bufOut.reset(); 1286 if (annotation != null) { 1287 stream.print(annotation); 1288 } 1289 stream.print(s); 1290 } 1291 } 1292 } 1293 }