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