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().getErrors().add(msg);
304            throw e;
305        }
306    }
307
308    public synchronized void restart() {
309        deactivate();
310        activate();
311    }
312
313    @Override
314    public int getApplicationStartedOrder() {
315        if (component == null) {
316            return 0;
317        }
318        Object ci = component.getInstance();
319        if (!(ci instanceof Component)) {
320            return 0;
321        }
322        return ((Component) ci).getApplicationStartedOrder();
323    }
324
325    @Override
326    public void notifyApplicationStarted() {
327        if (component != null) {
328            Object ci = component.getInstance();
329            if (ci instanceof Component) {
330                try {
331                    ((Component) ci).applicationStarted(component);
332                } catch (RuntimeException e) {
333                    log.error(String.format("Component %s notification of application started failed: %s",
334                            component.getName(), e.getMessage()), e);
335                    state = START_FAILURE;
336                }
337            }
338        }
339    }
340
341    public synchronized void activate() {
342        if (state != RESOLVED) {
343            return;
344        }
345
346        component = createComponentInstance();
347
348        state = ACTIVATING;
349        manager.sendEvent(new ComponentEvent(ComponentEvent.ACTIVATING_COMPONENT, this));
350
351        // activate component
352        component.activate();
353        log.info("Component activated: " + name);
354
355        state = ACTIVATED;
356        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_ACTIVATED, this));
357
358        // register contributed extensions if any
359        if (extensions != null) {
360            checkExtensions();
361            for (Extension xt : extensions) {
362                xt.setComponent(component);
363                try {
364                    manager.registerExtension(xt);
365                } catch (RuntimeException e) {
366                    String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: "
367                            + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName();
368                    log.error(msg, e);
369                    msg += " (" + e.toString() + ')';
370                    Framework.getRuntime().getErrors().add(msg);
371                }
372            }
373        }
374
375        // register pending extensions if any
376        List<ComponentName> names = new ArrayList<>(1 + aliases.size());
377        names.add(name);
378        names.addAll(aliases);
379        for (ComponentName n : names) {
380            Set<Extension> pendingExt = manager.pendingExtensions.remove(n);
381            if (pendingExt == null) {
382                continue;
383            }
384            for (Extension xt : pendingExt) {
385                ComponentManagerImpl.loadContributions(this, xt);
386                try {
387                    component.registerExtension(xt);
388                } catch (RuntimeException e) {
389                    String msg = "Failed to register extension to: " + xt.getTargetComponent() + ", xpoint: "
390                            + xt.getExtensionPoint() + " in component: " + xt.getComponent().getName();
391                    log.error(msg, e);
392                    msg += " (" + e.toString() + ')';
393                    Framework.getRuntime().getErrors().add(msg);
394                }
395            }
396        }
397    }
398
399    public synchronized void deactivate() {
400        if (state != ACTIVATED && state != START_FAILURE) {
401            return;
402        }
403
404        state = DEACTIVATING;
405        manager.sendEvent(new ComponentEvent(ComponentEvent.DEACTIVATING_COMPONENT, this));
406
407        // unregister contributed extensions if any
408        if (extensions != null) {
409            for (Extension xt : extensions) {
410                try {
411                    manager.unregisterExtension(xt);
412                } catch (RuntimeException e) {
413                    String message = "Failed to unregister extension. Contributor: " + xt.getComponent() + " to "
414                            + xt.getTargetComponent() + "; xpoint: " + xt.getExtensionPoint();
415                    log.error(message, e);
416                    Framework.getRuntime().getErrors().add(message);
417                }
418            }
419        }
420
421        component.deactivate();
422
423        component = null;
424
425        state = RESOLVED;
426        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_DEACTIVATED, this));
427    }
428
429    public synchronized void resolve() {
430        if (state != REGISTERED) {
431            return;
432        }
433
434        // register services
435        manager.registerServices(this);
436
437        state = RESOLVED;
438        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_RESOLVED, this));
439        // TODO lazy activation
440        activate();
441    }
442
443    public synchronized void unresolve() {
444        if (state == REGISTERED || state == UNREGISTERED) {
445            return;
446        }
447
448        // un-register services
449        manager.unregisterServices(this);
450
451        if (state == ACTIVATED || state == START_FAILURE) {
452            deactivate();
453        }
454        state = REGISTERED;
455        manager.sendEvent(new ComponentEvent(ComponentEvent.COMPONENT_UNRESOLVED, this));
456    }
457
458    @Override
459    // not synchronized, intermediate states from other synchronized methods
460    // are not a problem
461    public boolean isActivated() {
462        return state == ACTIVATED;
463    }
464
465    @Override
466    // not synchronized, intermediate states from other synchronized methods
467    // are not a problem
468    public boolean isResolved() {
469        return state == RESOLVED;
470    }
471
472    @Override
473    public String[] getProvidedServiceNames() {
474        if (serviceDescriptor != null) {
475            return serviceDescriptor.services;
476        }
477        return null;
478    }
479
480    public ServiceDescriptor getServiceDescriptor() {
481        return serviceDescriptor;
482    }
483
484    @Override
485    public String getImplementation() {
486        return implementation;
487    }
488
489    public void checkExtensions() {
490        // HashSet<String> targets = new HashSet<String>();
491        for (ExtensionImpl xt : extensions) {
492            if (xt.target == null) {
493                Framework.getRuntime().getWarnings().add(
494                        "Bad extension declaration (no target attribute specified). Component: " + getName());
495                continue;
496            }
497            // TODO do nothing for now -> fix the faulty components and then
498            // activate these warnings
499            // String key = xt.target.getName()+"#"+xt.getExtensionPoint();
500            // if (targets.contains(key)) { // multiple extensions to same
501            // target point declared in same component
502            // String message =
503            // "Component "+getName()+" contains multiple extensions to "+key;
504            // Framework.getRuntime().getWarnings().add(message);
505            // //TODO: un-comment the following line if you want to treat this
506            // as a dev. error
507            // //Framework.handleDevError(new Error(message));
508            // } else {
509            // targets.add(key);
510            // }
511        }
512    }
513
514    @Override
515    public URL getXmlFileUrl() {
516        return xmlFileUrl;
517    }
518
519    @Override
520    public boolean equals(Object obj) {
521        if (obj == this) {
522            return true;
523        }
524        if (obj instanceof RegistrationInfo) {
525            return name.equals(((RegistrationInfo) obj).getName());
526        }
527        return false;
528    }
529
530    @Override
531    public int hashCode() {
532        return name.hashCode();
533    }
534
535}