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}