001/*
002 * (C) Copyright 2006-2012 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Bogdan Stefanescu
016 *     Florent Guillaume
017 */
018package org.nuxeo.runtime.model.impl;
019
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.runtime.model.ComponentName;
029import org.nuxeo.runtime.model.RegistrationInfo;
030
031/**
032 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
033 */
034public class ComponentRegistry {
035
036    private final Log log = LogFactory.getLog(ComponentRegistry.class);
037
038    /**
039     * All registered components including unresolved ones. You can check the state of a component for getting the
040     * unresolved ones.
041     */
042    protected Map<ComponentName, RegistrationInfoImpl> components;
043
044    /** Map of aliased name to canonical name. */
045    protected Map<ComponentName, ComponentName> aliases;
046
047    /**
048     * Maps a component name to a set of component names that are depending on that component. Values are always
049     * unaliased.
050     */
051    protected MappedSet requirements;
052
053    /**
054     * Map pending components to the set of unresolved components they are waiting for. Key is always unaliased.
055     */
056    protected MappedSet pendings;
057
058    public ComponentRegistry() {
059        components = new HashMap<ComponentName, RegistrationInfoImpl>();
060        aliases = new HashMap<ComponentName, ComponentName>();
061        requirements = new MappedSet();
062        pendings = new MappedSet();
063    }
064
065    public void destroy() {
066        components = null;
067        aliases = null;
068        requirements = null;
069        pendings = null;
070    }
071
072    protected ComponentName unaliased(ComponentName name) {
073        ComponentName alias = aliases.get(name);
074        return alias == null ? name : alias;
075    }
076
077    public final boolean isResolved(ComponentName name) {
078        RegistrationInfo ri = components.get(unaliased(name));
079        if (ri == null) {
080            return false;
081        }
082        return ri.getState() > RegistrationInfo.REGISTERED;
083    }
084
085    /**
086     * Fill the pending map with all unresolved dependencies of the given component. Returns false if no unresolved
087     * dependencies are found, otherwise returns true.
088     *
089     * @param ri
090     * @return
091     */
092    protected final boolean computePendings(RegistrationInfo ri) {
093        Set<ComponentName> set = ri.getRequiredComponents();
094        if (set == null || set.isEmpty()) {
095            return false;
096        }
097        boolean hasUnresolvedDependencies = false;
098        // fill the requirements and pending map
099        for (ComponentName name : set) {
100            if (!isResolved(name)) {
101                pendings.put(ri.getName(), name);
102                hasUnresolvedDependencies = true;
103            }
104            requirements.put(name, ri.getName());
105        }
106        return hasUnresolvedDependencies;
107    }
108
109    /**
110     * @param ri
111     * @return true if the component was resolved, false if the component is pending
112     */
113    public boolean addComponent(RegistrationInfoImpl ri) {
114        ComponentName name = ri.getName();
115        Set<ComponentName> al = ri.getAliases();
116        String aliasInfo = al.isEmpty() ? "" : ", aliases=" + al;
117        log.info("Registering component: " + name + aliasInfo);
118        ri.register();
119        components.put(name, ri);
120        for (ComponentName n : al) {
121            aliases.put(n, name);
122        }
123        boolean hasUnresolvedDependencies = computePendings(ri);
124        if (!hasUnresolvedDependencies) {
125            resolveComponent(ri);
126            return true;
127        }
128        return false;
129    }
130
131    public RegistrationInfoImpl removeComponent(ComponentName name) {
132        RegistrationInfoImpl ri = components.remove(name);
133        if (ri != null) {
134            try {
135                unresolveComponent(ri);
136            } finally {
137                ri.unregister();
138            }
139        }
140        return ri;
141    }
142
143    public Set<ComponentName> getMissingDependencies(ComponentName name) {
144        return pendings.get(name);
145    }
146
147    public RegistrationInfoImpl getComponent(ComponentName name) {
148        return components.get(unaliased(name));
149    }
150
151    public boolean contains(ComponentName name) {
152        return components.containsKey(unaliased(name));
153    }
154
155    public int size() {
156        return components.size();
157    }
158
159    public Collection<RegistrationInfoImpl> getComponents() {
160        return components.values();
161    }
162
163    public RegistrationInfoImpl[] getComponentsArray() {
164        return components.values().toArray(new RegistrationInfoImpl[components.size()]);
165    }
166
167    public Map<ComponentName, Set<ComponentName>> getPendingComponents() {
168        return pendings.map;
169    }
170
171    protected void resolveComponent(RegistrationInfoImpl ri) {
172        ComponentName riName = ri.getName();
173        Set<ComponentName> names = new HashSet<ComponentName>();
174        names.add(riName);
175        names.addAll(ri.getAliases());
176
177        ri.resolve();
178        // try to resolve pending components that are waiting the newly
179        // resolved component
180        Set<ComponentName> dependsOnMe = new HashSet<ComponentName>();
181        for (ComponentName n : names) {
182            Set<ComponentName> reqs = requirements.get(n);
183            if (reqs != null) {
184                dependsOnMe.addAll(reqs); // unaliased
185            }
186        }
187        if (dependsOnMe == null || dependsOnMe.isEmpty()) {
188            return;
189        }
190        for (ComponentName name : dependsOnMe) { // unaliased
191            for (ComponentName n : names) {
192                pendings.remove(name, n);
193            }
194            Set<ComponentName> set = pendings.get(name);
195            if (set == null || set.isEmpty()) {
196                RegistrationInfoImpl waitingRi = components.get(name);
197                resolveComponent(waitingRi);
198            }
199        }
200    }
201
202    protected void unresolveComponent(RegistrationInfoImpl ri) {
203        Set<ComponentName> reqs = ri.getRequiredComponents();
204        ComponentName name = ri.getName();
205        ri.unresolve();
206        pendings.remove(name);
207        if (reqs != null) {
208            for (ComponentName req : reqs) {
209                requirements.remove(req, name);
210            }
211        }
212        Set<ComponentName> set = requirements.get(name); // unaliased
213        if (set != null && !set.isEmpty()) {
214            for (ComponentName dep : set.toArray(new ComponentName[set.size()])) {
215                RegistrationInfoImpl depRi = components.get(dep);
216                if (depRi != null) {
217                    unresolveComponent(depRi);
218                }
219            }
220        }
221    }
222
223    static class MappedSet {
224        protected Map<ComponentName, Set<ComponentName>> map;
225
226        public MappedSet() {
227            map = new HashMap<ComponentName, Set<ComponentName>>();
228        }
229
230        public Set<ComponentName> get(ComponentName name) {
231            return map.get(name);
232        }
233
234        public Set<ComponentName> put(ComponentName key, ComponentName value) {
235            Set<ComponentName> set = map.get(key);
236            if (set == null) {
237                set = new HashSet<ComponentName>();
238                map.put(key, set);
239            }
240            set.add(value);
241            return set;
242        }
243
244        public Set<ComponentName> remove(ComponentName key) {
245            return map.remove(key);
246        }
247
248        public Set<ComponentName> remove(ComponentName key, ComponentName value) {
249            Set<ComponentName> set = map.get(key);
250            if (set != null) {
251                set.remove(value);
252                if (set.isEmpty()) {
253                    map.remove(key);
254                }
255            }
256            return set;
257        }
258
259        public boolean isEmpty() {
260            return map.isEmpty();
261        }
262
263        public int size() {
264            return map.size();
265        }
266
267        public void clear() {
268            map.clear();
269        }
270    }
271}