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;
015
016import java.io.File;
017import java.net.URL;
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.List;
022import java.util.Map;
023import java.util.Map.Entry;
024import java.util.Set;
025import java.util.logging.Level;
026
027import org.apache.commons.logging.Log;
028import org.apache.commons.logging.LogFactory;
029
030import org.nuxeo.common.codec.CryptoProperties;
031import org.nuxeo.common.logging.JavaUtilLoggingHelper;
032import org.nuxeo.common.utils.TextTemplate;
033import org.nuxeo.runtime.api.Framework;
034import org.nuxeo.runtime.model.ComponentInstance;
035import org.nuxeo.runtime.model.ComponentManager;
036import org.nuxeo.runtime.model.ComponentName;
037import org.nuxeo.runtime.model.RuntimeContext;
038import org.nuxeo.runtime.model.impl.ComponentManagerImpl;
039import org.nuxeo.runtime.model.impl.DefaultRuntimeContext;
040
041import org.osgi.framework.Bundle;
042
043/**
044 * Abstract implementation of the Runtime Service.
045 * <p>
046 * Implementors are encouraged to extend this class instead of directly implementing the {@link RuntimeService}
047 * interface.
048 *
049 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
050 */
051public abstract class AbstractRuntimeService implements RuntimeService {
052
053    /**
054     * Property that controls whether or not to redirect JUL to JCL. By default is true (JUL will be redirected)
055     */
056    public static final String REDIRECT_JUL = "org.nuxeo.runtime.redirectJUL";
057
058    public static final String REDIRECT_JUL_THRESHOLD = "org.nuxeo.runtime.redirectJUL.threshold";
059
060    private static final Log log = LogFactory.getLog(RuntimeService.class);
061
062    protected boolean isStarted = false;
063
064    protected boolean isShuttingDown = false;
065
066    protected File workingDir;
067
068    protected CryptoProperties properties = new CryptoProperties(System.getProperties());
069
070    protected ComponentManager manager;
071
072    protected final RuntimeContext context;
073
074    protected final List<RuntimeExtension> extensions = new ArrayList<>();
075
076    protected AbstractRuntimeService(DefaultRuntimeContext context) {
077        this(context, null);
078    }
079
080    // warnings during the deployment. Here are collected all errors occurred
081    // during the startup
082    protected final List<String> warnings = new ArrayList<>();
083
084    protected AbstractRuntimeService(DefaultRuntimeContext context, Map<String, String> properties) {
085        this.context = context;
086        context.setRuntime(this);
087        if (properties != null) {
088            this.properties.putAll(properties);
089        }
090        // get errors set by NuxeoDeployer
091        String errs = System.getProperty("org.nuxeo.runtime.deployment.errors");
092        if (errs != null) {
093            warnings.addAll(Arrays.asList(errs.split("\n")));
094            System.clearProperty("org.nuxeo.runtime.deployment.errors");
095        }
096    }
097
098    @Override
099    public List<String> getWarnings() {
100        return warnings;
101    }
102
103    protected ComponentManager createComponentManager() {
104        return new ComponentManagerImpl(this);
105    }
106
107    protected static URL getBuiltinFeatureURL() {
108        return Thread.currentThread().getContextClassLoader().getResource("org/nuxeo/runtime/nx-feature.xml");
109    }
110
111    @Override
112    public synchronized void start() {
113        if (!isStarted) {
114            if (Boolean.parseBoolean(getProperty(REDIRECT_JUL, "false"))) {
115                Level threshold = Level.parse(getProperty(REDIRECT_JUL_THRESHOLD, "INFO").toUpperCase());
116                JavaUtilLoggingHelper.redirectToApacheCommons(threshold);
117            }
118            log.info("Starting Nuxeo Runtime service " + getName() + "; version: " + getVersion());
119            // NXRuntime.setInstance(this);
120            manager = createComponentManager();
121            Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_START, this));
122            doStart();
123            startExtensions();
124            isStarted = true;
125            Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STARTED, this));
126        }
127    }
128
129    @Override
130    public synchronized void stop() {
131        if (!isStarted) {
132            return;
133        }
134        isShuttingDown = true;
135        try {
136            log.info("Stopping Nuxeo Runtime service " + getName() + "; version: " + getVersion());
137            Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP, this));
138            try {
139                stopExtensions();
140                doStop();
141                manager.shutdown();
142            } finally {
143                isStarted = false;
144                Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STOPPED, this));
145                manager = null;
146            }
147            JavaUtilLoggingHelper.reset();
148        } finally {
149            isShuttingDown = false;
150        }
151    }
152
153    @Override
154    public boolean isStarted() {
155        return isStarted;
156    }
157
158    @Override
159    public boolean isShuttingDown() {
160        return isShuttingDown;
161    }
162
163    protected void doStart() {
164    }
165
166    protected void doStop() {
167    }
168
169    @Override
170    public File getHome() {
171        return workingDir;
172    }
173
174    public void setHome(File home) {
175        workingDir = home;
176    }
177
178    @Override
179    public String getDescription() {
180        return toString();
181    }
182
183    @Override
184    public CryptoProperties getProperties() {
185        // do not unreference properties: some methods rely on this to set
186        // variables here...
187        return properties;
188    }
189
190    @Override
191    public String getProperty(String name) {
192        return getProperty(name, null);
193    }
194
195    @Override
196    public String getProperty(String name, String defValue) {
197        String value = properties.getProperty(name, defValue);
198        if (value == null || ("${" + name + "}").equals(value)) {
199            // avoid loop, don't expand
200            return value;
201        }
202        return expandVars(value);
203    }
204
205    @Override
206    public void setProperty(String name, Object value) {
207        properties.setProperty(name, value.toString());
208    }
209
210    @Override
211    public String toString() {
212        StringBuilder sb = new StringBuilder();
213        return sb.append(getName()).append(" version ").append(getVersion().toString()).toString();
214    }
215
216    @Override
217    public Object getComponent(String name) {
218        ComponentInstance co = getComponentInstance(name);
219        return co != null ? co.getInstance() : null;
220    }
221
222    @Override
223    public Object getComponent(ComponentName name) {
224        ComponentInstance co = getComponentInstance(name);
225        return co != null ? co.getInstance() : null;
226    }
227
228    @Override
229    public ComponentInstance getComponentInstance(String name) {
230        return manager.getComponent(new ComponentName(name));
231    }
232
233    @Override
234    public ComponentInstance getComponentInstance(ComponentName name) {
235        return manager.getComponent(name);
236    }
237
238    @Override
239    public ComponentManager getComponentManager() {
240        return manager;
241    }
242
243    @Override
244    public RuntimeContext getContext() {
245        return context;
246    }
247
248    protected void startExtensions() {
249        for (RuntimeExtension ext : extensions) {
250            ext.start();
251        }
252    }
253
254    protected void stopExtensions() {
255        for (RuntimeExtension ext : extensions) {
256            ext.stop();
257        }
258    }
259
260    @Override
261    public <T> T getService(Class<T> serviceClass) {
262        return manager.getService(serviceClass);
263    }
264
265    @Override
266    public String expandVars(String expression) {
267        return new TextTemplate(properties).processText(expression);
268    }
269
270    @Override
271    public File getBundleFile(Bundle bundle) {
272        return null;
273    }
274
275    @Override
276    public Bundle getBundle(String symbolicName) {
277        throw new UnsupportedOperationException("Not implemented");
278    }
279
280    /**
281     * @since 5.5
282     * @param msg summary message about all components loading status
283     * @return true if there was no detected error, else return false
284     */
285    @Override
286    public boolean getStatusMessage(StringBuilder msg) {
287        String hr = "======================================================================";
288        if (!warnings.isEmpty()) {
289            msg.append(hr).append("\n= Component Loading Errors:\n");
290            for (String warning : warnings) {
291                msg.append("  * ").append(warning).append('\n');
292            }
293        }
294        Map<ComponentName, Set<ComponentName>> pendingRegistrations = manager.getPendingRegistrations();
295        Collection<ComponentName> unstartedRegistrations = manager.getActivatingRegistrations();
296        unstartedRegistrations.addAll(manager.getStartFailureRegistrations());
297        msg.append(hr)
298           .append("\n= Component Loading Status: Pending: ")
299           .append(pendingRegistrations.size())
300           .append(" / Unstarted: ")
301           .append(unstartedRegistrations.size())
302           .append(" / Total: ")
303           .append(manager.getRegistrations().size())
304           .append('\n');
305        for (Entry<ComponentName, Set<ComponentName>> e : pendingRegistrations.entrySet()) {
306            msg.append("  * ").append(e.getKey()).append(" requires ").append(e.getValue()).append('\n');
307        }
308        for (ComponentName componentName : unstartedRegistrations) {
309            msg.append("  - ").append(componentName).append('\n');
310        }
311        msg.append(hr);
312        return (warnings.isEmpty() && pendingRegistrations.isEmpty() && unstartedRegistrations.isEmpty());
313    }
314
315}