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:[email protected]">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}