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