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