001/*
002 * (C) Copyright 2006-2020 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 *     Anahide Tchertchian
019 */
020package org.nuxeo.runtime.model.impl;
021
022import static java.nio.charset.StandardCharsets.UTF_8;
023
024import java.io.ByteArrayInputStream;
025import java.io.IOException;
026import java.io.InputStream;
027import java.net.URL;
028import java.util.ArrayList;
029import java.util.List;
030
031import org.apache.commons.io.IOUtils;
032import org.apache.commons.lang3.StringUtils;
033import org.apache.logging.log4j.LogManager;
034import org.apache.logging.log4j.Logger;
035import org.nuxeo.runtime.RuntimeService;
036import org.nuxeo.runtime.RuntimeServiceException;
037import org.nuxeo.runtime.api.Framework;
038import org.nuxeo.runtime.model.ComponentManager;
039import org.nuxeo.runtime.model.ComponentName;
040import org.nuxeo.runtime.model.RegistrationInfo;
041import org.nuxeo.runtime.model.RuntimeContext;
042import org.nuxeo.runtime.model.StreamRef;
043import org.nuxeo.runtime.model.URLStreamRef;
044import org.nuxeo.runtime.osgi.OSGiRuntimeActivator;
045import org.nuxeo.runtime.osgi.OSGiRuntimeContext;
046import org.osgi.framework.Bundle;
047
048/**
049 * New behavior @since 9.2 As the runtime lifecycle changed there make no sense to unregister components by their own.
050 * <p>
051 * The component registry is either reset to a clean state or shutdown.
052 * <p>
053 * So methods like unregister by location used in tests make no sense. These methods are preserved yet for compatibility
054 * with some tests but may be removed in future.
055 * <p>
056 * Also when a context is destroyed we unregister all the components the context deployed.
057 * <p>
058 * This is also deprecated since the unregister component is deprecated too.
059 * <p>
060 * The code inside destroy method was removed too since the deployedFiles map was removed.
061 * <p>
062 * This map posed problems with the registry snapshot approaches since it was not kept in sync with the registry.
063 * <p>
064 * <p>
065 * If unregistering components will be removed completely don't forget to remove
066 * {@link ComponentManager#unregisterByLocation(String)} and the {@link ComponentRegistry#deployedFiles}.
067 * <p>
068 * <p>
069 * Features like studio or IDE which needs unregistering components must use their own mechanism.
070 *
071 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
072 */
073public class DefaultRuntimeContext implements RuntimeContext {
074
075    private static final Logger log = LogManager.getLogger(DefaultRuntimeContext.class);
076
077    protected RuntimeService runtime;
078
079    /**
080     * The list of component names deployed by this context.
081     *
082     * @since 9.2
083     */
084    protected List<ComponentName> components;
085
086    protected final ComponentDescriptorReader reader;
087
088    public DefaultRuntimeContext() {
089        this(Framework.getRuntime());
090    }
091
092    public DefaultRuntimeContext(RuntimeService runtime) {
093        this.runtime = runtime;
094        this.components = new ArrayList<>();
095        reader = new ComponentDescriptorReader();
096    }
097
098    public void setRuntime(RuntimeService runtime) {
099        this.runtime = runtime;
100    }
101
102    @Override
103    public RuntimeService getRuntime() {
104        return runtime;
105    }
106
107    @Override
108    public ComponentName[] getComponents() {
109        return components.toArray(new ComponentName[0]);
110    }
111
112    @Override
113    public URL getResource(String name) {
114        return Thread.currentThread().getContextClassLoader().getResource(name);
115    }
116
117    @Override
118    public URL getLocalResource(String name) {
119        return Thread.currentThread().getContextClassLoader().getResource(name);
120    }
121
122    @Override
123    public Class<?> loadClass(String className) throws ClassNotFoundException {
124        return Thread.currentThread().getContextClassLoader().loadClass(className);
125    }
126
127    @Override
128    public RegistrationInfo deploy(URL url) throws IOException {
129        return deploy(new URLStreamRef(url));
130    }
131
132    @Override
133    public RegistrationInfo deploy(StreamRef ref) throws IOException {
134        String name = ref.getId();
135        RegistrationInfoImpl ri = createRegistrationInfo(ref);
136        log.debug("Deploying component from url {}", name);
137        ri.sourceId = name;
138        ri.context = this;
139        ri.xmlFileUrl = ref.asURL();
140        if (ri.getBundle() != null) {
141            // this is an external component XML.
142            // should use the real owner bundle as the context.
143            Bundle bundle = OSGiRuntimeActivator.getInstance().getBundle(ri.getBundle());
144            if (bundle != null) {
145                ri.context = new OSGiRuntimeContext(bundle);
146            }
147        }
148        runtime.getComponentManager().register(ri);
149        components.add(ri.getName());
150        return ri;
151    }
152
153    @Override
154    public void undeploy(URL url) {
155        runtime.getComponentManager().unregisterByLocation(url.toString());
156    }
157
158    @Override
159    public void undeploy(StreamRef ref) {
160        runtime.getComponentManager().unregisterByLocation(ref.getId());
161    }
162
163    @Override
164    public boolean isDeployed(URL url) {
165        return runtime.getComponentManager().hasComponentFromLocation(url.toString());
166    }
167
168    @Override
169    public boolean isDeployed(StreamRef ref) {
170        return runtime.getComponentManager().hasComponentFromLocation(ref.getId());
171    }
172
173    @Override
174    public RegistrationInfo deploy(String location) {
175        URL url = getLocalResource(location);
176        if (url == null) {
177            throw new IllegalArgumentException("No local resources was found with this name: " + location);
178        }
179        try {
180            return deploy(url);
181        } catch (IOException e) {
182            throw new RuntimeServiceException("Cannot deploy: " + location, e);
183        }
184    }
185
186    @Override
187    public void undeploy(String location) {
188        URL url = getLocalResource(location);
189        if (url == null) {
190            throw new IllegalArgumentException("No local resources was found with this name: " + location);
191        }
192        undeploy(url);
193    }
194
195    @Override
196    public boolean isDeployed(String location) {
197        URL url = getLocalResource(location);
198        if (url != null) {
199            return isDeployed(url);
200        } else {
201            log.warn("No local resources was found with this name: {}", location);
202            return false;
203        }
204    }
205
206    @Override
207    public void destroy() {
208        ComponentManager mgr = runtime.getComponentManager();
209        for (ComponentName cname : components) {
210            mgr.unregister(cname);
211        }
212    }
213
214    @Override
215    public Bundle getBundle() {
216        return null;
217    }
218
219    public RegistrationInfoImpl createRegistrationInfo(StreamRef ref) throws IOException {
220        String source;
221        try (InputStream stream = ref.getStream()) {
222            source = IOUtils.toString(stream, UTF_8);
223        }
224        if (StringUtils.isBlank(source)) {
225            throw new IOException("Empty registration from " + ref.getId());
226        }
227        String expanded = Framework.expandVars(source);
228        RegistrationInfoImpl ri;
229        try (InputStream in = new ByteArrayInputStream(expanded.getBytes())) {
230            ri = createRegistrationInfo(in);
231        } catch (IOException e) {
232            throw new IOException(
233                    String.format("Could not resolve registration from %s (%s)", ref.getId(), e.getMessage()), e);
234        }
235        if (ri == null || ri.getName() == null) {
236            throw new IOException("Could not resolve registration from " + ref.getId());
237        }
238        return ri;
239    }
240
241    public RegistrationInfoImpl createRegistrationInfo(InputStream in) throws IOException {
242        return reader.read(this, in);
243    }
244
245}