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
013package org.nuxeo.runtime.model.impl;
014
015import java.net.URL;
016import java.util.ArrayList;
017import java.util.Collections;
018import java.util.HashMap;
019import java.util.HashSet;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.commons.logging.Log;
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.common.xmap.annotation.XContent;
027import org.nuxeo.common.xmap.annotation.XNode;
028import org.nuxeo.common.xmap.annotation.XNodeList;
029import org.nuxeo.common.xmap.annotation.XNodeMap;
030import org.nuxeo.common.xmap.annotation.XObject;
031import org.nuxeo.runtime.ComponentEvent;
032import org.nuxeo.runtime.Version;
033import org.nuxeo.runtime.api.Framework;
034import org.nuxeo.runtime.model.Component;
035import org.nuxeo.runtime.model.ComponentInstance;
036import org.nuxeo.runtime.model.ComponentManager;
037import org.nuxeo.runtime.model.ComponentName;
038import org.nuxeo.runtime.model.ConfigurationDescriptor;
039import org.nuxeo.runtime.model.Extension;
040import org.nuxeo.runtime.model.ExtensionPoint;
041import org.nuxeo.runtime.model.Property;
042import org.nuxeo.runtime.model.RegistrationInfo;
043import org.nuxeo.runtime.model.RuntimeContext;
044
045/**
046 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
047 */
048@XObject("component")
049public class RegistrationInfoImpl implements RegistrationInfo {
050
051    private static final long serialVersionUID = -4135715215018199522L;
052
053    private static final Log log = LogFactory.getLog(RegistrationInfoImpl.class);
054
055    // Note: some of these instance variables are accessed directly from other
056    // classes in this package.
057
058    transient ComponentManagerImpl manager;
059
060    @XNode("@service")
061    ServiceDescriptor serviceDescriptor;
062
063    // the managed object name
064    @XNode("@name")
065    ComponentName name;
066
067    @XNode("@disabled")
068    boolean disabled;
069
070    @XNode("configuration")
071    ConfigurationDescriptor config;
072
073    // the registration state
074    int state = UNREGISTERED;
075
076    // my aliases
077    @XNodeList(value = "alias", type = HashSet.class, componentType = ComponentName.class)
078    Set<ComponentName> aliases = new HashSet<>();
079
080    // the object names I depend of
081    @XNodeList(value = "require", type = HashSet.class, componentType = ComponentName.class)
082    Set<ComponentName> requires = new HashSet<>();
083
084    @XNode("implementation@class")
085    String implementation;
086
087    @XNodeList(value = "extension-point", type = ExtensionPointImpl[].class, componentType = ExtensionPointImpl.class)
088    ExtensionPointImpl[] extensionPoints = new ExtensionPointImpl[0];
089
090    @XNodeList(value = "extension", type = ExtensionImpl[].class, componentType = ExtensionImpl.class)
091    ExtensionImpl[] extensions = new ExtensionImpl[0];
092
093    @XNodeMap(value = "property", key = "@name", type = HashMap.class, componentType = Property.class)
094    Map<String, Property> properties = new HashMap<>();
095
096    @XNode("@version")
097    Version version = Version.ZERO;
098
099    /**
100     * To be set when deploying configuration components that are not in a bundle (e.g. from config. dir). Represent the
101     * bundle that will be assumed to be the owner of the component.
102     */
103    @XNode("@bundle")
104    String bundle;
105
106    @XContent("documentation")
107    String documentation;
108
109    URL xmlFileUrl;
110
111    /**
112     * This is used by the component persistence service to identify registration that was dynamically created and
113     * persisted by users.
114     */
115    boolean isPersistent;
116
117    transient RuntimeContext context;
118
119    // the managed component
120    transient ComponentInstance component;
121
122    public RegistrationInfoImpl() {
123    }
124
125    /**
126     * Useful when dynamically registering components
127     *
128     * @param name the component name
129     */
130    public RegistrationInfoImpl(ComponentName name) {
131        this.name = name;
132    }
133
134    /**
135     * Attach to a manager - this method must be called after all registration fields are initialized.
136     *
137     * @param manager
138     */
139    public void attach(ComponentManagerImpl manager) {
140        if (this.manager != null) {
141            throw new IllegalStateException("Registration '" + name + "' was already attached to a manager");
142        }
143        this.manager = manager;
144    }
145
146    public void setContext(RuntimeContext rc) {
147        this.context = rc;
148    }
149
150    @Override
151    public boolean isDisabled() {
152        return disabled;
153    }
154
155    @Override
156    public final boolean isPersistent() {
157        return isPersistent;
158    }
159
160    @Override
161    public void setPersistent(boolean isPersistent) {
162        this.isPersistent = isPersistent;
163    }
164
165    public void destroy() {
166        requires.clear();
167        aliases.clear();
168        properties.clear();
169        extensionPoints = new ExtensionPointImpl[0];
170        extensions = new ExtensionImpl[0];
171        version = null;
172        component = null;
173        name = null;
174        manager = null;
175    }
176
177    public final boolean isDisposed() {
178        return name == null;
179    }
180
181    @Override
182    public ExtensionPoint[] getExtensionPoints() {
183        return extensionPoints;
184    }
185
186    @Override
187    public ComponentInstance getComponent() {
188        return component;
189    }
190
191    /**
192     * Reload the underlying component if reload is supported
193     */
194    public synchronized void reload() {
195        if (component != null) {
196            component.reload();
197        }
198    }
199
200    @Override
201    public ComponentName getName() {
202        return name;
203    }
204
205    @Override
206    public Map<String, Property> getProperties() {
207        return properties;
208    }
209
210    public ExtensionPointImpl getExtensionPoint(String name) {
211        for (ExtensionPointImpl xp : extensionPoints) {
212            if (xp.name.equals(name)) {
213                return xp;
214            }
215        }
216        return null;
217    }
218
219    @Override
220    public int getState() {
221        return state;
222    }
223
224    @Override
225    public Extension[] getExtensions() {
226        return extensions;
227    }
228
229    @Override
230    public Set<ComponentName> getAliases() {
231        return aliases == null ? Collections.<ComponentName> emptySet() : aliases;
232    }
233
234    @Override
235    public Set<ComponentName> getRequiredComponents() {
236        return requires;
237    }
238
239    @Override
240    public RuntimeContext getContext() {
241        return context;
242    }
243
244    @Override
245    public String getBundle() {
246        return bundle;
247    }
248
249    @Override
250    public Version getVersion() {
251        return version;
252    }
253
254    @Override
255    public String getDocumentation() {
256        return documentation;
257    }
258
259    @Override
260    public String toString() {
261        return "RegistrationInfo: " + name;
262    }
263
264    @Override
265    public ComponentManager getManager() {
266        return manager;
267    }
268
269    synchronized void register() {
270        if (state != UNREGISTERED) {
271            return;
272        }
273        state = REGISTERED;
274        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_REGISTERED, this));
275    }
276
277    synchronized void unregister() {
278        if (state == UNREGISTERED) {
279            return;
280        }
281        if (state == ACTIVATED || state == RESOLVED || state == START_FAILURE ) {
282            unresolve();
283        }
284        state = UNREGISTERED;
285        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNREGISTERED, this));
286        destroy();
287    }
288
289    protected ComponentInstance createComponentInstance() {
290        try {
291            return new ComponentInstanceImpl(this);
292        } catch (RuntimeException e) {
293            String msg = "Failed to instantiate component: " + implementation;
294            log.error(msg, e);
295            msg += " (" + e.toString() + ')';
296            Framework.getRuntime().getWarnings().add(msg);
297            Framework.handleDevError(e);
298            throw e;
299        }
300    }
301
302    public synchronized void restart() {
303        deactivate();
304        activate();
305    }
306
307    @Override
308    public int getApplicationStartedOrder() {
309        if (component == null) {
310            return 0;
311        }
312        Object ci = component.getInstance();
313        if (!(ci instanceof Component)) {
314            return 0;
315        }
316        return ((Component) ci).getApplicationStartedOrder();
317    }
318
319    @Override
320    public void notifyApplicationStarted() {
321        if (component != null) {
322            Object ci = component.getInstance();
323            if (ci instanceof Component) {
324                try {
325                    ((Component) ci).applicationStarted(component);
326                } catch (RuntimeException e) {
327                    log.error(String.format("Component %s notification of application started failed.",
328                            component.getName()), e);
329                    state = START_FAILURE;
330                }
331            }
332        }
333    }
334
335    public synchronized void activate() {
336        if (state != RESOLVED) {
337            return;
338        }
339
340        component = createComponentInstance();
341
342        state = ACTIVATING;
343        manager.sendEvent(new ComponentEvent(ComponentEvent.ACTIVATING_COMPONENT, this));
344
345        // activate component
346        component.activate();
347        log.info("Component activated: " + name);
348
349        state = ACTIVATED;
350        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_ACTIVATED, this));
351
352        // register contributed extensions if any
353        if (extensions != null) {
354            checkExtensions();
355            for (Extension xt : extensions) {
356                xt.setComponent(component);
357                try {
358                    manager.registerExtension(xt);
359                } catch (RuntimeException e) {
360                    String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: "
361                            + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName();
362                    log.error(msg, e);
363                    msg += " (" + e.toString() + ')';
364                    Framework.getRuntime().getWarnings().add(msg);
365                    Framework.handleDevError(e);
366                }
367            }
368        }
369
370        // register pending extensions if any
371        List<ComponentName> names = new ArrayList<>(1 + aliases.size());
372        names.add(name);
373        names.addAll(aliases);
374        for (ComponentName n : names) {
375            Set<Extension> pendingExt = manager.pendingExtensions.remove(n);
376            if (pendingExt == null) {
377                continue;
378            }
379            for (Extension xt : pendingExt) {
380                ComponentManagerImpl.loadContributions(this, xt);
381                try {
382                    component.registerExtension(xt);
383                } catch (RuntimeException e) {
384                    String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: "
385                            + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName();
386                    log.error(msg, e);
387                    msg += " (" + e.toString() + ')';
388                    Framework.getRuntime().getWarnings().add(msg);
389                    Framework.handleDevError(e);
390                }
391            }
392        }
393    }
394
395    public synchronized void deactivate() {
396        if (state != ACTIVATED && state != START_FAILURE) {
397            return;
398        }
399
400        state = DEACTIVATING;
401        manager.sendEvent(new ComponentEvent(ComponentEvent.DEACTIVATING_COMPONENT, this));
402
403        // unregister contributed extensions if any
404        if (extensions != null) {
405            for (Extension xt : extensions) {
406                try {
407                    manager.unregisterExtension(xt);
408                } catch (RuntimeException e) {
409                    log.error(
410                            "Failed to unregister extension. Contributor: " + xt.getComponent() + " to "
411                                    + xt.getTargetComponent() + "; xpoint: " + xt.getExtensionPoint(), e);
412                    Framework.handleDevError(e);
413                }
414            }
415        }
416
417        component.deactivate();
418
419        component = null;
420
421        state = RESOLVED;
422        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_DEACTIVATED, this));
423    }
424
425    public synchronized void resolve() {
426        if (state != REGISTERED) {
427            return;
428        }
429
430        // register services
431        manager.registerServices(this);
432
433        state = RESOLVED;
434        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_RESOLVED, this));
435        // TODO lazy activation
436        activate();
437    }
438
439    public synchronized void unresolve() {
440        if (state == REGISTERED || state == UNREGISTERED) {
441            return;
442        }
443
444        // un-register services
445        manager.unregisterServices(this);
446
447        if (state == ACTIVATED || state == START_FAILURE) {
448            deactivate();
449        }
450        state = REGISTERED;
451        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNRESOLVED, this));
452    }
453
454    @Override
455    // not synchronized, intermediate states from other synchronized methods
456    // are not a problem
457    public boolean isActivated() {
458        return state == ACTIVATED;
459    }
460
461    @Override
462    // not synchronized, intermediate states from other synchronized methods
463    // are not a problem
464    public boolean isResolved() {
465        return state == RESOLVED;
466    }
467
468    @Override
469    public String[] getProvidedServiceNames() {
470        if (serviceDescriptor != null) {
471            return serviceDescriptor.services;
472        }
473        return null;
474    }
475
476    public ServiceDescriptor getServiceDescriptor() {
477        return serviceDescriptor;
478    }
479
480    @Override
481    public String getImplementation() {
482        return implementation;
483    }
484
485    public void checkExtensions() {
486        // HashSet<String> targets = new HashSet<String>();
487        for (ExtensionImpl xt : extensions) {
488            if (xt.target == null) {
489                Framework.getRuntime().getWarnings().add(
490                        "Bad extension declaration (no target attribute specified). Component: " + getName());
491                continue;
492            }
493            // TODO do nothing for now -> fix the faulty components and then
494            // activate these warnings
495            // String key = xt.target.getName()+"#"+xt.getExtensionPoint();
496            // if (targets.contains(key)) { // multiple extensions to same
497            // target point declared in same component
498            // String message =
499            // "Component "+getName()+" contains multiple extensions to "+key;
500            // Framework.getRuntime().getWarnings().add(message);
501            // //TODO: un-comment the following line if you want to treat this
502            // as a dev. error
503            // //Framework.handleDevError(new Error(message));
504            // } else {
505            // targets.add(key);
506            // }
507        }
508    }
509
510    @Override
511    public URL getXmlFileUrl() {
512        return xmlFileUrl;
513    }
514
515    @Override
516    public boolean equals(Object obj) {
517        if (obj == this) {
518            return true;
519        }
520        if (obj instanceof RegistrationInfo) {
521            return name.equals(((RegistrationInfo) obj).getName());
522        }
523        return false;
524    }
525
526    @Override
527    public int hashCode() {
528        return name.hashCode();
529    }
530
531}