001/*
002 * (C) Copyright 2006-2017 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 *     Bogdan Stefanescu
018 *     Florent Guillaume
019 */
020package org.nuxeo.runtime.model.impl;
021
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.LinkedHashMap;
027import java.util.Map;
028import java.util.Set;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.runtime.model.ComponentName;
033import org.nuxeo.runtime.model.RegistrationInfo;
034
035/**
036 * This class is synchronized to safely update and access the different maps managed by the registry
037 *
038 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
039 */
040public class ComponentRegistry {
041
042    private final Log log = LogFactory.getLog(ComponentRegistry.class);
043
044    /**
045     * All registered components including unresolved ones. You can check the state of a component for getting the
046     * unresolved ones.
047     */
048    protected Map<ComponentName, RegistrationInfoImpl> components;
049
050    /**
051     * The list of resolved components. We need to use a linked hash map preserve the resolve order. We don't use a
052     * simple list to optimize removal by name (used by unregister operations).
053     *
054     * @since 9.2
055     */
056    protected LinkedHashMap<ComponentName, RegistrationInfoImpl> resolved;
057
058    /** Map of aliased name to canonical name. */
059    protected Map<ComponentName, ComponentName> aliases;
060
061    /**
062     * Maps a component name to a set of component names that are depending on that component. Values are always
063     * unaliased.
064     */
065    protected MappedSet requirements;
066
067    /**
068     * Map pending components to the set of unresolved components they are waiting for. Key is always unaliased.
069     */
070    protected MappedSet pendings;
071
072    /**
073     * Map deployment source ids to component names This was previously managed by DefaultRuntimeContext - but is no
074     * more usable in the original form. This map is only useful for unregister by location - which is used by some
075     * tests. Remove this if the unregister API will be removed.
076     *
077     * @since 9.2
078     */
079    protected Map<String, ComponentName> deployedFiles;
080
081    public ComponentRegistry() {
082        components = new HashMap<>();
083        aliases = new HashMap<>();
084        requirements = new MappedSet();
085        pendings = new MappedSet();
086        resolved = new LinkedHashMap<>();
087        deployedFiles = new HashMap<>();
088    }
089
090    public ComponentRegistry(ComponentRegistry reg) {
091        components = new HashMap<>(reg.components);
092        aliases = new HashMap<>(reg.aliases);
093        requirements = new MappedSet(reg.requirements);
094        pendings = new MappedSet(reg.pendings);
095        resolved = new LinkedHashMap<>(reg.resolved);
096        deployedFiles = new HashMap<>(reg.deployedFiles);
097    }
098
099    public synchronized void destroy() {
100        components = null;
101        aliases = null;
102        requirements = null;
103        pendings = null;
104        deployedFiles = null;
105    }
106
107    public synchronized final boolean isResolved(ComponentName name) {
108        RegistrationInfo ri = components.get(unaliased(name));
109        if (ri == null) {
110            return false;
111        }
112        return ri.getState() > RegistrationInfo.REGISTERED;
113    }
114
115    /**
116     * @return true if the component was resolved, false if the component is pending
117     */
118    public synchronized boolean addComponent(RegistrationInfoImpl ri) {
119        ComponentName name = ri.getName();
120        Set<ComponentName> al = ri.getAliases();
121        String aliasInfo = al.isEmpty() ? "" : ", aliases=" + al;
122        log.info("Registering component: " + name + aliasInfo);
123        ri.register();
124        // map the source id with the component name - see ComponentManager.unregisterByLocation
125        if (ri.sourceId != null) {
126            deployedFiles.put(ri.sourceId, ri.getName());
127        }
128        components.put(name, ri);
129        for (ComponentName n : al) {
130            aliases.put(n, name);
131        }
132        boolean hasUnresolvedDependencies = computePendings(ri);
133        if (!hasUnresolvedDependencies) {
134            resolveComponent(ri);
135            return true;
136        }
137        return false;
138    }
139
140    public synchronized RegistrationInfoImpl removeComponent(ComponentName name) {
141        RegistrationInfoImpl ri = components.remove(name);
142        if (ri != null) {
143            try {
144                unresolveComponent(ri);
145            } finally {
146                ri.unregister();
147            }
148        }
149        return ri;
150    }
151
152    /**
153     * @return an unmodifiable map of resolved registration info by component name, sorted by {@link LinkedHashMap}.
154     * @since 9.2
155     */
156    public synchronized Map<ComponentName, RegistrationInfoImpl> getResolvedMap() {
157        return Collections.unmodifiableMap(resolved);
158    }
159
160    /**
161     * @return an unmodifiable collection of resolved registration infos, sorted by {@link LinkedHashMap}
162     * @since 9.2
163     */
164    public synchronized Collection<RegistrationInfoImpl> getResolvedRegistrationInfo() {
165        return Collections.unmodifiableCollection(resolved.values());
166    }
167
168    /**
169     * @return an unmodifiable collection of resolved component names, sorted by {@link LinkedHashMap}
170     * @since 9.2
171     */
172    public synchronized Collection<ComponentName> getResolvedNames() {
173        return Collections.unmodifiableCollection(resolved.keySet());
174    }
175
176    /**
177     * @return an unmodifiable collection of missing dependencies
178     * @since 9.2
179     */
180    public synchronized Set<ComponentName> getMissingDependencies(ComponentName name) {
181        return Collections.unmodifiableSet(pendings.get(name));
182    }
183
184    /**
185     * Get the registration info for the given component name or null if none was registered.
186     */
187    public synchronized RegistrationInfoImpl getComponent(ComponentName name) {
188        return components.get(unaliased(name));
189    }
190
191    /**
192     * Check if the component is already registered against this registry
193     */
194    public synchronized boolean contains(ComponentName name) {
195        return components.containsKey(unaliased(name));
196    }
197
198    /**
199     * Get the registered components count
200     */
201    public synchronized int size() {
202        return components.size();
203    }
204
205    /**
206     * @return an unmodifiable collection of registered components
207     */
208    public synchronized Collection<RegistrationInfo> getComponents() {
209        return Collections.unmodifiableCollection(components.values());
210    }
211
212    /**
213     * Get a copy of the registered components as an array.
214     */
215    public synchronized RegistrationInfoImpl[] getComponentsArray() {
216        return components.values().toArray(new RegistrationInfoImpl[components.size()]);
217    }
218
219    /**
220     * @return an unmodifiable map of pending components
221     */
222    public synchronized Map<ComponentName, Set<ComponentName>> getPendingComponents() {
223        return Collections.unmodifiableMap(pendings.map);
224    }
225
226    protected ComponentName unaliased(ComponentName name) {
227        ComponentName alias = aliases.get(name);
228        return alias == null ? name : alias;
229    }
230
231    /**
232     * Fill the pending map with all unresolved dependencies of the given component. Returns false if no unresolved
233     * dependencies are found, otherwise returns true.
234     */
235    protected final boolean computePendings(RegistrationInfo ri) {
236        Set<ComponentName> set = ri.getRequiredComponents();
237        if (set == null || set.isEmpty()) {
238            return false;
239        }
240        boolean hasUnresolvedDependencies = false;
241        // fill the requirements and pending map
242        for (ComponentName name : set) {
243            if (!isResolved(name)) {
244                pendings.put(ri.getName(), name);
245                hasUnresolvedDependencies = true;
246            }
247            requirements.put(name, ri.getName());
248        }
249        return hasUnresolvedDependencies;
250    }
251
252    protected void resolveComponent(RegistrationInfoImpl ri) {
253        ComponentName riName = ri.getName();
254        Set<ComponentName> names = new HashSet<>();
255        names.add(riName);
256        names.addAll(ri.getAliases());
257
258        ri.resolve();
259        resolved.put(ri.getName(), ri); // track resolved components
260
261        // try to resolve pending components that are waiting the newly
262        // resolved component
263        Set<ComponentName> dependsOnMe = new HashSet<>();
264        for (ComponentName n : names) {
265            Set<ComponentName> reqs = requirements.get(n);
266            if (reqs != null) {
267                dependsOnMe.addAll(reqs); // unaliased
268            }
269        }
270        if (dependsOnMe == null || dependsOnMe.isEmpty()) {
271            return;
272        }
273        for (ComponentName name : dependsOnMe) { // unaliased
274            for (ComponentName n : names) {
275                pendings.remove(name, n);
276            }
277            Set<ComponentName> set = pendings.get(name);
278            if (set == null || set.isEmpty()) {
279                RegistrationInfoImpl waitingRi = components.get(name);
280                resolveComponent(waitingRi);
281            }
282        }
283    }
284
285    protected void unresolveComponent(RegistrationInfoImpl ri) {
286        Set<ComponentName> reqs = ri.getRequiredComponents();
287        ComponentName name = ri.getName();
288        ri.unresolve();
289        resolved.remove(name);
290        pendings.remove(name);
291        if (reqs != null) {
292            for (ComponentName req : reqs) {
293                requirements.remove(req, name);
294            }
295        }
296        Set<ComponentName> set = requirements.get(name); // unaliased
297        if (set != null && !set.isEmpty()) {
298            for (ComponentName dep : set.toArray(new ComponentName[set.size()])) {
299                RegistrationInfoImpl depRi = components.get(dep);
300                if (depRi != null) {
301                    unresolveComponent(depRi);
302                }
303            }
304        }
305    }
306
307    protected static class MappedSet {
308
309        protected Map<ComponentName, Set<ComponentName>> map;
310
311        public MappedSet() {
312            map = new HashMap<>();
313        }
314
315        /**
316         * Create a clone of a mapped set (set values are cloned too)
317         */
318        public MappedSet(MappedSet mset) {
319            this();
320            for (Map.Entry<ComponentName, Set<ComponentName>> entry : mset.map.entrySet()) {
321                ComponentName name = entry.getKey();
322                Set<ComponentName> set = entry.getValue();
323                Set<ComponentName> newSet = new HashSet<>(set);
324                map.put(name, newSet);
325            }
326        }
327
328        public Set<ComponentName> get(ComponentName name) {
329            return map.get(name);
330        }
331
332        public Set<ComponentName> put(ComponentName key, ComponentName value) {
333            Set<ComponentName> set = map.get(key);
334            if (set == null) {
335                set = new HashSet<>();
336                map.put(key, set);
337            }
338            set.add(value);
339            return set;
340        }
341
342        public Set<ComponentName> remove(ComponentName key) {
343            return map.remove(key);
344        }
345
346        public Set<ComponentName> remove(ComponentName key, ComponentName value) {
347            Set<ComponentName> set = map.get(key);
348            if (set != null) {
349                set.remove(value);
350                if (set.isEmpty()) {
351                    map.remove(key);
352                }
353            }
354            return set;
355        }
356
357        public boolean isEmpty() {
358            return map.isEmpty();
359        }
360
361        public int size() {
362            return map.size();
363        }
364
365        public void clear() {
366            map.clear();
367        }
368    }
369}