001/*
002 * (C) Copyright 2006-2017 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        try {
088            return ri.getContext().loadClass(ri.getImplementation()).newInstance();
089        } catch (ReflectiveOperationException e) {
090            throw new RuntimeServiceException(e);
091        }
092    }
093
094    @Override
095    public void destroy() {
096        deactivate();
097        instance = null;
098        ri = null;
099        factories = null;
100    }
101
102    @Override
103    public RuntimeContext getContext() {
104        return ri.getContext();
105    }
106
107    @Override
108    public ComponentName getName() {
109        return ri.getName();
110    }
111
112    // TODO: cache info about implementation to avoid computing it each time
113    @Override
114    public void activate() {
115        // activate the implementation instance
116        try {
117            if (instance instanceof Component) {
118                ((Component) instance).activate(this);
119            } else { // try by reflection
120                Method meth = instance.getClass().getDeclaredMethod("activate", ComponentContext.class);
121                meth.setAccessible(true);
122                meth.invoke(instance, this);
123            }
124            registerServices();
125        } catch (NoSuchMethodException e) {
126            // ignore this exception since the activate method is not mandatory
127        } catch (SecurityException | IllegalAccessException | InvocationTargetException e) {
128            handleError("Failed to activate component: " + getName(), e);
129        }
130    }
131
132    // TODO: cache info about implementation to avoid computing it each time
133    @Override
134    public void deactivate() {
135        // activate the implementation instance
136        try {
137            unregisterServices();
138            if (instance instanceof Component) {
139                ((Component) instance).deactivate(this);
140            } else {
141                // try by reflection
142                Method meth = instance.getClass().getDeclaredMethod("deactivate", ComponentContext.class);
143                meth.setAccessible(true);
144                meth.invoke(instance, this);
145            }
146        } catch (NoSuchMethodException e) {
147            // ignore this exception since the activate method is not mandatory
148        } catch (SecurityException | IllegalAccessException | InvocationTargetException e) {
149            handleError("Failed to deactivate component: " + getName(), e);
150        }
151    }
152
153    /**
154     * @since 9.3
155     */
156    @Override
157    public void start() {
158        if (instance instanceof Component) {
159            ((Component) instance).start(this);
160        }
161    }
162
163    /**
164     * @since 9.3
165     */
166    @Override
167    public void stop() throws InterruptedException {
168        if (instance instanceof Component) {
169            ((Component) instance).stop(this);
170        }
171    }
172
173    /**
174     * @deprecated since 9.3, but in fact since 5.6, only usage in {@link RegistrationInfoImpl}
175     */
176    @Deprecated
177    @Override
178    public void reload() {
179        // activate the implementation instance
180        try {
181            Method meth = instance.getClass().getDeclaredMethod("reload", ComponentContext.class);
182            meth.setAccessible(true);
183            meth.invoke(instance, this);
184        } catch (NoSuchMethodException e) {
185            // ignore this exception since the reload method is not mandatory
186        } catch (ReflectiveOperationException e) {
187            handleError("Failed to reload component: " + getName(), e);
188        }
189    }
190
191    // TODO: cache info about implementation to avoid computing it each time
192    @Override
193    public void registerExtension(Extension extension) {
194        // if this the target extension point is extending another extension
195        // point from another component
196        // then delegate the registration to the that component component
197        Optional<ExtensionPoint> optXp = ri.getExtensionPoint(extension.getExtensionPoint());
198        if (optXp.isPresent()) {
199            String superCo = optXp.get().getSuperComponent();
200            if (superCo != null) {
201                // we don't implement extension point overriding for now in new implementation of RegistrationInfo
202                ((ExtensionImpl) extension).target = new ComponentName(superCo);
203                ((RegistrationInfoImpl) ri).manager.registerExtension(extension);
204                return;
205            }
206            // this extension is for us - register it
207            // activate the implementation instance
208            if (instance instanceof Component) {
209                ((Component) instance).registerExtension(extension);
210            } else if (instance != this) {
211                // try by reflection, avoiding stack overflow
212                try {
213                    Method meth = instance.getClass().getDeclaredMethod("registerExtension", Extension.class);
214                    meth.setAccessible(true);
215                    meth.invoke(instance, extension);
216                } catch (ReflectiveOperationException e) {
217                    handleError("Error registering " + extension.getComponent().getName(), e);
218                }
219            }
220        } else {
221            String message = "Warning: target extension point '" + extension.getExtensionPoint() + "' of '"
222                    + extension.getTargetComponent().getName() + "' is unknown. Check your extension in component "
223                    + extension.getComponent().getName();
224            handleError(message, null);
225        }
226    }
227
228    // TODO: cache info about implementation to avoid computing it each time
229    @Override
230    public void unregisterExtension(Extension extension) {
231        // activate the implementation instance
232        if (instance instanceof Component) {
233            ((Component) instance).unregisterExtension(extension);
234        } else if (instance != this) {
235            // try by reflection, avoiding stack overflow
236            try {
237                Method meth = instance.getClass().getDeclaredMethod("unregisterExtension", Extension.class);
238                meth.setAccessible(true);
239                meth.invoke(instance, extension);
240            } catch (ReflectiveOperationException e) {
241                handleError("Error unregistering " + extension.getComponent().getName(), e);
242            }
243        }
244    }
245
246    protected void handleError(String message, Exception e) {
247        Exception ee = e;
248        if (e != null) {
249            ee = ExceptionUtils.unwrapInvoke(e);
250        }
251        log.error(message, ee);
252        Framework.getRuntime().getMessageHandler().addError(message);
253    }
254
255    @Override
256    public <T> T getAdapter(Class<T> adapter) {
257        T res = null;
258        Object object = getInstance();
259        if (object == null) {
260            return null;
261        }
262        if (object instanceof Adaptable) {
263            res = ((Adaptable) object).getAdapter(adapter);
264        } else if (adapter.isAssignableFrom(object.getClass())) {
265            res = adapter.cast(object);
266        }
267        // to handle hot reload
268        if (res instanceof TimestampedService && object instanceof TimestampedService) {
269            Long lastModified = ((TimestampedService) object).getLastModified();
270            ((TimestampedService) res).setLastModified(lastModified);
271        }
272        return res;
273    }
274
275    @Override
276    public String[] getPropertyNames() {
277        Set<String> set = ri.getProperties().keySet();
278        return set.toArray(new String[set.size()]);
279    }
280
281    @Override
282    public Property getProperty(String property) {
283        return ri.getProperties().get(property);
284    }
285
286    @Override
287    public RuntimeContext getRuntimeContext() {
288        return ri.getContext();
289    }
290
291    @Override
292    public Object getPropertyValue(String property) {
293        return getPropertyValue(property, null);
294    }
295
296    @Override
297    public Object getPropertyValue(String property, Object defValue) {
298        Property prop = getProperty(property);
299        if (prop != null) {
300            return prop.getValue();
301        } else {
302            return defValue;
303        }
304    }
305
306    @Override
307    public String[] getProvidedServiceNames() {
308        return ri.getProvidedServiceNames();
309    }
310
311    /**
312     * Register provided services as OSGi services
313     */
314    public void registerServices() {
315        if (!Framework.isOSGiServiceSupported()) {
316            return;
317        }
318        String[] names = getProvidedServiceNames();
319        if (names != null && names.length > 0) {
320            factories = new ArrayList<>();
321            for (String className : names) {
322                OSGiServiceFactory factory = new OSGiServiceFactory(className);
323                factory.register();
324                factories.add(factory);
325            }
326        }
327    }
328
329    public void unregisterServices() {
330        // TODO the reload method is not reloading services. do we want this?
331        if (factories != null) {
332            for (OSGiServiceFactory factory : factories) {
333                factory.unregister();
334            }
335            factories = null;
336        }
337    }
338
339    @Override
340    public String toString() {
341        if (ri == null) {
342            return super.toString();
343        }
344        return ri.toString();
345    }
346
347    protected class OSGiServiceFactory implements ServiceFactory {
348        protected Class<?> clazz;
349
350        protected ServiceRegistration reg;
351
352        public OSGiServiceFactory(String className) {
353            this(ri.getContext().getBundle(), className);
354        }
355
356        public OSGiServiceFactory(Bundle bundle, String className) {
357            try {
358                clazz = ri.getContext().getBundle().loadClass(className);
359            } catch (ClassNotFoundException e) {
360                throw new RuntimeServiceException(e);
361            }
362        }
363
364        @Override
365        public Object getService(Bundle bundle, ServiceRegistration registration) {
366            return getAdapter(clazz);
367        }
368
369        @Override
370        public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
371            // do nothing
372        }
373
374        public void register() {
375            reg = ri.getContext().getBundle().getBundleContext().registerService(clazz.getName(), this, null);
376        }
377
378        public void unregister() {
379            if (reg != null) {
380                reg.unregister();
381            }
382            reg = null;
383        }
384    }
385
386}