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}