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