001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.admin; 028 029 030 031 import static org.opends.messages.AdminMessages.*; 032 import static org.opends.server.loggers.ErrorLogger.*; 033 import static org.opends.server.loggers.debug.DebugLogger.*; 034 import static org.opends.server.util.StaticUtils.*; 035 036 import java.io.BufferedReader; 037 import java.io.File; 038 import java.io.FileFilter; 039 import java.io.IOException; 040 import java.io.InputStream; 041 import java.io.InputStreamReader; 042 import java.lang.reflect.Method; 043 import java.net.MalformedURLException; 044 import java.net.URL; 045 import java.net.URLClassLoader; 046 import java.util.ArrayList; 047 import java.util.HashSet; 048 import java.util.LinkedList; 049 import java.util.List; 050 import java.util.Set; 051 import java.util.jar.JarEntry; 052 import java.util.jar.JarFile; 053 054 import org.opends.messages.Message; 055 import org.opends.server.admin.std.meta.RootCfgDefn; 056 import org.opends.server.core.DirectoryServer; 057 import org.opends.server.loggers.debug.DebugTracer; 058 import org.opends.server.types.DebugLogLevel; 059 import org.opends.server.types.InitializationException; 060 import org.opends.server.util.Validator; 061 062 063 064 /** 065 * Manages the class loader which should be used for loading 066 * configuration definition classes and associated extensions. 067 * <p> 068 * For extensions which define their own extended configuration 069 * definitions, the class loader will make sure that the configuration 070 * definition classes are loaded and initialized. 071 * <p> 072 * Initially the class loader provider is disabled, and calls to the 073 * {@link #getClassLoader()} will return the system default class 074 * loader. 075 * <p> 076 * Applications <b>MUST NOT</b> maintain persistent references to the 077 * class loader as it can change at run-time. 078 */ 079 public final class ClassLoaderProvider { 080 081 /** 082 * The tracer object for the debug logger. 083 */ 084 private static final DebugTracer TRACER = getTracer(); 085 086 /** 087 * Private URLClassLoader implementation. This is only required so 088 * that we can provide access to the addURL method. 089 */ 090 private static final class MyURLClassLoader extends URLClassLoader { 091 092 /** 093 * Create a class loader with the default parent class loader. 094 */ 095 public MyURLClassLoader() { 096 super(new URL[0]); 097 } 098 099 100 101 /** 102 * Create a class loader with the provided parent class loader. 103 * 104 * @param parent 105 * The parent class loader. 106 */ 107 public MyURLClassLoader(ClassLoader parent) { 108 super(new URL[0], parent); 109 } 110 111 112 113 /** 114 * Add a Jar file to this class loader. 115 * 116 * @param jarFile 117 * The name of the Jar file. 118 * @throws MalformedURLException 119 * If a protocol handler for the URL could not be found, 120 * or if some other error occurred while constructing 121 * the URL. 122 * @throws SecurityException 123 * If a required system property value cannot be 124 * accessed. 125 */ 126 public void addJarFile(File jarFile) throws SecurityException, 127 MalformedURLException { 128 addURL(jarFile.toURI().toURL()); 129 } 130 131 } 132 133 // The name of the manifest file listing the core configuration 134 // definition classes. 135 private static final String CORE_MANIFEST = "core.manifest"; 136 137 // The name of the manifest file listing a extension's configuration 138 // definition classes. 139 private static final String EXTENSION_MANIFEST = "extension.manifest"; 140 141 // The name of the lib directory. 142 private static final String LIB_DIR = "lib"; 143 144 // The name of the extensions directory. 145 private static final String EXTENSIONS_DIR = "extensions"; 146 147 // The singleton instance. 148 private static final ClassLoaderProvider INSTANCE = new ClassLoaderProvider(); 149 150 151 152 /** 153 * Get the single application wide class loader provider instance. 154 * 155 * @return Returns the single application wide class loader provider 156 * instance. 157 */ 158 public static ClassLoaderProvider getInstance() { 159 return INSTANCE; 160 } 161 162 // Set of registered Jar files. 163 private Set<File> jarFiles = new HashSet<File>(); 164 165 // Underlying class loader used to load classes and resources (null 166 // if disabled). 167 // 168 // We contain a reference to the URLClassLoader rather than 169 // sub-class it so that it is possible to replace the loader at 170 // run-time. For example, when removing or replacing extension Jar 171 // files (the URLClassLoader only supports adding new 172 // URLs, not removal). 173 private MyURLClassLoader loader = null; 174 175 176 177 // Private constructor. 178 private ClassLoaderProvider() { 179 // No implementation required. 180 } 181 182 183 184 /** 185 * Add the named extensions to this class loader provider. 186 * 187 * @param extensions 188 * The names of the extensions to be loaded. The names 189 * should not contain any path elements and must be located 190 * within the extensions folder. 191 * @throws InitializationException 192 * If one of the extensions could not be loaded and 193 * initialized. 194 * @throws IllegalStateException 195 * If this class loader provider is disabled. 196 * @throws IllegalArgumentException 197 * If one of the extension names was not a single relative 198 * path name element or was an absolute path. 199 */ 200 public synchronized void addExtension(String... extensions) 201 throws InitializationException, IllegalStateException, 202 IllegalArgumentException { 203 Validator.ensureNotNull(extensions); 204 205 if (loader == null) { 206 throw new IllegalStateException( 207 "Class loader provider is disabled."); 208 } 209 210 File libPath = new File(DirectoryServer.getServerRoot(), LIB_DIR); 211 File extensionsPath = new File(libPath, EXTENSIONS_DIR); 212 213 ArrayList<File> files = new ArrayList<File>(extensions.length); 214 for (String extension : extensions) { 215 File file = new File(extensionsPath, extension); 216 217 // For security reasons we need to make sure that the file name 218 // passed in did not contain any path elements and names a file 219 // in the extensions folder. 220 221 // Can handle potential null parent. 222 if (!extensionsPath.equals(file.getParentFile())) { 223 throw new IllegalArgumentException("Illegal file name: " 224 + extension); 225 } 226 227 // The file is valid. 228 files.add(file); 229 } 230 231 // Add the extensions. 232 addExtension(files.toArray(new File[files.size()])); 233 } 234 235 236 237 /** 238 * Disable this class loader provider and removed any registered 239 * extensions. 240 * 241 * @throws IllegalStateException 242 * If this class loader provider is already disabled. 243 */ 244 public synchronized void disable() throws IllegalStateException { 245 if (loader == null) { 246 throw new IllegalStateException( 247 "Class loader provider already disabled."); 248 } 249 loader = null; 250 jarFiles = new HashSet<File>(); 251 } 252 253 254 255 /** 256 * Enable this class loader provider using the application's 257 * class loader as the parent class loader. 258 * 259 * @throws InitializationException 260 * If the class loader provider could not initialize 261 * successfully. 262 * @throws IllegalStateException 263 * If this class loader provider is already enabled. 264 */ 265 public synchronized void enable() throws InitializationException, 266 IllegalStateException { 267 enable(RootCfgDefn.class.getClassLoader()); 268 } 269 270 271 272 /** 273 * Enable this class loader provider using the provided parent class 274 * loader. 275 * 276 * @param parent 277 * The parent class loader. 278 * @throws InitializationException 279 * If the class loader provider could not initialize 280 * successfully. 281 * @throws IllegalStateException 282 * If this class loader provider is already enabled. 283 */ 284 public synchronized void enable(ClassLoader parent) 285 throws InitializationException, IllegalStateException { 286 if (loader != null) { 287 throw new IllegalStateException( 288 "Class loader provider already enabled."); 289 } 290 291 if (parent != null) { 292 loader = new MyURLClassLoader(parent); 293 } else { 294 loader = new MyURLClassLoader(); 295 } 296 297 // Forcefully load all configuration definition classes in 298 // OpenDS.jar. 299 initializeCoreComponents(); 300 301 // Put extensions jars into the class loader and load all 302 // configuration definition classes in that they contain. 303 initializeAllExtensions(); 304 } 305 306 307 308 /** 309 * Gets the class loader which should be used for loading classes 310 * and resources. When this class loader provider is disabled, the 311 * system default class loader will be returned by default. 312 * <p> 313 * Applications <b>MUST NOT</b> maintain persistent references to 314 * the class loader as it can change at run-time. 315 * 316 * @return Returns the class loader which should be used for loading 317 * classes and resources. 318 */ 319 public synchronized ClassLoader getClassLoader() { 320 if (loader != null) { 321 return loader; 322 } else { 323 return ClassLoader.getSystemClassLoader(); 324 } 325 } 326 327 328 329 /** 330 * Indicates whether this class loader provider is enabled. 331 * 332 * @return Returns <code>true</code> if this class loader provider 333 * is enabled. 334 */ 335 public synchronized boolean isEnabled() { 336 return loader != null; 337 } 338 339 340 341 /** 342 * Add the named extensions to this class loader. 343 * 344 * @param extensions 345 * The names of the extensions to be loaded. 346 * @throws InitializationException 347 * If one of the extensions could not be loaded and 348 * initialized. 349 */ 350 private synchronized void addExtension(File... extensions) 351 throws InitializationException { 352 // First add the Jar files to the class loader. 353 List<JarFile> jars = new LinkedList<JarFile>(); 354 for (File extension : extensions) { 355 if (jarFiles.contains(extension)) { 356 // Skip this file as it is already loaded. 357 continue; 358 } 359 360 // Attempt to load it. 361 jars.add(loadJarFile(extension)); 362 363 // Register the Jar file with the class loader. 364 try { 365 loader.addJarFile(extension); 366 } catch (Exception e) { 367 if (debugEnabled()) { 368 TRACER.debugCaught(DebugLogLevel.ERROR, e); 369 } 370 371 Message message = ERR_ADMIN_CANNOT_OPEN_JAR_FILE. 372 get(extension.getName(), extension.getParent(), 373 stackTraceToSingleLineString(e)); 374 throw new InitializationException(message); 375 } 376 jarFiles.add(extension); 377 } 378 379 // Now forcefully load the configuration definition classes. 380 for (JarFile jar : jars) { 381 initializeExtension(jar); 382 } 383 } 384 385 386 387 /** 388 * Put extensions jars into the class loader and load all 389 * configuration definition classes in that they contain. 390 * 391 * @throws InitializationException 392 * If the extensions folder could not be accessed or if a 393 * extension jar file could not be accessed or if one of 394 * the configuration definition classes could not be 395 * initialized. 396 */ 397 private void initializeAllExtensions() 398 throws InitializationException { 399 File libPath = new File(DirectoryServer.getServerRoot(), LIB_DIR); 400 File extensionsPath = new File(libPath, EXTENSIONS_DIR); 401 402 try { 403 if (!extensionsPath.exists()) { 404 // The extensions directory does not exist. This is not a 405 // critical problem. 406 Message message = ERR_ADMIN_NO_EXTENSIONS_DIR.get( 407 String.valueOf(extensionsPath)); 408 logError(message); 409 return; 410 } 411 412 if (!extensionsPath.isDirectory()) { 413 // The extensions directory is not a directory. This is more 414 // critical. 415 Message message = 416 ERR_ADMIN_EXTENSIONS_DIR_NOT_DIRECTORY.get( 417 String.valueOf(extensionsPath)); 418 throw new InitializationException(message); 419 } 420 421 // Get each extension file name. 422 FileFilter filter = new FileFilter() { 423 424 /** 425 * Must be a Jar file. 426 */ 427 public boolean accept(File pathname) { 428 if (!pathname.isFile()) { 429 return false; 430 } 431 432 String name = pathname.getName(); 433 return name.endsWith(".jar"); 434 } 435 436 }; 437 438 // Add and initialize the extensions. 439 addExtension(extensionsPath.listFiles(filter)); 440 } catch (InitializationException e) { 441 if (debugEnabled()) { 442 TRACER.debugCaught(DebugLogLevel.ERROR, e); 443 } 444 throw e; 445 } catch (Exception e) { 446 if (debugEnabled()) { 447 TRACER.debugCaught(DebugLogLevel.ERROR, e); 448 } 449 450 Message message = ERR_ADMIN_EXTENSIONS_CANNOT_LIST_FILES.get( 451 String.valueOf(extensionsPath), stackTraceToSingleLineString(e)); 452 throw new InitializationException(message, e); 453 } 454 } 455 456 457 458 /** 459 * Make sure all core configuration definitions are loaded. 460 * 461 * @throws InitializationException 462 * If the core manifest file could not be read or if one 463 * of the configuration definition classes could not be 464 * initialized. 465 */ 466 private void initializeCoreComponents() 467 throws InitializationException { 468 InputStream is = RootCfgDefn.class.getResourceAsStream("/admin/" 469 + CORE_MANIFEST); 470 471 if (is == null) { 472 Message message = ERR_ADMIN_CANNOT_FIND_CORE_MANIFEST.get(CORE_MANIFEST); 473 throw new InitializationException(message); 474 } 475 476 try { 477 loadDefinitionClasses(is); 478 } catch (InitializationException e) { 479 if (debugEnabled()) { 480 TRACER.debugCaught(DebugLogLevel.ERROR, e); 481 } 482 483 Message message = ERR_CLASS_LOADER_CANNOT_LOAD_CORE.get(CORE_MANIFEST, 484 stackTraceToSingleLineString(e)); 485 throw new InitializationException(message); 486 } 487 } 488 489 490 491 /** 492 * Make sure all the configuration definition classes in a extension 493 * are loaded. 494 * 495 * @param jarFile 496 * The extension's Jar file. 497 * @throws InitializationException 498 * If the extension jar file could not be accessed or if 499 * one of the configuration definition classes could not 500 * be initialized. 501 */ 502 private void initializeExtension(JarFile jarFile) 503 throws InitializationException { 504 JarEntry entry = jarFile.getJarEntry("admin/" 505 + EXTENSION_MANIFEST); 506 if (entry != null) { 507 InputStream is; 508 try { 509 is = jarFile.getInputStream(entry); 510 } catch (Exception e) { 511 if (debugEnabled()) { 512 TRACER.debugCaught(DebugLogLevel.ERROR, e); 513 } 514 515 Message message = ERR_ADMIN_CANNOT_READ_EXTENSION_MANIFEST.get( 516 EXTENSION_MANIFEST, jarFile.getName(), 517 stackTraceToSingleLineString(e)); 518 throw new InitializationException(message); 519 } 520 521 try { 522 loadDefinitionClasses(is); 523 } catch (InitializationException e) { 524 if (debugEnabled()) { 525 TRACER.debugCaught(DebugLogLevel.ERROR, e); 526 } 527 528 Message message = ERR_CLASS_LOADER_CANNOT_LOAD_EXTENSION.get(jarFile 529 .getName(), EXTENSION_MANIFEST, stackTraceToSingleLineString(e)); 530 throw new InitializationException(message); 531 } 532 } 533 } 534 535 536 537 /** 538 * Forcefully load configuration definition classes named in a 539 * manifest file. 540 * 541 * @param is 542 * The manifest file input stream. 543 * @throws InitializationException 544 * If the definition classes could not be loaded and 545 * initialized. 546 */ 547 private void loadDefinitionClasses(InputStream is) 548 throws InitializationException { 549 BufferedReader reader = new BufferedReader(new InputStreamReader( 550 is)); 551 List<AbstractManagedObjectDefinition<?, ?>> definitions = 552 new LinkedList<AbstractManagedObjectDefinition<?,?>>(); 553 while (true) { 554 String className; 555 try { 556 className = reader.readLine(); 557 } catch (IOException e) { 558 Message msg = ERR_CLASS_LOADER_CANNOT_READ_MANIFEST_FILE.get(String 559 .valueOf(e.getMessage())); 560 throw new InitializationException(msg, e); 561 } 562 563 // Break out when the end of the manifest is reached. 564 if (className == null) { 565 break; 566 } 567 568 // Skip blank lines. 569 className = className.trim(); 570 if (className.length() == 0) { 571 continue; 572 } 573 574 // Skip lines beginning with #. 575 if (className.startsWith("#")) { 576 continue; 577 } 578 579 TRACER.debugMessage(DebugLogLevel.INFO, "Loading class " + className); 580 581 // Load the class and get an instance of it if it is a definition. 582 Class<?> theClass; 583 try { 584 theClass = Class.forName(className, true, loader); 585 } catch (Exception e) { 586 Message msg = ERR_CLASS_LOADER_CANNOT_LOAD_CLASS.get(className, String 587 .valueOf(e.getMessage())); 588 throw new InitializationException(msg, e); 589 } 590 if (AbstractManagedObjectDefinition.class.isAssignableFrom(theClass)) { 591 // We need to instantiate it using its getInstance() static method. 592 Method method; 593 try { 594 method = theClass.getMethod("getInstance"); 595 } catch (Exception e) { 596 Message msg = ERR_CLASS_LOADER_CANNOT_FIND_GET_INSTANCE_METHOD.get( 597 className, String.valueOf(e.getMessage())); 598 throw new InitializationException(msg, e); 599 } 600 601 // Get the definition instance. 602 AbstractManagedObjectDefinition<?, ?> d; 603 try { 604 d = (AbstractManagedObjectDefinition<?, ?>) method.invoke(null); 605 } catch (Exception e) { 606 Message msg = ERR_CLASS_LOADER_CANNOT_INVOKE_GET_INSTANCE_METHOD.get( 607 className, String.valueOf(e.getMessage())); 608 throw new InitializationException(msg, e); 609 } 610 definitions.add(d); 611 } 612 } 613 614 // Initialize any definitions that were loaded. 615 for (AbstractManagedObjectDefinition<?, ?> d : definitions) { 616 try { 617 d.initialize(); 618 } catch (Exception e) { 619 Message msg = ERR_CLASS_LOADER_CANNOT_INITIALIZE_DEFN.get(d.getName(), 620 d.getClass().getName(), String.valueOf(e.getMessage())); 621 throw new InitializationException(msg, e); 622 } 623 } 624 } 625 626 627 628 /** 629 * Load the named Jar file. 630 * 631 * @param jar 632 * The name of the Jar file to load. 633 * @return Returns the loaded Jar file. 634 * @throws InitializationException 635 * If the Jar file could not be loaded. 636 */ 637 private JarFile loadJarFile(File jar) 638 throws InitializationException { 639 JarFile jarFile; 640 641 try { 642 // Load the extension jar file. 643 jarFile = new JarFile(jar); 644 } catch (Exception e) { 645 if (debugEnabled()) { 646 TRACER.debugCaught(DebugLogLevel.ERROR, e); 647 } 648 649 Message message = ERR_ADMIN_CANNOT_OPEN_JAR_FILE.get( 650 jar.getName(), jar.getParent(), stackTraceToSingleLineString(e)); 651 throw new InitializationException(message); 652 } 653 return jarFile; 654 } 655 656 }