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.logging.log4j.LogManager;
031import org.apache.logging.log4j.Logger;
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 Logger log = LogManager.getLogger(ComponentManager.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            componentEvent = ComponentEvent.ACTIVATING_COMPONENT;
317            break;
318        case STARTING:
319            componentEvent = ComponentEvent.STARTING_COMPONENT;
320            break;
321        case STARTED:
322            componentEvent = ComponentEvent.COMPONENT_STARTED;
323            break;
324        case START_FAILURE:
325            break;
326        case STOPPING:
327            componentEvent = ComponentEvent.STOPPING_COMPONENT;
328            break;
329        case DEACTIVATING:
330            componentEvent = ComponentEvent.DEACTIVATING_COMPONENT;
331            break;
332        }
333        if (componentEvent > -1) {
334            manager.sendEvent(new ComponentEvent(componentEvent, this));
335        }
336    }
337
338    protected ComponentInstance createComponentInstance() {
339        try {
340            return new ComponentInstanceImpl(this);
341        } catch (RuntimeException e) {
342            String msg = "Failed to instantiate component: " + implementation;
343            log.error(msg, e);
344            msg += " (" + e.toString() + ')';
345            Framework.getRuntime().getMessageHandler().addError(msg);
346            throw e;
347        }
348    }
349
350    /**
351     * @deprecated since 9.2 seems unused
352     */
353    @Deprecated
354    public synchronized void restart() {
355        deactivate();
356        activate();
357    }
358
359    @Override
360    public int getApplicationStartedOrder() {
361        if (component == null) {
362            return 0;
363        }
364        Object ci = component.getInstance();
365        if (!(ci instanceof Component)) {
366            return 0;
367        }
368        return ((Component) ci).getApplicationStartedOrder();
369    }
370
371    public synchronized void start() {
372        if (state != ACTIVATED) {
373            return;
374        }
375        state = STARTING;
376        manager.sendEvent(new ComponentEvent(ComponentEvent.STARTING_COMPONENT, this));
377        try {
378            if (component != null) {
379                Object ci = component.getInstance();
380                if (ci instanceof Component) {
381                    ((Component) ci).start(component);
382                }
383            }
384            log.debug("Component started: {}", name);
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",
389                    component == null ? null : component.getName(), 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        log.debug("Component stopped: {}", name);
413        state = ACTIVATED;
414        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_STOPPED, this));
415    }
416
417    public synchronized void activate() {
418        if (state != RESOLVED) {
419            return;
420        }
421
422        component = createComponentInstance();
423
424        state = ACTIVATING;
425        manager.sendEvent(new ComponentEvent(ComponentEvent.ACTIVATING_COMPONENT, this));
426
427        // activate component
428        component.activate();
429        log.debug("Component activated: {}", name);
430
431        state = ACTIVATED;
432        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_ACTIVATED, this));
433
434        // register contributed extensions if any
435        if (extensions != null) {
436            checkExtensions();
437            for (Extension xt : extensions) {
438                xt.setComponent(component);
439                try {
440                    manager.registerExtension(xt);
441                } catch (RuntimeException e) {
442                    String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: "
443                            + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName();
444                    log.error(msg, e);
445                    msg += " (" + e.toString() + ')';
446                    Framework.getRuntime().getMessageHandler().addError(msg);
447                }
448            }
449        }
450
451        // register pending extensions if any
452        List<ComponentName> names = new ArrayList<>(1 + aliases.size());
453        names.add(name);
454        names.addAll(aliases);
455        for (ComponentName n : names) {
456            Set<Extension> pendingExt = manager.pendingExtensions.remove(n);
457            if (pendingExt == null) {
458                continue;
459            }
460            for (Extension xt : pendingExt) {
461                ComponentManagerImpl.loadContributions(this, xt);
462                try {
463                    component.registerExtension(xt);
464                } catch (RuntimeException e) {
465                    String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: "
466                            + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName();
467                    log.error(msg, e);
468                    msg += " (" + e.toString() + ')';
469                    Framework.getRuntime().getMessageHandler().addError(msg);
470                }
471            }
472        }
473    }
474
475    public synchronized void deactivate() {
476        deactivate(true);
477    }
478
479    /**
480     * Deactivate the component. If mustUnregisterExtensions is false then the call was made by the manager because all
481     * components are stopped (and deactivated) so the extension unregister should not be done (this will speedup the
482     * stop and also fix broken component which are not correctly defining dependencies.)
483     *
484     * @since 9.2
485     */
486    public synchronized void deactivate(boolean mustUnregisterExtensions) {
487        if (state != ACTIVATED && state != START_FAILURE) {
488            return;
489        }
490
491        state = DEACTIVATING;
492        manager.sendEvent(new ComponentEvent(ComponentEvent.DEACTIVATING_COMPONENT, this));
493
494        if (mustUnregisterExtensions) {
495            // unregister contributed extensions if any
496            if (extensions != null) {
497                for (Extension xt : extensions) {
498                    try {
499                        manager.unregisterExtension(xt);
500                    } catch (RuntimeException e) {
501                        String message = "Failed to unregister extension. Contributor: " + xt.getComponent() + " to "
502                                + xt.getTargetComponent() + "; xpoint: " + xt.getExtensionPoint();
503                        log.error(message, e);
504                        Framework.getRuntime().getMessageHandler().addError(message);
505                    }
506                }
507            }
508        }
509
510        component.deactivate();
511        log.debug("Component deactivated: {}", name);
512
513        component = null;
514
515        state = RESOLVED;
516        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_DEACTIVATED, this));
517    }
518
519    public synchronized void resolve() {
520        if (state != REGISTERED) {
521            return;
522        }
523
524        // register services
525        manager.registerServices(this);
526
527        // components are no more automatically activated when resolved. see ComponentManager#start()
528        state = RESOLVED;
529        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_RESOLVED, this));
530    }
531
532    public synchronized void unresolve() {
533        if (state == REGISTERED || state == UNREGISTERED) {
534            return;
535        }
536
537        // un-register services
538        manager.unregisterServices(this);
539
540        // components are no more automatically deactivated when unresolved. see ComponentManager#stop()
541        state = REGISTERED;
542        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNRESOLVED, this));
543    }
544
545    @Override
546    // not synchronized, intermediate states from other synchronized methods
547    // are not a problem
548    public boolean isActivated() {
549        return state == ACTIVATED;
550    }
551
552    @Override
553    // not synchronized, intermediate states from other synchronized methods
554    // are not a problem
555    public boolean isResolved() {
556        return state == RESOLVED;
557    }
558
559    @Override
560    public String[] getProvidedServiceNames() {
561        if (serviceDescriptor != null) {
562            return serviceDescriptor.services;
563        }
564        return null;
565    }
566
567    public ServiceDescriptor getServiceDescriptor() {
568        return serviceDescriptor;
569    }
570
571    @Override
572    public String getImplementation() {
573        return implementation;
574    }
575
576    public void checkExtensions() {
577        // HashSet<String> targets = new HashSet<String>();
578        for (ExtensionImpl xt : extensions) {
579            if (xt.target == null) {
580                Framework.getRuntime().getMessageHandler().addWarning(
581                        "Bad extension declaration (no target attribute specified). Component: " + getName());
582                continue;
583            }
584            // TODO do nothing for now -> fix the faulty components and then
585            // activate these warnings
586            // String key = xt.target.getName()+"#"+xt.getExtensionPoint();
587            // if (targets.contains(key)) { // multiple extensions to same
588            // target point declared in same component
589            // String message =
590            // "Component "+getName()+" contains multiple extensions to "+key;
591            // Framework.getRuntime().getMessageHandler().addWarning(message);
592            // //TODO: un-comment the following line if you want to treat this
593            // as a dev. error
594            // //Framework.handleDevError(new Error(message));
595            // } else {
596            // targets.add(key);
597            // }
598        }
599    }
600
601    @Override
602    public URL getXmlFileUrl() {
603        return xmlFileUrl;
604    }
605
606    @Override
607    public String getSourceId() {
608        return sourceId;
609    }
610
611    @Override
612    public boolean equals(Object obj) {
613        if (obj == this) {
614            return true;
615        }
616        if (obj instanceof RegistrationInfo) {
617            return name.equals(((RegistrationInfo) obj).getName());
618        }
619        return false;
620    }
621
622    @Override
623    public int hashCode() {
624        return name.hashCode();
625    }
626
627    /**
628     * Use former way for {@link RegistrationInfoImpl}.
629     *
630     * @since 9.3
631     */
632    @Override
633    public boolean useFormerLifecycleManagement() {
634        return true;
635    }
636
637}