001/*
002 * (C) Copyright 2006-2017 Nuxeo (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 */
019package org.nuxeo.runtime;
020
021import java.io.File;
022import java.net.URL;
023import java.time.Duration;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.List;
028import java.util.Map;
029import java.util.Map.Entry;
030import java.util.Set;
031import java.util.logging.Level;
032import java.util.stream.Collectors;
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.Extension;
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    /**
088     * Warnings during the deployment. These messages don't block startup, even in strict mode.
089     */
090    protected final List<String> warnings = new ArrayList<>();
091
092    /**
093     * Errors during the deployment. Here are collected all errors occurred during the startup. These messages block
094     * startup in strict mode.
095     *
096     * @since 9.1
097     */
098    protected final List<String> errors = new ArrayList<>();
099
100    protected AbstractRuntimeService(DefaultRuntimeContext context, Map<String, String> properties) {
101        this.context = context;
102        context.setRuntime(this);
103        if (properties != null) {
104            this.properties.putAll(properties);
105        }
106        // get errors set by NuxeoDeployer
107        String errs = System.getProperty("org.nuxeo.runtime.deployment.errors");
108        if (errs != null) {
109            errors.addAll(Arrays.asList(errs.split("\n")));
110            System.clearProperty("org.nuxeo.runtime.deployment.errors");
111        }
112    }
113
114    @Override
115    public List<String> getWarnings() {
116        return warnings;
117    }
118
119    /**
120     * @since 9.1
121     */
122    @Override
123    public List<String> getErrors() {
124        return errors;
125    }
126
127    protected ComponentManager createComponentManager() {
128        return new ComponentManagerImpl(this);
129    }
130
131    protected static URL getBuiltinFeatureURL() {
132        return Thread.currentThread().getContextClassLoader().getResource("org/nuxeo/runtime/nx-feature.xml");
133    }
134
135    @Override
136    public synchronized void start() {
137        if (isStarted) {
138            return;
139        }
140        if (Boolean.parseBoolean(getProperty(REDIRECT_JUL, "true"))) {
141            Level threshold = Level.parse(getProperty(REDIRECT_JUL_THRESHOLD, "INFO").toUpperCase());
142            JavaUtilLoggingHelper.redirectToApacheCommons(threshold);
143        }
144        log.info("Starting Nuxeo Runtime service " + getName() + "; version: " + getVersion());
145        // NXRuntime.setInstance(this);
146        manager = createComponentManager();
147        Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_START, this));
148        ServicePassivator.passivate()
149                         .withQuietDelay(Duration.ofSeconds(0))
150                         .monitor()
151                         .withTimeout(Duration.ofSeconds(0))
152                         .withEnforceMode(false)
153                         .await()
154                         .proceed(() -> {
155                             try {
156                                 doStart();
157                                 startExtensions();
158                             } finally {
159                                 Framework.sendEvent(
160                                         new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STARTED, this));
161                                 isStarted = true;
162                             }
163                         });
164    }
165
166    @Override
167    public synchronized void stop() {
168        if (!isStarted) {
169            return;
170        }
171        isShuttingDown = true;
172        try {
173            log.info("Stopping Nuxeo Runtime service " + getName() + "; version: " + getVersion());
174            Framework.sendEvent(new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_ABOUT_TO_STOP, this));
175            ServicePassivator.passivate()
176                             .withQuietDelay(Duration.ofSeconds(0))
177                             .monitor()
178                             .withTimeout(Duration.ofSeconds(0))
179                             .withEnforceMode(false)
180                             .await()
181                             .proceed(() -> {
182                                 try {
183                                     stopExtensions();
184                                     doStop();
185                                     manager.shutdown();
186                                 } finally {
187                                     isStarted = false;
188                                     Framework.sendEvent(
189                                             new RuntimeServiceEvent(RuntimeServiceEvent.RUNTIME_STOPPED, this));
190                                     manager = null;
191                                 }
192                             });
193        } finally {
194            JavaUtilLoggingHelper.reset();
195            isShuttingDown = false;
196        }
197    }
198
199    @Override
200    public boolean isStarted() {
201        return isStarted;
202    }
203
204    @Override
205    public boolean isShuttingDown() {
206        return isShuttingDown;
207    }
208
209    protected void doStart() {
210    }
211
212    protected void doStop() {
213    }
214
215    @Override
216    public File getHome() {
217        return workingDir;
218    }
219
220    public void setHome(File home) {
221        workingDir = home;
222    }
223
224    @Override
225    public String getDescription() {
226        return toString();
227    }
228
229    @Override
230    public CryptoProperties getProperties() {
231        // do not unreference properties: some methods rely on this to set
232        // variables here...
233        return properties;
234    }
235
236    @Override
237    public String getProperty(String name) {
238        return getProperty(name, null);
239    }
240
241    @Override
242    public String getProperty(String name, String defValue) {
243        String value = properties.getProperty(name, defValue);
244        if (value == null || ("${" + name + "}").equals(value)) {
245            // avoid loop, don't expand
246            return value;
247        }
248        return expandVars(value);
249    }
250
251    @Override
252    public void setProperty(String name, Object value) {
253        properties.setProperty(name, value.toString());
254    }
255
256    @Override
257    public String toString() {
258        return getName() + " version " + getVersion();
259    }
260
261    @Override
262    public Object getComponent(String name) {
263        ComponentInstance co = getComponentInstance(name);
264        return co != null ? co.getInstance() : null;
265    }
266
267    @Override
268    public Object getComponent(ComponentName name) {
269        ComponentInstance co = getComponentInstance(name);
270        return co != null ? co.getInstance() : null;
271    }
272
273    @Override
274    public ComponentInstance getComponentInstance(String name) {
275        return manager.getComponent(new ComponentName(name));
276    }
277
278    @Override
279    public ComponentInstance getComponentInstance(ComponentName name) {
280        return manager.getComponent(name);
281    }
282
283    @Override
284    public ComponentManager getComponentManager() {
285        return manager;
286    }
287
288    @Override
289    public RuntimeContext getContext() {
290        return context;
291    }
292
293    protected void startExtensions() {
294        for (RuntimeExtension ext : extensions) {
295            ext.start();
296        }
297    }
298
299    protected void stopExtensions() {
300        for (RuntimeExtension ext : extensions) {
301            ext.stop();
302        }
303    }
304
305    @Override
306    public <T> T getService(Class<T> serviceClass) {
307        return manager.getService(serviceClass);
308    }
309
310    @Override
311    public String expandVars(String expression) {
312        return new TextTemplate(properties).processText(expression);
313    }
314
315    @Override
316    public File getBundleFile(Bundle bundle) {
317        return null;
318    }
319
320    @Override
321    public Bundle getBundle(String symbolicName) {
322        throw new UnsupportedOperationException("Not implemented");
323    }
324
325    /**
326     * @since 5.5
327     * @param msg summary message about all components loading status
328     * @return true if there was no detected error, else return false
329     */
330    @Override
331    public boolean getStatusMessage(StringBuilder msg) {
332        String hr = "======================================================================";
333        if (!warnings.isEmpty()) {
334            msg.append(hr).append("\n= Component Loading Warnings:\n");
335            for (String warning : warnings) {
336                msg.append("  * ").append(warning).append('\n');
337            }
338        }
339        if (!errors.isEmpty()) {
340            msg.append(hr).append("\n= Component Loading Errors:\n");
341            for (String error : errors) {
342                msg.append("  * ").append(error).append('\n');
343            }
344        }
345        Map<ComponentName, Set<ComponentName>> pendingRegistrations = manager.getPendingRegistrations();
346        Map<ComponentName, Set<Extension>> missingRegistrations = manager.getMissingRegistrations();
347        Collection<ComponentName> unstartedRegistrations = manager.getActivatingRegistrations();
348        unstartedRegistrations.addAll(manager.getStartFailureRegistrations());
349        msg.append(hr)
350           .append("\n= Component Loading Status: Pending: ")
351           .append(pendingRegistrations.size())
352           .append(" / Missing: ")
353           .append(missingRegistrations.size())
354           .append(" / Unstarted: ")
355           .append(unstartedRegistrations.size())
356           .append(" / Total: ")
357           .append(manager.getRegistrations().size())
358           .append('\n');
359        for (Entry<ComponentName, Set<ComponentName>> e : pendingRegistrations.entrySet()) {
360            msg.append("  * ").append(e.getKey()).append(" requires ").append(e.getValue()).append('\n');
361        }
362        for (Entry<ComponentName, Set<Extension>> e : missingRegistrations.entrySet()) {
363            msg.append("  * ")
364               .append(e.getKey())
365               .append(" references missing ")
366               .append(e.getValue()
367                        .stream()
368                        .map(ext -> "target=" + ext.getTargetComponent().getName() + ";point="
369                                + ext.getExtensionPoint())
370                        .collect(Collectors.toList()))
371               .append('\n');
372        }
373        for (ComponentName componentName : unstartedRegistrations) {
374            msg.append("  - ").append(componentName).append('\n');
375        }
376        msg.append(hr);
377        return (errors.isEmpty() && pendingRegistrations.isEmpty() && missingRegistrations.isEmpty()
378                && unstartedRegistrations.isEmpty());
379    }
380
381}