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}