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