001package org.andromda.core.namespace; 002 003import java.io.InputStream; 004import java.net.URL; 005import java.text.Collator; 006import java.util.ArrayList; 007import java.util.Collection; 008import java.util.Collections; 009import java.util.Comparator; 010import java.util.HashMap; 011import java.util.LinkedHashMap; 012import java.util.List; 013import java.util.Map; 014import org.andromda.core.common.AndroMDALogger; 015import org.andromda.core.common.ComponentContainer; 016import org.andromda.core.common.Merger; 017import org.andromda.core.common.ResourceFinder; 018import org.andromda.core.common.ResourceUtils; 019import org.andromda.core.common.XmlObjectFactory; 020import org.andromda.core.configuration.Namespaces; 021import org.andromda.core.profile.Profile; 022import org.apache.commons.io.IOUtils; 023import org.apache.commons.lang.StringUtils; 024 025/** 026 * The registry for namespace components. Namespace components are components 027 * that reside within a namespace and can be configured by a namespace. 028 * 029 * @author Chad Brandon 030 */ 031public class NamespaceComponents 032{ 033 /** 034 * The shared registry instance. 035 */ 036 private static NamespaceComponents instance; 037 038 /** 039 * Gets the shared instance of this registry. 040 * 041 * @return the shared registry instance. 042 */ 043 public static final NamespaceComponents instance() 044 { 045 if (instance == null) 046 { 047 final XmlObjectFactory factory = XmlObjectFactory.getInstance(NamespaceComponents.class); 048 instance = (NamespaceComponents)factory.getObject(ResourceUtils.getResource(CONFIGURATION_URI)); 049 } 050 return instance; 051 } 052 053 /** 054 * The URI to the descriptor for this instance. 055 */ 056 private static final String CONFIGURATION_URI = "META-INF/andromda/namespace-components.xml"; 057 058 /** 059 * This class should not be instantiated through this constructor, it is 060 * only here to allow construction by the {@link XmlObjectFactory}. The 061 * instance of this class should be retrieved through the call to 062 * {@link #instance()}. 063 */ 064 public NamespaceComponents() 065 { 066 } 067 068 /** 069 * Discovers all namespaces found on the classpath. 070 */ 071 public void discover() 072 { 073 AndroMDALogger.info("- discovering namespaces -"); 074 075 final XmlObjectFactory registryFactory = XmlObjectFactory.getInstance(NamespaceRegistry.class); 076 final ComponentContainer container = ComponentContainer.instance(); 077 078 // - discover all registries and sort them by name 079 final Map<NamespaceRegistry, URL> registryMap = this.discoverAllRegistries(); 080 final List<NamespaceRegistry> registries = new ArrayList<NamespaceRegistry>(registryMap.keySet()); 081 Collections.sort( 082 registries, 083 new NamespaceRegistryComparator()); 084 for (NamespaceRegistry registry : registries) 085 { 086 final URL resource = registryMap.get(registry); 087 final String registryName = registry.getName(); 088 089 // - only register if we haven't yet registered the namespace resource 090 if (!this.registeredNamespaceResources.contains(resource)) 091 { 092 final Namespaces namespaces = Namespaces.instance(); 093 final String namespace = registry.isShared() ? Namespaces.DEFAULT : registry.getName(); 094 095 // - first merge on the namespace registry descriptor (if needed) 096 final Merger merger = Merger.instance(); 097 boolean requiresMerge = merger.requiresMerge(namespace); 098 if (requiresMerge) 099 { 100 registry = 101 (NamespaceRegistry) registryFactory.getObject( 102 merger.getMergedString( 103 ResourceUtils.getContents(resource), 104 namespace), resource); 105 } 106 107 // - add the resource root 108 registry.addResourceRoot(this.getNamespaceResourceRoot(resource)); 109 110 // - only log the fact we've found the namespace registry, if we haven't done it yet 111 if (!this.registeredRegistries.contains(registryName)) 112 { 113 AndroMDALogger.info("found namespace --> '" + registryName + '\''); 114 this.registeredRegistries.add(registryName); 115 } 116 117 final NamespaceRegistry existingRegistry = namespaces.getRegistry(registryName); 118 if (existingRegistry != null) 119 { 120 // - if we already have an existing registry with the same name, copy 121 // over any resources. 122 registry.copy(existingRegistry); 123 } 124 125 // - add the registry to the namespaces instance 126 namespaces.addRegistry(registry); 127 final String[] components = registry.getRegisteredComponents(); 128 for (final String componentName : components) 129 { 130 final Component component = this.getComponent(componentName); 131 if (component == null) 132 { 133 throw new NamespaceComponentsException('\'' + componentName + 134 "' is not a valid namespace component"); 135 } 136 137 // - add any paths defined within the registry 138 component.addPaths(registry.getPaths(component.getName())); 139 if (!container.isRegisteredByNamespace( 140 registryName, 141 component.getType())) 142 { 143 AndroMDALogger.info(" + registering component '" + componentName + '\''); 144 final XmlObjectFactory componentFactory = XmlObjectFactory.getInstance(component.getType()); 145 final URL componentResource = 146 this.getNamespaceResource( 147 registry.getResourceRoots(), 148 component.getPaths()); 149 if (componentResource == null) 150 { 151 throw new NamespaceComponentsException('\'' + componentName + 152 "' is not a valid component within namespace '" + namespace + "' (the " + 153 componentName + "'s descriptor can not be found)"); 154 } 155 NamespaceComponent namespaceComponent = 156 (NamespaceComponent) componentFactory.getObject(componentResource); 157 158 // - now perform a merge of the descriptor (if we require one) 159 if (requiresMerge) 160 { 161 namespaceComponent = 162 (NamespaceComponent) componentFactory.getObject( 163 merger.getMergedString( 164 ResourceUtils.getContents(componentResource), 165 namespace)); 166 } 167 168 namespaceComponent.setNamespace(registryName); 169 namespaceComponent.setResource(componentResource); 170 container.registerComponentByNamespace( 171 registryName, 172 component.getType(), 173 namespaceComponent); 174 } 175 } 176 } 177 this.registeredNamespaceResources.add(resource); 178 } 179 180 // - initialize the profile 181 Profile.instance().initialize(); 182 } 183 184 /** 185 * Discovers all registries and loads them into a map with the registry as the key 186 * and the resource that configured the registry as the value. 187 * 188 * @return the registries in a Map 189 */ 190 private Map<NamespaceRegistry, URL> discoverAllRegistries() 191 { 192 final Map<NamespaceRegistry, URL> registries = new HashMap<NamespaceRegistry, URL>(); 193 final URL[] resources = ResourceFinder.findResources(this.getPath()); 194 final XmlObjectFactory registryFactory = XmlObjectFactory.getInstance(NamespaceRegistry.class); 195 if (resources != null) 196 { 197 for (final URL resource : resources) 198 { 199 final NamespaceRegistry registry = (NamespaceRegistry) registryFactory.getObject(resource); 200 registries.put( 201 registry, 202 resource); 203 } 204 } 205 return registries; 206 } 207 208 /** 209 * Keeps track of the namespaces resources that have been already registered. 210 */ 211 private Collection<URL> registeredNamespaceResources = new ArrayList<URL>(); 212 213 /** 214 * Keeps track of the namespace registries that have been registered. 215 */ 216 private Collection<String> registeredRegistries = new ArrayList<String>(); 217 218 /** 219 * Attempts to retrieve a resource relative to the given 220 * <code>resourceRoots</code> by computing the complete path from the given 221 * relative <code>path</code>. Retrieves the first valid one found. 222 * 223 * @param resourceRoots the resourceRoots from which to perform search. 224 * @param paths the relative paths to check. 225 * @return the resource found or null if invalid. 226 */ 227 private URL getNamespaceResource( 228 final URL[] resourceRoots, 229 final String[] paths) 230 { 231 URL namespaceResource = null; 232 if (resourceRoots != null) 233 { 234 for (final URL resource : resourceRoots) 235 { 236 for (final String path : paths) 237 { 238 InputStream stream = null; 239 try 240 { 241 namespaceResource = new URL(ResourceUtils.normalizePath(resource + path)); 242 stream = namespaceResource.openStream(); 243 } 244 catch (final Throwable throwable) 245 { 246 namespaceResource = null; 247 } 248 finally 249 { 250 IOUtils.closeQuietly(stream); 251 } 252 253 // - break if we've found one 254 if (namespaceResource != null) 255 { 256 break; 257 } 258 } 259 260 // - break if we've found one 261 if (namespaceResource != null) 262 { 263 break; 264 } 265 } 266 } 267 return namespaceResource; 268 } 269 270 /** 271 * Attempts to retrieve the resource root of the namespace; that is the 272 * directory (whether it be a regular directory or achive root) which this 273 * namespace spans. 274 * 275 * @param resource the resource from which to retrieve the root. 276 * @return the namespace root, or null if could not be found. 277 */ 278 private URL getNamespaceResourceRoot(final URL resource) 279 { 280 final String resourcePath = resource != null ? resource.toString().replace( 281 '\\', 282 '/') : null; 283 return ResourceUtils.toURL(StringUtils.replace( 284 resourcePath, 285 this.path, 286 "")); 287 } 288 289 /** 290 * The path to search for the namespace descriptor. 291 */ 292 private String path; 293 294 /** 295 * Gets the path to the namespace registry descriptor. 296 * 297 * @return The path to a namespace registry descriptor. 298 */ 299 public String getPath() 300 { 301 return this.path; 302 } 303 304 /** 305 * Sets the path to the namespace registry descriptor. 306 * 307 * @param path The path to a namespace registry descriptor. 308 */ 309 public void setPath(String path) 310 { 311 this.path = path; 312 } 313 314 /** 315 * Stores the actual component definitions for this namespace registry. 316 */ 317 private final Map<String, Component> components = new LinkedHashMap<String, Component>(); 318 319 /** 320 * Adds a new component to this namespace registry. 321 * 322 * @param component the component to add to this namespace registry. 323 */ 324 public void addComponent(final Component component) 325 { 326 if (component != null) 327 { 328 this.components.put( 329 component.getName(), 330 component); 331 } 332 } 333 334 /** 335 * Shuts down this component registry and reclaims any resources used. 336 */ 337 public void shutdown() 338 { 339 this.components.clear(); 340 this.registeredNamespaceResources.clear(); 341 this.registeredRegistries.clear(); 342 NamespaceComponents.instance = null; 343 } 344 345 /** 346 * Retrieves a component by name (or returns null if one can not be found). 347 * 348 * @param name the name of the component to retrieve. 349 * @return the component instance or null. 350 */ 351 private Component getComponent(final String name) 352 { 353 return this.components.get(name); 354 } 355 356 /** 357 * Used to sort namespace registries by name. 358 */ 359 private static final class NamespaceRegistryComparator 360 implements Comparator<NamespaceRegistry> 361 { 362 private final Collator collator = Collator.getInstance(); 363 364 NamespaceRegistryComparator() 365 { 366 collator.setStrength(Collator.PRIMARY); 367 } 368 369 public int compare( 370 final NamespaceRegistry objectA, 371 final NamespaceRegistry objectB) 372 { 373 return collator.compare( 374 objectA.getName(), 375 objectB.getName()); 376 } 377 } 378}