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