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