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