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