View Javadoc
1   package org.andromda.core.namespace;
2   
3   import java.io.InputStream;
4   import java.net.URL;
5   import java.text.Collator;
6   import java.util.ArrayList;
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.Comparator;
10  import java.util.HashMap;
11  import java.util.LinkedHashMap;
12  import java.util.List;
13  import java.util.Map;
14  import org.andromda.core.common.AndroMDALogger;
15  import org.andromda.core.common.ComponentContainer;
16  import org.andromda.core.common.Merger;
17  import org.andromda.core.common.ResourceFinder;
18  import org.andromda.core.common.ResourceUtils;
19  import org.andromda.core.common.XmlObjectFactory;
20  import org.andromda.core.configuration.Namespaces;
21  import org.andromda.core.profile.Profile;
22  import org.apache.commons.io.IOUtils;
23  import org.apache.commons.lang.StringUtils;
24  
25  /**
26   * The registry for namespace components. Namespace components are components
27   * that reside within a namespace and can be configured by a namespace.
28   *
29   * @author Chad Brandon
30   */
31  public class NamespaceComponents
32  {
33      /**
34       * The shared registry instance.
35       */
36      private static NamespaceComponents instance;
37  
38      /**
39       * Gets the shared instance of this registry.
40       *
41       * @return the shared registry instance.
42       */
43      public static final NamespaceComponents instance()
44      {
45          if (instance == null)
46          {
47              final XmlObjectFactory factory = XmlObjectFactory.getInstance(NamespaceComponents.class);
48              instance = (NamespaceComponents)factory.getObject(ResourceUtils.getResource(CONFIGURATION_URI));
49          }
50          return instance;
51      }
52  
53      /**
54       * The URI to the descriptor for this instance.
55       */
56      private static final String CONFIGURATION_URI = "META-INF/andromda/namespace-components.xml";
57  
58      /**
59       * This class should not be instantiated through this constructor, it is
60       * only here to allow construction by the {@link XmlObjectFactory}. The
61       * instance of this class should be retrieved through the call to
62       * {@link #instance()}.
63       */
64      public NamespaceComponents()
65      {
66      }
67  
68      /**
69       * Discovers all namespaces found on the classpath.
70       */
71      public void discover()
72      {
73          AndroMDALogger.info("- discovering namespaces -");
74  
75          final XmlObjectFactory registryFactory = XmlObjectFactory.getInstance(NamespaceRegistry.class);
76          final ComponentContainer container = ComponentContainer.instance();
77  
78          // - discover all registries and sort them by name
79          final Map<NamespaceRegistry, URL> registryMap = this.discoverAllRegistries();
80          final List<NamespaceRegistry> registries = new ArrayList<NamespaceRegistry>(registryMap.keySet());
81          Collections.sort(
82              registries,
83              new NamespaceRegistryComparator());
84          for (NamespaceRegistry registry : registries)
85          {
86              final URL resource = registryMap.get(registry);
87              final String registryName = registry.getName();
88  
89              // - only register if we haven't yet registered the namespace resource
90              if (!this.registeredNamespaceResources.contains(resource))
91              {
92                  final Namespaces namespaces = Namespaces.instance();
93                  final String namespace = registry.isShared() ? Namespaces.DEFAULT : registry.getName();
94  
95                  // - first merge on the namespace registry descriptor (if needed)
96                  final Merger merger = Merger.instance();
97                  boolean requiresMerge = merger.requiresMerge(namespace);
98                  if (requiresMerge)
99                  {
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 }