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