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