001/* 002 * Copyright (c) 2006-2015 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Nuxeo - initial API and implementation 011 * 012 */ 013 014package org.nuxeo.runtime.api; 015 016import java.io.File; 017import java.net.MalformedURLException; 018import java.net.URL; 019import java.util.List; 020import java.util.Properties; 021 022import javax.security.auth.callback.CallbackHandler; 023import javax.security.auth.login.LoginContext; 024import javax.security.auth.login.LoginException; 025 026import org.apache.commons.io.FileDeleteStrategy; 027import org.apache.commons.lang.StringUtils; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.nuxeo.common.Environment; 031import org.nuxeo.common.collections.ListenerList; 032import org.nuxeo.runtime.RuntimeService; 033import org.nuxeo.runtime.RuntimeServiceEvent; 034import org.nuxeo.runtime.RuntimeServiceException; 035import org.nuxeo.runtime.RuntimeServiceListener; 036import org.nuxeo.runtime.api.login.LoginAs; 037import org.nuxeo.runtime.api.login.LoginService; 038import org.nuxeo.runtime.trackers.files.FileEvent; 039import org.nuxeo.runtime.trackers.files.FileEventTracker; 040 041/** 042 * This class is the main entry point to a Nuxeo runtime application. 043 * <p> 044 * It offers an easy way to create new sessions, to access system services and other resources. 045 * <p> 046 * There are two type of services: 047 * <ul> 048 * <li>Global Services - these services are uniquely defined by a service class, and there is an unique instance of the 049 * service in the system per class. 050 * <li>Local Services - these services are defined by a class and an URI. This type of service allows multiple service 051 * instances for the same class of services. Each instance is uniquely defined in the system by an URI. 052 * </ul> 053 * 054 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a> 055 */ 056public final class Framework { 057 058 private static final Log log = LogFactory.getLog(Framework.class); 059 060 private static Boolean testModeSet; 061 062 /** 063 * Global dev property 064 * 065 * @since 5.6 066 * @see #isDevModeSet() 067 */ 068 public static final String NUXEO_DEV_SYSTEM_PROP = "org.nuxeo.dev"; 069 070 /** 071 * Global testing property 072 * 073 * @since 5.6 074 * @see #isTestModeSet() 075 */ 076 public static final String NUXEO_TESTING_SYSTEM_PROP = "org.nuxeo.runtime.testing"; 077 078 /** 079 * Property to control strict runtime mode 080 * 081 * @since 5.6 082 * @see #handleDevError(Throwable) 083 */ 084 public static final String NUXEO_STRICT_RUNTIME_SYSTEM_PROP = "org.nuxeo.runtime.strict"; 085 086 /** 087 * The runtime instance. 088 */ 089 private static RuntimeService runtime; 090 091 private static final ListenerList listeners = new ListenerList(); 092 093 /** 094 * A class loader used to share resources between all bundles. 095 * <p> 096 * This is useful to put resources outside any bundle (in a directory on the file system) and then refer them from 097 * XML contributions. 098 * <p> 099 * The resource directory used by this loader is ${nuxeo_data_dir}/resources whee ${nuxeo_data_dir} is usually 100 * ${nuxeo_home}/data 101 */ 102 protected static SharedResourceLoader resourceLoader; 103 104 /** 105 * Whether or not services should be exported as OSGI services. This is controlled by the ${ecr.osgi.services} 106 * property. The default is false. 107 */ 108 protected static Boolean isOSGiServiceSupported; 109 110 // Utility class. 111 private Framework() { 112 } 113 114 public static void initialize(RuntimeService runtimeService) { 115 if (runtime != null) { 116 throw new RuntimeServiceException("Nuxeo Framework was already initialized"); 117 } 118 runtime = runtimeService; 119 reloadResourceLoader(); 120 runtime.start(); 121 } 122 123 public static void reloadResourceLoader() { 124 File rs = new File(Environment.getDefault().getData(), "resources"); 125 rs.mkdirs(); 126 URL url; 127 try { 128 url = rs.toURI().toURL(); 129 } catch (MalformedURLException e) { 130 throw new RuntimeServiceException(e); 131 } 132 resourceLoader = new SharedResourceLoader(new URL[] { url }, Framework.class.getClassLoader()); 133 } 134 135 /** 136 * Reload the resources loader, keeping URLs already tracked, and adding possibility to add or remove some URLs. 137 * <p> 138 * Useful for hot reload of jars. 139 * 140 * @since 5.6 141 */ 142 public static void reloadResourceLoader(List<URL> urlsToAdd, List<URL> urlsToRemove) { 143 File rs = new File(Environment.getDefault().getData(), "resources"); 144 rs.mkdirs(); 145 URL[] existing = null; 146 if (resourceLoader != null) { 147 existing = resourceLoader.getURLs(); 148 } 149 // reinit 150 URL url; 151 try { 152 url = rs.toURI().toURL(); 153 } catch (MalformedURLException e) { 154 throw new RuntimeException(e); 155 } 156 resourceLoader = new SharedResourceLoader(new URL[] { url }, Framework.class.getClassLoader()); 157 // add back existing urls unless they should be removed, and add new 158 // urls 159 if (existing != null) { 160 for (URL oldURL : existing) { 161 if (urlsToRemove == null || !urlsToRemove.contains(oldURL)) { 162 resourceLoader.addURL(oldURL); 163 } 164 } 165 } 166 if (urlsToAdd != null) { 167 for (URL newURL : urlsToAdd) { 168 resourceLoader.addURL(newURL); 169 } 170 } 171 } 172 173 public static void shutdown() { 174 if (runtime == null) { 175 throw new IllegalStateException("runtime not exist"); 176 } 177 try { 178 runtime.stop(); 179 } finally { 180 runtime = null; 181 } 182 } 183 184 /** 185 * Tests whether or not the runtime was initialized. 186 * 187 * @return true if the runtime was initialized, false otherwise 188 */ 189 public static synchronized boolean isInitialized() { 190 return runtime != null; 191 } 192 193 public static SharedResourceLoader getResourceLoader() { 194 return resourceLoader; 195 } 196 197 /** 198 * Gets the runtime service instance. 199 * 200 * @return the runtime service instance 201 */ 202 public static RuntimeService getRuntime() { 203 return runtime; 204 } 205 206 /** 207 * Gets a service given its class. 208 */ 209 public static <T> T getService(Class<T> serviceClass) { 210 ServiceProvider provider = DefaultServiceProvider.getProvider(); 211 if (provider != null) { 212 return provider.getService(serviceClass); 213 } 214 checkRuntimeInitialized(); 215 // TODO impl a runtime service provider 216 return runtime.getService(serviceClass); 217 } 218 219 /** 220 * Gets a service given its class. 221 */ 222 public static <T> T getLocalService(Class<T> serviceClass) { 223 return getService(serviceClass); 224 } 225 226 /** 227 * Lookup a registered object given its key. 228 */ 229 public static Object lookup(String key) { 230 return null; // TODO 231 } 232 233 /** 234 * Login in the system as the system user (a pseudo-user having all privileges). 235 * 236 * @return the login session if successful. Never returns null. 237 * @throws LoginException on login failure 238 */ 239 public static LoginContext login() throws LoginException { 240 checkRuntimeInitialized(); 241 LoginService loginService = runtime.getService(LoginService.class); 242 if (loginService != null) { 243 return loginService.login(); 244 } 245 return null; 246 } 247 248 /** 249 * Login in the system as the system user (a pseudo-user having all privileges). The given username will be used to 250 * identify the user id that called this method. 251 * 252 * @param username the originating user id 253 * @return the login session if successful. Never returns null. 254 * @throws LoginException on login failure 255 */ 256 public static LoginContext loginAs(String username) throws LoginException { 257 checkRuntimeInitialized(); 258 LoginService loginService = runtime.getService(LoginService.class); 259 if (loginService != null) { 260 return loginService.loginAs(username); 261 } 262 return null; 263 } 264 265 /** 266 * Login in the system as the given user without checking the password. 267 * 268 * @param username the user name to login as. 269 * @return the login context 270 * @throws LoginException if any error occurs 271 * @since 5.4.2 272 */ 273 public static LoginContext loginAsUser(String username) throws LoginException { 274 return getLocalService(LoginAs.class).loginAs(username); 275 } 276 277 /** 278 * Login in the system as the given user using the given password. 279 * 280 * @param username the username to login 281 * @param password the password 282 * @return a login session if login was successful. Never returns null. 283 * @throws LoginException if login failed 284 */ 285 public static LoginContext login(String username, Object password) throws LoginException { 286 checkRuntimeInitialized(); 287 LoginService loginService = runtime.getService(LoginService.class); 288 if (loginService != null) { 289 return loginService.login(username, password); 290 } 291 return null; 292 } 293 294 /** 295 * Login in the system using the given callback handler for login info resolution. 296 * 297 * @param cbHandler used to fetch the login info 298 * @return the login context 299 * @throws LoginException 300 */ 301 public static LoginContext login(CallbackHandler cbHandler) throws LoginException { 302 checkRuntimeInitialized(); 303 LoginService loginService = runtime.getService(LoginService.class); 304 if (loginService != null) { 305 return loginService.login(cbHandler); 306 } 307 return null; 308 } 309 310 public static void sendEvent(RuntimeServiceEvent event) { 311 Object[] listenersArray = listeners.getListeners(); 312 for (Object listener : listenersArray) { 313 ((RuntimeServiceListener) listener).handleEvent(event); 314 } 315 } 316 317 /** 318 * Registers a listener to be notified about runtime events. 319 * <p> 320 * If the listener is already registered, do nothing. 321 * 322 * @param listener the listener to register 323 */ 324 public static void addListener(RuntimeServiceListener listener) { 325 listeners.add(listener); 326 } 327 328 /** 329 * Removes the given listener. 330 * <p> 331 * If the listener is not registered, do nothing. 332 * 333 * @param listener the listener to remove 334 */ 335 public static void removeListener(RuntimeServiceListener listener) { 336 listeners.remove(listener); 337 } 338 339 /** 340 * Gets the given property value if any, otherwise null. 341 * <p> 342 * The framework properties will be searched first then if any matching property is found the system properties are 343 * searched too. 344 * 345 * @param key the property key 346 * @return the property value if any or null otherwise 347 */ 348 public static String getProperty(String key) { 349 return getProperty(key, null); 350 } 351 352 /** 353 * Gets the given property value if any, otherwise returns the given default value. 354 * <p> 355 * The framework properties will be searched first then if any matching property is found the system properties are 356 * searched too. 357 * 358 * @param key the property key 359 * @param defValue the default value to use 360 * @return the property value if any otherwise the default value 361 */ 362 public static String getProperty(String key, String defValue) { 363 checkRuntimeInitialized(); 364 return runtime.getProperty(key, defValue); 365 } 366 367 /** 368 * Gets all the framework properties. The system properties are not included in the returned map. 369 * 370 * @return the framework properties map. Never returns null. 371 */ 372 public static Properties getProperties() { 373 checkRuntimeInitialized(); 374 return runtime.getProperties(); 375 } 376 377 /** 378 * Expands any variable found in the given expression with the value of the corresponding framework property. 379 * <p> 380 * The variable format is ${property_key}. 381 * <p> 382 * System properties are also expanded. 383 */ 384 public static String expandVars(String expression) { 385 checkRuntimeInitialized(); 386 return runtime.expandVars(expression); 387 } 388 389 public static boolean isOSGiServiceSupported() { 390 if (isOSGiServiceSupported == null) { 391 isOSGiServiceSupported = Boolean.valueOf(isBooleanPropertyTrue("ecr.osgi.services")); 392 } 393 return isOSGiServiceSupported.booleanValue(); 394 } 395 396 /** 397 * Returns true if dev mode is set. 398 * <p> 399 * Activating this mode, some of the code may not behave as it would in production, to ease up debugging and working 400 * on developing the application. 401 * <p> 402 * For instance, it'll enable hot-reload if some packages are installed while the framework is running. It will also 403 * reset some caches when that happens. 404 * <p> 405 * Before 5.6, when activating this mode, the Runtime Framework stopped on low-level errors, see 406 * {@link #handleDevError(Throwable)} but this behaviour has been removed. 407 */ 408 public static boolean isDevModeSet() { 409 return isBooleanPropertyTrue(NUXEO_DEV_SYSTEM_PROP); 410 } 411 412 /** 413 * Returns true if test mode is set. 414 * <p> 415 * Activating this mode, some of the code may not behave as it would in production, to ease up testing. 416 */ 417 public static boolean isTestModeSet() { 418 if (testModeSet == null) { 419 testModeSet = isBooleanPropertyTrue(NUXEO_TESTING_SYSTEM_PROP); 420 } 421 return testModeSet; 422 } 423 424 /** 425 * Returns true if given property is false when compared to a boolean value. Returns false if given property in 426 * unset. 427 * <p> 428 * Checks for the system properties if property is not found in the runtime properties. 429 * 430 * @since 5.8 431 */ 432 public static boolean isBooleanPropertyFalse(String propName) { 433 String v = getProperty(propName); 434 if (v == null) { 435 v = System.getProperty(propName); 436 } 437 if (StringUtils.isBlank(v)) { 438 return false; 439 } 440 return !Boolean.parseBoolean(v); 441 } 442 443 /** 444 * Returns true if given property is true when compared to a boolean value. 445 * <p> 446 * Checks for the system properties if property is not found in the runtime properties. 447 * 448 * @since 5.6 449 */ 450 public static boolean isBooleanPropertyTrue(String propName) { 451 String v = getProperty(propName); 452 if (v == null) { 453 v = System.getProperty(propName); 454 } 455 return Boolean.parseBoolean(v); 456 } 457 458 /** 459 * Since 5.6, this method stops the application if property {@link #NUXEO_STRICT_RUNTIME_SYSTEM_PROP} is set to 460 * true, and one of the following errors occurred during startup. 461 * <ul> 462 * <li>Component XML parse error. 463 * <li>Contribution to an unknown extension point. 464 * <li>Component with an unknown implementation class (the implementation entry exists in the XML descriptor but 465 * cannot be resolved to a class). 466 * <li>Uncatched exception on extension registration / unregistration (either in framework or user component code) 467 * <li>Uncatched exception on component activation / deactivation (either in framework or user component code) 468 * <li>Broken Nuxeo-Component MANIFEST entry. (i.e. the entry cannot be resolved to a resource) 469 * </ul> 470 * <p> 471 * Before 5.6, this method stopped the application if development mode was enabled (i.e. org.nuxeo.dev system 472 * property is set) but this is not the case anymore to handle a dev mode that does not stop the runtime framework 473 * when using hot reload. 474 * 475 * @param t the exception or null if none 476 */ 477 public static void handleDevError(Throwable t) { 478 if (isBooleanPropertyTrue(NUXEO_STRICT_RUNTIME_SYSTEM_PROP)) { 479 System.err.println("Fatal error caught in strict " + "runtime mode => exiting."); 480 if (t != null) { 481 t.printStackTrace(); 482 } 483 System.exit(1); 484 } else if (t != null) { 485 log.error(t, t); 486 } 487 } 488 489 /** 490 * @see FileEventTracker 491 * @param aFile The file to delete 492 * @param aMarker the marker Object 493 */ 494 public static void trackFile(File aFile, Object aMarker) { 495 FileEvent.onFile(Framework.class, aFile, aMarker).send(); 496 } 497 498 /** 499 * Strategy is now always FileDeleteStrategy.FORCE unless you've specified another one 500 * 501 * @deprecated 502 * @since 6.0 503 * @see #trackFile(File, Object) 504 * @param file The file to delete 505 * @param marker the marker Object 506 * @param fileDeleteStrategy add a custom delete strategy 507 */ 508 @Deprecated 509 public static void trackFile(File file, Object marker, FileDeleteStrategy fileDeleteStrategy) { 510 trackFile(file, marker); 511 } 512 513 /** 514 * @since 6.0 515 */ 516 protected static void checkRuntimeInitialized() { 517 if (runtime == null) { 518 throw new IllegalStateException("Runtime not initialized"); 519 } 520 } 521 522 public static void main(String[] args) { 523 } 524 525}