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