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 java.util.ArrayList;
023import java.util.List;
024import java.util.Optional;
025import java.util.Set;
026
027import org.apache.commons.lang3.reflect.MethodUtils;
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.common.utils.ExceptionUtils;
031import org.nuxeo.runtime.RuntimeMessage;
032import org.nuxeo.runtime.RuntimeMessage.Level;
033import org.nuxeo.runtime.RuntimeMessage.Source;
034import org.nuxeo.runtime.RuntimeServiceException;
035import org.nuxeo.runtime.api.Framework;
036import org.nuxeo.runtime.model.Adaptable;
037import org.nuxeo.runtime.model.Component;
038import org.nuxeo.runtime.model.ComponentInstance;
039import org.nuxeo.runtime.model.ComponentName;
040import org.nuxeo.runtime.model.Extension;
041import org.nuxeo.runtime.model.ExtensionPoint;
042import org.nuxeo.runtime.model.Property;
043import org.nuxeo.runtime.model.RegistrationInfo;
044import org.nuxeo.runtime.model.RuntimeContext;
045import org.nuxeo.runtime.service.TimestampedService;
046import org.osgi.framework.Bundle;
047import org.osgi.framework.ServiceFactory;
048import org.osgi.framework.ServiceRegistration;
049
050/**
051 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
052 */
053public class ComponentInstanceImpl implements ComponentInstance {
054
055    private static final Log log = LogFactory.getLog(ComponentInstanceImpl.class);
056
057    protected Object instance;
058
059    protected RegistrationInfo ri;
060
061    protected List<OSGiServiceFactory> factories;
062
063    public ComponentInstanceImpl(RegistrationInfo ri) {
064        this.ri = ri;
065        if (ri.getImplementation() == null) {
066            // TODO: should be an extension component
067            instance = this;
068        } else {
069            // TODO: load class only once when creating the registration info
070            instance = createInstance();
071        }
072    }
073
074    @Override
075    public Object getInstance() {
076        return instance;
077    }
078
079    public void create() {
080        if (ri.getImplementation() == null) {
081            instance = this; // should be an extension component
082        } else {
083            // TODO: load class only once when creating the reshgitration info
084            instance = createInstance();
085        }
086    }
087
088    protected Object createInstance() {
089        Object object;
090        try {
091            object = ri.getContext().loadClass(ri.getImplementation()).getDeclaredConstructor().newInstance();
092        } catch (ReflectiveOperationException | LinkageError e) {
093            throw new RuntimeServiceException(e);
094        }
095        if (object instanceof Component) {
096            ((Component) object).setName(ri.getName().getName());
097        }
098        return object;
099    }
100
101    @Override
102    public void destroy() {
103        deactivate();
104        instance = null;
105        ri = null;
106        factories = null;
107    }
108
109    @Override
110    public RuntimeContext getContext() {
111        return ri.getContext();
112    }
113
114    @Override
115    public ComponentName getName() {
116        return ri.getName();
117    }
118
119    // TODO: cache info about implementation to avoid computing it each time
120    @Override
121    public void activate() {
122        // activate the implementation instance
123        try {
124            if (instance instanceof Component) {
125                ((Component) instance).activate(this);
126            } else if (instance != this) {
127                // try by reflection
128                MethodUtils.invokeMethod(instance, true, "activate", this);
129            }
130            registerServices();
131        } catch (NoSuchMethodException e) {
132            // ignore this exception since the activate method is not mandatory
133        } catch (ReflectiveOperationException e) {
134            handleError("Failed to activate component: " + getName(), Source.COMPONENT, getName().getName(), e);
135        }
136    }
137
138    // TODO: cache info about implementation to avoid computing it each time
139    @Override
140    public void deactivate() {
141        // activate the implementation instance
142        try {
143            unregisterServices();
144            if (instance instanceof Component) {
145                ((Component) instance).deactivate(this);
146            } else if (instance != this) {
147                // try by reflection
148                MethodUtils.invokeMethod(instance, true, "deactivate", this);
149            }
150        } catch (NoSuchMethodException e) {
151            // ignore this exception since the deactivate method is not mandatory
152        } catch (ReflectiveOperationException e) {
153            handleError("Failed to deactivate component: " + getName(), Source.COMPONENT, getName().getName(), e);
154        }
155    }
156
157    /**
158     * @since 9.3
159     */
160    @Override
161    public void start() {
162        if (instance instanceof Component) {
163            ((Component) instance).start(this);
164        }
165    }
166
167    /**
168     * @since 9.3
169     */
170    @Override
171    public void stop() throws InterruptedException {
172        if (instance instanceof Component) {
173            ((Component) instance).stop(this);
174        }
175    }
176
177    /**
178     * @deprecated since 9.3, but in fact since 5.6, only usage in {@link RegistrationInfoImpl}
179     */
180    @Deprecated
181    @Override
182    public void reload() {
183        // activate the implementation instance
184        try {
185            MethodUtils.invokeMethod(instance, true, "reload", this);
186        } catch (NoSuchMethodException e) {
187            // ignore this exception since the reload method is not mandatory
188        } catch (ReflectiveOperationException e) {
189            handleError("Failed to reload component: " + getName(), Source.COMPONENT, getName().getName(), e);
190        }
191    }
192
193    // TODO: cache info about implementation to avoid computing it each time
194    @Override
195    public void registerExtension(Extension extension) {
196        // if this the target extension point is extending another extension
197        // point from another component
198        // then delegate the registration to the that component component
199        Optional<ExtensionPoint> optXp = ri.getExtensionPoint(extension.getExtensionPoint());
200        if (optXp.isPresent()) {
201            String superCo = optXp.get().getSuperComponent();
202            if (superCo != null) {
203                // we don't implement extension point overriding for now in new implementation of RegistrationInfo
204                ((ExtensionImpl) extension).target = new ComponentName(superCo);
205                ((RegistrationInfoImpl) ri).manager.registerExtension(extension);
206                return;
207            }
208            // this extension is for us - register it
209            // activate the implementation instance
210            if (instance instanceof Component) {
211                ((Component) instance).registerExtension(extension);
212            } else if (instance != this) {
213                // try by reflection, avoiding stack overflow
214                try {
215                    MethodUtils.invokeMethod(instance, true, "registerExtension", extension);
216                } catch (ReflectiveOperationException e) {
217                    ComponentName compName = extension.getComponent().getName();
218                    handleError("Error registering " + compName, Source.EXTENSION, compName.getName(), e);
219                }
220            }
221        } else {
222            ComponentName compName = extension.getComponent().getName();
223            String message = "Warning: target extension point '" + extension.getExtensionPoint() + "' of '"
224                    + extension.getTargetComponent().getName() + "' is unknown. Check your extension in component "
225                    + compName;
226            handleError(message, Source.EXTENSION, compName.getName(), null);
227        }
228    }
229
230    // TODO: cache info about implementation to avoid computing it each time
231    @Override
232    public void unregisterExtension(Extension extension) {
233        // activate the implementation instance
234        if (instance instanceof Component) {
235            ((Component) instance).unregisterExtension(extension);
236        } else if (instance != this) {
237            // try by reflection, avoiding stack overflow
238            try {
239                MethodUtils.invokeMethod(instance, true, "unregisterExtension", extension);
240            } catch (ReflectiveOperationException e) {
241                ComponentName compName = extension.getComponent().getName();
242                handleError("Error unregistering " + compName, Source.EXTENSION, compName.getName(), e);
243            }
244        }
245    }
246
247    protected void handleError(String message, Source source, String sourceId, Exception e) {
248        Exception ee = e;
249        if (e != null) {
250            ee = ExceptionUtils.unwrapInvoke(e);
251        }
252        log.error(message, ee);
253        Framework.getRuntime()
254                 .getMessageHandler()
255                 .addMessage(new RuntimeMessage(Level.ERROR, message, source, sourceId));
256    }
257
258    @Override
259    public <T> T getAdapter(Class<T> adapter) {
260        T res = null;
261        Object object = getInstance();
262        if (object == null) {
263            return null;
264        }
265        if (object instanceof Adaptable) {
266            res = ((Adaptable) object).getAdapter(adapter);
267        } else if (adapter.isAssignableFrom(object.getClass())) {
268            res = adapter.cast(object);
269        }
270        // to handle hot reload
271        if (res instanceof TimestampedService && object instanceof TimestampedService) {
272            Long lastModified = ((TimestampedService) object).getLastModified();
273            ((TimestampedService) res).setLastModified(lastModified);
274        }
275        return res;
276    }
277
278    @Override
279    public String[] getPropertyNames() {
280        Set<String> set = ri.getProperties().keySet();
281        return set.toArray(new String[set.size()]);
282    }
283
284    @Override
285    public Property getProperty(String property) {
286        return ri.getProperties().get(property);
287    }
288
289    @Override
290    public RuntimeContext getRuntimeContext() {
291        return ri.getContext();
292    }
293
294    @Override
295    public Object getPropertyValue(String property) {
296        return getPropertyValue(property, null);
297    }
298
299    @Override
300    public Object getPropertyValue(String property, Object defValue) {
301        Property prop = getProperty(property);
302        if (prop != null) {
303            return prop.getValue();
304        } else {
305            return defValue;
306        }
307    }
308
309    @Override
310    public String[] getProvidedServiceNames() {
311        return ri.getProvidedServiceNames();
312    }
313
314    /**
315     * Register provided services as OSGi services
316     */
317    public void registerServices() {
318        if (!Framework.isOSGiServiceSupported()) {
319            return;
320        }
321        String[] names = getProvidedServiceNames();
322        if (names != null && names.length > 0) {
323            factories = new ArrayList<>();
324            for (String className : names) {
325                OSGiServiceFactory factory = new OSGiServiceFactory(className);
326                factory.register();
327                factories.add(factory);
328            }
329        }
330    }
331
332    public void unregisterServices() {
333        // TODO the reload method is not reloading services. do we want this?
334        if (factories != null) {
335            for (OSGiServiceFactory factory : factories) {
336                factory.unregister();
337            }
338            factories = null;
339        }
340    }
341
342    @Override
343    public String toString() {
344        if (ri == null) {
345            return super.toString();
346        }
347        return ri.toString();
348    }
349
350    protected class OSGiServiceFactory implements ServiceFactory {
351        protected Class<?> clazz;
352
353        protected ServiceRegistration reg;
354
355        public OSGiServiceFactory(String className) {
356            this(ri.getContext().getBundle(), className);
357        }
358
359        public OSGiServiceFactory(Bundle bundle, String className) {
360            try {
361                clazz = ri.getContext().getBundle().loadClass(className);
362            } catch (ClassNotFoundException e) {
363                throw new RuntimeServiceException(e);
364            }
365        }
366
367        @Override
368        public Object getService(Bundle bundle, ServiceRegistration registration) {
369            return getAdapter(clazz);
370        }
371
372        @Override
373        public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
374            // do nothing
375        }
376
377        public void register() {
378            reg = ri.getContext().getBundle().getBundleContext().registerService(clazz.getName(), this, null);
379        }
380
381        public void unregister() {
382            if (reg != null) {
383                reg.unregister();
384            }
385            reg = null;
386        }
387    }
388
389    @Override
390    public RegistrationInfo getRegistrationInfo() {
391        return ri;
392    }
393
394}