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