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