001/* 002 * (C) Copyright 2006-2017 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 */ 019package org.nuxeo.runtime.api; 020 021import java.io.File; 022import java.io.IOException; 023import java.net.MalformedURLException; 024import java.net.URL; 025import java.nio.file.Files; 026import java.nio.file.Path; 027import java.nio.file.attribute.FileAttribute; 028import java.util.List; 029import java.util.Properties; 030import java.util.function.Supplier; 031 032import javax.security.auth.callback.CallbackHandler; 033import javax.security.auth.login.LoginContext; 034import javax.security.auth.login.LoginException; 035 036import org.apache.commons.io.FileDeleteStrategy; 037import org.apache.commons.lang.StringUtils; 038import org.apache.commons.logging.Log; 039import org.apache.commons.logging.LogFactory; 040import org.nuxeo.common.Environment; 041import org.nuxeo.common.collections.ListenerList; 042import org.nuxeo.runtime.RuntimeService; 043import org.nuxeo.runtime.RuntimeServiceEvent; 044import org.nuxeo.runtime.RuntimeServiceException; 045import org.nuxeo.runtime.RuntimeServiceListener; 046import org.nuxeo.runtime.api.login.LoginAs; 047import org.nuxeo.runtime.api.login.LoginService; 048import org.nuxeo.runtime.trackers.files.FileEvent; 049import org.nuxeo.runtime.trackers.files.FileEventTracker; 050 051/** 052 * This class is the main entry point to a Nuxeo runtime application. 053 * <p> 054 * It offers an easy way to create new sessions, to access system services and other resources. 055 * <p> 056 * There are two type of services: 057 * <ul> 058 * <li>Global Services - these services are uniquely defined by a service class, and there is an unique instance of the 059 * service in the system per class. 060 * <li>Local Services - these services are defined by a class and an URI. This type of service allows multiple service 061 * instances for the same class of services. Each instance is uniquely defined in the system by an URI. 062 * </ul> 063 * 064 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 065 */ 066public final class Framework { 067 068 private static final Log log = LogFactory.getLog(Framework.class); 069 070 private static Boolean testModeSet; 071 072 /** 073 * Global dev property 074 * 075 * @since 5.6 076 * @see #isDevModeSet() 077 */ 078 public static final String NUXEO_DEV_SYSTEM_PROP = "org.nuxeo.dev"; 079 080 /** 081 * Global testing property 082 * 083 * @since 5.6 084 * @see #isTestModeSet() 085 */ 086 public static final String NUXEO_TESTING_SYSTEM_PROP = "org.nuxeo.runtime.testing"; 087 088 /** 089 * Property to control strict runtime mode 090 * 091 * @since 5.6 092 * @see #handleDevError(Throwable) 093 * @deprecated since 9.1 This property is not documented and doesn't work. 094 */ 095 @Deprecated 096 public static final String NUXEO_STRICT_RUNTIME_SYSTEM_PROP = "org.nuxeo.runtime.strict"; 097 098 /** 099 * The runtime instance. 100 */ 101 private static RuntimeService runtime; 102 103 private static final ListenerList listeners = new ListenerList(); 104 105 /** 106 * A class loader used to share resources between all bundles. 107 * <p> 108 * This is useful to put resources outside any bundle (in a directory on the file system) and then refer them from 109 * XML contributions. 110 * <p> 111 * The resource directory used by this loader is ${nuxeo_data_dir}/resources whee ${nuxeo_data_dir} is usually 112 * ${nuxeo_home}/data 113 */ 114 protected static SharedResourceLoader resourceLoader; 115 116 /** 117 * Whether or not services should be exported as OSGI services. This is controlled by the ${ecr.osgi.services} 118 * property. The default is false. 119 */ 120 protected static Boolean isOSGiServiceSupported; 121 122 // Utility class. 123 private Framework() { 124 } 125 126 public static void initialize(RuntimeService runtimeService) { 127 if (runtime != null) { 128 throw new RuntimeServiceException("Nuxeo Framework was already initialized"); 129 } 130 runtime = runtimeService; 131 reloadResourceLoader(); 132 runtime.start(); 133 } 134 135 public static void reloadResourceLoader() { 136 File rs = new File(Environment.getDefault().getData(), "resources"); 137 rs.mkdirs(); 138 URL url; 139 try { 140 url = rs.toURI().toURL(); 141 } catch (MalformedURLException e) { 142 throw new RuntimeServiceException(e); 143 } 144 resourceLoader = new SharedResourceLoader(new URL[] { url }, Framework.class.getClassLoader()); 145 } 146 147 /** 148 * Reload the resources loader, keeping URLs already tracked, and adding possibility to add or remove some URLs. 149 * <p> 150 * Useful for hot reload of jars. 151 * 152 * @since 5.6 153 */ 154 public static void reloadResourceLoader(List<URL> urlsToAdd, List<URL> urlsToRemove) { 155 File rs = new File(Environment.getDefault().getData(), "resources"); 156 rs.mkdirs(); 157 URL[] existing = null; 158 if (resourceLoader != null) { 159 existing = resourceLoader.getURLs(); 160 } 161 // reinit 162 URL url; 163 try { 164 url = rs.toURI().toURL(); 165 } catch (MalformedURLException e) { 166 throw new RuntimeException(e); 167 } 168 resourceLoader = new SharedResourceLoader(new URL[] { url }, Framework.class.getClassLoader()); 169 // add back existing urls unless they should be removed, and add new 170 // urls 171 if (existing != null) { 172 for (URL oldURL : existing) { 173 if (urlsToRemove == null || !urlsToRemove.contains(oldURL)) { 174 resourceLoader.addURL(oldURL); 175 } 176 } 177 } 178 if (urlsToAdd != null) { 179 for (URL newURL : urlsToAdd) { 180 resourceLoader.addURL(newURL); 181 } 182 } 183 } 184 185 public static void shutdown() { 186 if (runtime == null) { 187 throw new IllegalStateException("runtime not exist"); 188 } 189 try { 190 runtime.stop(); 191 } finally { 192 runtime = null; 193 } 194 } 195 196 /** 197 * Tests whether or not the runtime was initialized. 198 * 199 * @return true if the runtime was initialized, false otherwise 200 */ 201 public static synchronized boolean isInitialized() { 202 return runtime != null; 203 } 204 205 public static SharedResourceLoader getResourceLoader() { 206 return resourceLoader; 207 } 208 209 /** 210 * Gets the runtime service instance. 211 * 212 * @return the runtime service instance 213 */ 214 public static RuntimeService getRuntime() { 215 return runtime; 216 } 217 218 /** 219 * Gets a service given its class. 220 */ 221 public static <T> T getService(Class<T> serviceClass) { 222 ServiceProvider provider = DefaultServiceProvider.getProvider(); 223 if (provider != null) { 224 return provider.getService(serviceClass); 225 } 226 checkRuntimeInitialized(); 227 // TODO impl a runtime service provider 228 return runtime.getService(serviceClass); 229 } 230 231 /** 232 * Gets a service given its class. 233 */ 234 public static <T> T getLocalService(Class<T> serviceClass) { 235 return getService(serviceClass); 236 } 237 238 /** 239 * Lookup a registered object given its key. 240 */ 241 public static Object lookup(String key) { 242 return null; // TODO 243 } 244 245 /** 246 * Runs the given {@link Runnable} while logged in as a system user. 247 * 248 * @param runnable what to run 249 * @since 8.4 250 */ 251 public static void doPrivileged(Runnable runnable) { 252 try { 253 LoginContext loginContext = login(); 254 try { 255 runnable.run(); 256 } finally { 257 if (loginContext != null) { // may be null in tests 258 loginContext.logout(); 259 } 260 } 261 } catch (LoginException e) { 262 throw new RuntimeException(e); 263 } 264 } 265 266 /** 267 * Calls the given {@link Supplier} while logged in as a system user and returns its result. 268 * 269 * @param supplier what to call 270 * @return the supplier's result 271 * @since 8.4 272 */ 273 public static <T> T doPrivileged(Supplier<T> supplier) { 274 try { 275 LoginContext loginContext = login(); 276 try { 277 return supplier.get(); 278 } finally { 279 if (loginContext != null) { // may be null in tests 280 loginContext.logout(); 281 } 282 } 283 } catch (LoginException e) { 284 throw new RuntimeException(e); 285 } 286 } 287 288 /** 289 * Login in the system as the system user (a pseudo-user having all privileges). 290 * 291 * @return the login session if successful. Never returns null. 292 * @throws LoginException on login failure 293 */ 294 public static LoginContext login() throws LoginException { 295 checkRuntimeInitialized(); 296 LoginService loginService = runtime.getService(LoginService.class); 297 if (loginService != null) { 298 return loginService.login(); 299 } 300 return null; 301 } 302 303 /** 304 * Login in the system as the system user (a pseudo-user having all privileges). The given username will be used to 305 * identify the user id that called this method. 306 * 307 * @param username the originating user id 308 * @return the login session if successful. Never returns null. 309 * @throws LoginException on login failure 310 */ 311 public static LoginContext loginAs(String username) throws LoginException { 312 checkRuntimeInitialized(); 313 LoginService loginService = runtime.getService(LoginService.class); 314 if (loginService != null) { 315 return loginService.loginAs(username); 316 } 317 return null; 318 } 319 320 /** 321 * Login in the system as the given user without checking the password. 322 * 323 * @param username the user name to login as. 324 * @return the login context 325 * @throws LoginException if any error occurs 326 * @since 5.4.2 327 */ 328 public static LoginContext loginAsUser(String username) throws LoginException { 329 return getLocalService(LoginAs.class).loginAs(username); 330 } 331 332 /** 333 * Login in the system as the given user using the given password. 334 * 335 * @param username the username to login 336 * @param password the password 337 * @return a login session if login was successful. Never returns null. 338 * @throws LoginException if login failed 339 */ 340 public static LoginContext login(String username, Object password) throws LoginException { 341 checkRuntimeInitialized(); 342 LoginService loginService = runtime.getService(LoginService.class); 343 if (loginService != null) { 344 return loginService.login(username, password); 345 } 346 return null; 347 } 348 349 /** 350 * Login in the system using the given callback handler for login info resolution. 351 * 352 * @param cbHandler used to fetch the login info 353 * @return the login context 354 * @throws LoginException 355 */ 356 public static LoginContext login(CallbackHandler cbHandler) throws LoginException { 357 checkRuntimeInitialized(); 358 LoginService loginService = runtime.getService(LoginService.class); 359 if (loginService != null) { 360 return loginService.login(cbHandler); 361 } 362 return null; 363 } 364 365 public static void sendEvent(RuntimeServiceEvent event) { 366 Object[] listenersArray = listeners.getListeners(); 367 for (Object listener : listenersArray) { 368 ((RuntimeServiceListener) listener).handleEvent(event); 369 } 370 } 371 372 /** 373 * Registers a listener to be notified about runtime events. 374 * <p> 375 * If the listener is already registered, do nothing. 376 * 377 * @param listener the listener to register 378 */ 379 public static void addListener(RuntimeServiceListener listener) { 380 listeners.add(listener); 381 } 382 383 /** 384 * Removes the given listener. 385 * <p> 386 * If the listener is not registered, do nothing. 387 * 388 * @param listener the listener to remove 389 */ 390 public static void removeListener(RuntimeServiceListener listener) { 391 listeners.remove(listener); 392 } 393 394 /** 395 * Gets the given property value if any, otherwise null. 396 * <p> 397 * The framework properties will be searched first then if any matching property is found the system properties are 398 * searched too. 399 * 400 * @param key the property key 401 * @return the property value if any or null otherwise 402 */ 403 public static String getProperty(String key) { 404 return getProperty(key, null); 405 } 406 407 /** 408 * Gets the given property value if any, otherwise returns the given default value. 409 * <p> 410 * The framework properties will be searched first then if any matching property is found the system properties are 411 * searched too. 412 * 413 * @param key the property key 414 * @param defValue the default value to use 415 * @return the property value if any otherwise the default value 416 */ 417 public static String getProperty(String key, String defValue) { 418 checkRuntimeInitialized(); 419 return runtime.getProperty(key, defValue); 420 } 421 422 /** 423 * Gets all the framework properties. The system properties are not included in the returned map. 424 * 425 * @return the framework properties map. Never returns null. 426 */ 427 public static Properties getProperties() { 428 checkRuntimeInitialized(); 429 return runtime.getProperties(); 430 } 431 432 /** 433 * Expands any variable found in the given expression with the value of the corresponding framework property. 434 * <p> 435 * The variable format is ${property_key}. 436 * <p> 437 * System properties are also expanded. 438 */ 439 public static String expandVars(String expression) { 440 checkRuntimeInitialized(); 441 return runtime.expandVars(expression); 442 } 443 444 public static boolean isOSGiServiceSupported() { 445 if (isOSGiServiceSupported == null) { 446 isOSGiServiceSupported = Boolean.valueOf(isBooleanPropertyTrue("ecr.osgi.services")); 447 } 448 return isOSGiServiceSupported.booleanValue(); 449 } 450 451 /** 452 * Returns true if dev mode is set. 453 * <p> 454 * Activating this mode, some of the code may not behave as it would in production, to ease up debugging and working 455 * on developing the application. 456 * <p> 457 * For instance, it'll enable hot-reload if some packages are installed while the framework is running. It will also 458 * reset some caches when that happens. 459 */ 460 public static boolean isDevModeSet() { 461 return isBooleanPropertyTrue(NUXEO_DEV_SYSTEM_PROP); 462 } 463 464 /** 465 * Returns true if test mode is set. 466 * <p> 467 * Activating this mode, some of the code may not behave as it would in production, to ease up testing. 468 */ 469 public static boolean isTestModeSet() { 470 if (testModeSet == null) { 471 testModeSet = isBooleanPropertyTrue(NUXEO_TESTING_SYSTEM_PROP); 472 } 473 return testModeSet; 474 } 475 476 /** 477 * Returns true if given property is false when compared to a boolean value. Returns false if given property in 478 * unset. 479 * <p> 480 * Checks for the system properties if property is not found in the runtime properties. 481 * 482 * @since 5.8 483 */ 484 public static boolean isBooleanPropertyFalse(String propName) { 485 String v = getProperty(propName); 486 if (v == null) { 487 v = System.getProperty(propName); 488 } 489 if (StringUtils.isBlank(v)) { 490 return false; 491 } 492 return !Boolean.parseBoolean(v); 493 } 494 495 /** 496 * Returns true if given property is true when compared to a boolean value. 497 * <p> 498 * Checks for the system properties if property is not found in the runtime properties. 499 * 500 * @since 5.6 501 */ 502 public static boolean isBooleanPropertyTrue(String propName) { 503 String v = getProperty(propName); 504 if (v == null) { 505 v = System.getProperty(propName); 506 } 507 return Boolean.parseBoolean(v); 508 } 509 510 /** 511 * Since 5.6, this method stops the application if property {@link #NUXEO_STRICT_RUNTIME_SYSTEM_PROP} is set to 512 * true, and one of the following errors occurred during startup. 513 * <ul> 514 * <li>Component XML parse error. 515 * <li>Contribution to an unknown extension point. 516 * <li>Component with an unknown implementation class (the implementation entry exists in the XML descriptor but 517 * cannot be resolved to a class). 518 * <li>Uncatched exception on extension registration / unregistration (either in framework or user component code) 519 * <li>Uncatched exception on component activation / deactivation (either in framework or user component code) 520 * <li>Broken Nuxeo-Component MANIFEST entry. (i.e. the entry cannot be resolved to a resource) 521 * </ul> 522 * <p> 523 * Before 5.6, this method stopped the application if development mode was enabled (i.e. org.nuxeo.dev system 524 * property is set) but this is not the case anymore to handle a dev mode that does not stop the runtime framework 525 * when using hot reload. 526 * 527 * @param t the exception or null if none 528 * @deprecated since 9.1 DON'T USE THIS METHOD ANYMORE, its behavior is not documented. It also seems to not work. 529 * If you want to stop server startup add messages to {@link RuntimeService#getErrors()}. 530 */ 531 @Deprecated 532 public static void handleDevError(Throwable t) { 533 if (isBooleanPropertyTrue(NUXEO_STRICT_RUNTIME_SYSTEM_PROP)) { 534 System.err.println("Fatal error caught in strict " + "runtime mode => exiting."); 535 if (t != null) { 536 t.printStackTrace(); 537 } 538 System.exit(1); 539 } else if (t != null) { 540 log.error(t, t); 541 } 542 } 543 544 /** 545 * @see FileEventTracker 546 * @param aFile The file to delete 547 * @param aMarker the marker Object 548 */ 549 public static void trackFile(File aFile, Object aMarker) { 550 FileEvent.onFile(Framework.class, aFile, aMarker).send(); 551 } 552 553 /** 554 * Strategy is not customizable anymore. 555 * 556 * @deprecated 557 * @since 6.0 558 * @see #trackFile(File, Object) 559 * @see org.nuxeo.runtime.trackers.files.FileEventTracker.SafeFileDeleteStrategy 560 * @param file The file to delete 561 * @param marker the marker Object 562 * @param fileDeleteStrategy ignored deprecated parameter 563 */ 564 @Deprecated 565 public static void trackFile(File file, Object marker, FileDeleteStrategy fileDeleteStrategy) { 566 trackFile(file, marker); 567 } 568 569 /** 570 * @since 6.0 571 */ 572 protected static void checkRuntimeInitialized() { 573 if (runtime == null) { 574 throw new IllegalStateException("Runtime not initialized"); 575 } 576 } 577 578 /** 579 * Creates an empty file in the framework temporary-file directory ({@code nuxeo.tmp.dir} vs {@code java.io.tmpdir} 580 * ), using the given prefix and suffix to generate its name. 581 * <p> 582 * Invoking this method is equivalent to invoking 583 * <code>{@link File#createTempFile(java.lang.String, java.lang.String, java.io.File) 584 * File.createTempFile(prefix, suffix, Environment.getDefault().getTemp())}</code>. 585 * <p> 586 * The {@link #createTempFilePath(String, String, FileAttribute...)} method provides an alternative method to create 587 * an empty file in the framework temporary-file directory. Files created by that method may have more restrictive 588 * access permissions to files created by this method and so may be more suited to security-sensitive applications. 589 * 590 * @param prefix The prefix string to be used in generating the file's name; must be at least three characters long 591 * @param suffix The suffix string to be used in generating the file's name; may be <code>null</code>, in which case 592 * the suffix <code>".tmp"</code> will be used 593 * @return An abstract pathname denoting a newly-created empty file 594 * @throws IllegalArgumentException If the <code>prefix</code> argument contains fewer than three characters 595 * @throws IOException If a file could not be created 596 * @throws SecurityException If a security manager exists and its <code> 597 * {@link java.lang.SecurityManager#checkWrite(java.lang.String)}</code> method does not allow a file to 598 * be created 599 * @since 8.1 600 * @see File#createTempFile(String, String, File) 601 * @see Environment#getTemp() 602 * @see #createTempFilePath(String, String, FileAttribute...) 603 * @see #createTempDirectory(String, FileAttribute...) 604 */ 605 public static File createTempFile(String prefix, String suffix) throws IOException { 606 try { 607 return File.createTempFile(prefix, suffix, getTempDir()); 608 } catch (IOException e) { 609 throw new IOException("Could not create temp file in " + getTempDir(), e); 610 } 611 } 612 613 /** 614 * @return the Nuxeo temp dir returned by {@link Environment#getTemp()}. If the Environment fails to initialize, 615 * then returns the File denoted by {@code "nuxeo.tmp.dir"} System property, or {@code "java.io.tmpdir"}. 616 * @since 8.1 617 */ 618 private static File getTempDir() { 619 Environment env = Environment.getDefault(); 620 File temp = env != null ? env.getTemp() 621 : new File(System.getProperty("nuxeo.tmp.dir", System.getProperty("java.io.tmpdir"))); 622 temp.mkdirs(); 623 return temp; 624 } 625 626 /** 627 * Creates an empty file in the framework temporary-file directory ({@code nuxeo.tmp.dir} vs {@code java.io.tmpdir} 628 * ), using the given prefix and suffix to generate its name. The resulting {@code Path} is associated with the 629 * default {@code FileSystem}. 630 * <p> 631 * Invoking this method is equivalent to invoking 632 * {@link Files#createTempFile(Path, String, String, FileAttribute...) 633 * Files.createTempFile(Environment.getDefault().getTemp().toPath(), prefix, suffix, attrs)}. 634 * 635 * @param prefix the prefix string to be used in generating the file's name; may be {@code null} 636 * @param suffix the suffix string to be used in generating the file's name; may be {@code null}, in which case " 637 * {@code .tmp}" is used 638 * @param attrs an optional list of file attributes to set atomically when creating the file 639 * @return the path to the newly created file that did not exist before this method was invoked 640 * @throws IllegalArgumentException if the prefix or suffix parameters cannot be used to generate a candidate file 641 * name 642 * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically when 643 * creating the directory 644 * @throws IOException if an I/O error occurs or the temporary-file directory does not exist 645 * @throws SecurityException In the case of the default provider, and a security manager is installed, the 646 * {@link SecurityManager#checkWrite(String) checkWrite} method is invoked to check write access to the 647 * file. 648 * @since 8.1 649 * @see Files#createTempFile(Path, String, String, FileAttribute...) 650 * @see Environment#getTemp() 651 * @see #createTempFile(String, String) 652 */ 653 public static Path createTempFilePath(String prefix, String suffix, FileAttribute<?>... attrs) throws IOException { 654 try { 655 return Files.createTempFile(getTempDir().toPath(), prefix, suffix, attrs); 656 } catch (IOException e) { 657 throw new IOException("Could not create temp file in " + getTempDir(), e); 658 } 659 } 660 661 /** 662 * Creates a new directory in the framework temporary-file directory ({@code nuxeo.tmp.dir} vs 663 * {@code java.io.tmpdir}), using the given prefix to generate its name. The resulting {@code Path} is associated 664 * with the default {@code FileSystem}. 665 * <p> 666 * Invoking this method is equivalent to invoking {@link Files#createTempDirectory(Path, String, FileAttribute...) 667 * Files.createTempDirectory(Environment.getDefault().getTemp().toPath(), prefix, suffix, attrs)}. 668 * 669 * @param prefix the prefix string to be used in generating the directory's name; may be {@code null} 670 * @param attrs an optional list of file attributes to set atomically when creating the directory 671 * @return the path to the newly created directory that did not exist before this method was invoked 672 * @throws IllegalArgumentException if the prefix cannot be used to generate a candidate directory name 673 * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically when 674 * creating the directory 675 * @throws IOException if an I/O error occurs or the temporary-file directory does not exist 676 * @throws SecurityException In the case of the default provider, and a security manager is installed, the 677 * {@link SecurityManager#checkWrite(String) checkWrite} method is invoked to check write access when 678 * creating the directory. 679 * @since 8.1 680 * @see Files#createTempDirectory(Path, String, FileAttribute...) 681 * @see Environment#getTemp() 682 * @see #createTempFile(String, String) 683 */ 684 public static Path createTempDirectory(String prefix, FileAttribute<?>... attrs) throws IOException { 685 try { 686 return Files.createTempDirectory(getTempDir().toPath(), prefix, attrs); 687 } catch (IOException e) { 688 throw new IOException("Could not create temp directory in " + getTempDir(), e); 689 } 690 } 691 692}