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, RegistrationInfo> 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, RegistrationInfo> 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(RegistrationInfo 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        if (ri.useFormerLifecycleManagement()) {
124            ((RegistrationInfoImpl) ri).register();
125        } else {
126            ri.setState(RegistrationInfo.REGISTERED);
127        }
128        // map the source id with the component name - see ComponentManager.unregisterByLocation
129        String sourceId = ri.getSourceId();
130        if (sourceId != null) {
131            deployedFiles.put(sourceId, ri.getName());
132        }
133        components.put(name, ri);
134        for (ComponentName n : al) {
135            aliases.put(n, name);
136        }
137        boolean hasUnresolvedDependencies = computePendings(ri);
138        if (!hasUnresolvedDependencies) {
139            resolveComponent(ri);
140            return true;
141        }
142        return false;
143    }
144
145    public synchronized RegistrationInfo removeComponent(ComponentName name) {
146        RegistrationInfo ri = components.remove(name);
147        if (ri != null) {
148            try {
149                unresolveComponent(ri);
150            } finally {
151                if (ri.useFormerLifecycleManagement()) {
152                    ((RegistrationInfoImpl) ri).unregister();
153                } else {
154                    ri.setState(RegistrationInfo.UNREGISTERED);
155                }
156            }
157        }
158        return ri;
159    }
160
161    /**
162     * @return an unmodifiable collection of resolved registration infos, sorted by {@link LinkedHashMap}
163     * @since 9.2
164     */
165    public synchronized Collection<RegistrationInfo> getResolvedRegistrationInfo() {
166        return Collections.unmodifiableCollection(resolved.values());
167    }
168
169    /**
170     * @return an unmodifiable collection of resolved component names, sorted by {@link LinkedHashMap}
171     * @since 9.2
172     */
173    public synchronized Collection<ComponentName> getResolvedNames() {
174        return Collections.unmodifiableCollection(resolved.keySet());
175    }
176
177    /**
178     * @return an unmodifiable collection of missing dependencies
179     * @since 9.2
180     */
181    public synchronized Set<ComponentName> getMissingDependencies(ComponentName name) {
182        return Collections.unmodifiableSet(pendings.get(name));
183    }
184
185    /**
186     * Get the registration info for the given component name or null if none was registered.
187     *
188     * @since 9.2
189     */
190    public synchronized RegistrationInfo getComponent(ComponentName name) {
191        return components.get(unaliased(name));
192    }
193
194    /**
195     * Check if the component is already registered against this registry
196     */
197    public synchronized boolean contains(ComponentName name) {
198        return components.containsKey(unaliased(name));
199    }
200
201    /**
202     * Get the registered components count
203     */
204    public synchronized int size() {
205        return components.size();
206    }
207
208    /**
209     * @return an unmodifiable collection of registered components
210     */
211    public synchronized Collection<RegistrationInfo> getComponents() {
212        return Collections.unmodifiableCollection(components.values());
213    }
214
215    /**
216     * Get a copy of the registered components as an array.
217     */
218    public synchronized RegistrationInfo[] getComponentsArray() {
219        return components.values().toArray(new RegistrationInfo[components.size()]);
220    }
221
222    /**
223     * @return an unmodifiable map of pending components
224     */
225    public synchronized Map<ComponentName, Set<ComponentName>> getPendingComponents() {
226        return Collections.unmodifiableMap(pendings.map);
227    }
228
229    protected ComponentName unaliased(ComponentName name) {
230        ComponentName alias = aliases.get(name);
231        return alias == null ? name : alias;
232    }
233
234    /**
235     * Fill the pending map with all unresolved dependencies of the given component. Returns false if no unresolved
236     * dependencies are found, otherwise returns true.
237     */
238    protected final boolean computePendings(RegistrationInfo ri) {
239        Set<ComponentName> set = ri.getRequiredComponents();
240        if (set == null || set.isEmpty()) {
241            return false;
242        }
243        boolean hasUnresolvedDependencies = false;
244        // fill the requirements and pending map
245        for (ComponentName name : set) {
246            if (!isResolved(name)) {
247                pendings.put(ri.getName(), name);
248                hasUnresolvedDependencies = true;
249            }
250            requirements.put(name, ri.getName());
251        }
252        return hasUnresolvedDependencies;
253    }
254
255    protected void resolveComponent(RegistrationInfo ri) {
256        ComponentName riName = ri.getName();
257        Set<ComponentName> names = new HashSet<>();
258        names.add(riName);
259        names.addAll(ri.getAliases());
260
261        if (ri.useFormerLifecycleManagement()) {
262            ((RegistrationInfoImpl) ri).resolve();
263        } else {
264            ri.setState(RegistrationInfo.RESOLVED);
265        }
266        resolved.put(ri.getName(), ri); // track resolved components
267
268        // try to resolve pending components that are waiting the newly
269        // resolved component
270        Set<ComponentName> dependsOnMe = new HashSet<>();
271        for (ComponentName n : names) {
272            Set<ComponentName> reqs = requirements.get(n);
273            if (reqs != null) {
274                dependsOnMe.addAll(reqs); // unaliased
275            }
276        }
277        if (dependsOnMe == null || dependsOnMe.isEmpty()) {
278            return;
279        }
280        for (ComponentName name : dependsOnMe) { // unaliased
281            for (ComponentName n : names) {
282                pendings.remove(name, n);
283            }
284            Set<ComponentName> set = pendings.get(name);
285            if (set == null || set.isEmpty()) {
286                RegistrationInfo waitingRi = components.get(name);
287                resolveComponent(waitingRi);
288            }
289        }
290    }
291
292    protected void unresolveComponent(RegistrationInfo ri) {
293        Set<ComponentName> reqs = ri.getRequiredComponents();
294        ComponentName name = ri.getName();
295        if (ri.useFormerLifecycleManagement()) {
296            ((RegistrationInfoImpl) ri).unresolve();
297        } else {
298            ri.setState(RegistrationInfo.REGISTERED);
299        }
300        resolved.remove(name);
301        pendings.remove(name);
302        if (reqs != null) {
303            for (ComponentName req : reqs) {
304                requirements.remove(req, name);
305            }
306        }
307        Set<ComponentName> set = requirements.get(name); // unaliased
308        if (set != null && !set.isEmpty()) {
309            for (ComponentName dep : set.toArray(new ComponentName[set.size()])) {
310                RegistrationInfo depRi = components.get(dep);
311                if (depRi != null) {
312                    unresolveComponent(depRi);
313                }
314            }
315        }
316    }
317
318    protected static class MappedSet {
319
320        protected Map<ComponentName, Set<ComponentName>> map;
321
322        public MappedSet() {
323            map = new HashMap<>();
324        }
325
326        /**
327         * Create a clone of a mapped set (set values are cloned too)
328         */
329        public MappedSet(MappedSet mset) {
330            this();
331            for (Map.Entry<ComponentName, Set<ComponentName>> entry : mset.map.entrySet()) {
332                ComponentName name = entry.getKey();
333                Set<ComponentName> set = entry.getValue();
334                Set<ComponentName> newSet = new HashSet<>(set);
335                map.put(name, newSet);
336            }
337        }
338
339        public Set<ComponentName> get(ComponentName name) {
340            return map.get(name);
341        }
342
343        public Set<ComponentName> put(ComponentName key, ComponentName value) {
344            Set<ComponentName> set = map.get(key);
345            if (set == null) {
346                set = new HashSet<>();
347                map.put(key, set);
348            }
349            set.add(value);
350            return set;
351        }
352
353        public Set<ComponentName> remove(ComponentName key) {
354            return map.remove(key);
355        }
356
357        public Set<ComponentName> remove(ComponentName key, ComponentName value) {
358            Set<ComponentName> set = map.get(key);
359            if (set != null) {
360                set.remove(value);
361                if (set.isEmpty()) {
362                    map.remove(key);
363                }
364            }
365            return set;
366        }
367
368        public boolean isEmpty() {
369            return map.isEmpty();
370        }
371
372        public int size() {
373            return map.size();
374        }
375
376        public void clear() {
377            map.clear();
378        }
379    }
380}