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}