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