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