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