001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005//     http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.ioc;
014
015import java.io.Closeable;
016import java.io.IOException;
017import java.io.InputStream;
018import java.net.URL;
019import java.util.Collections;
020import java.util.Enumeration;
021import java.util.HashSet;
022import java.util.Set;
023import java.util.jar.Manifest;
024
025import org.apache.tapestry5.commons.util.ExceptionUtils;
026
027import static org.apache.tapestry5.ioc.IOCConstants.MODULE_BUILDER_MANIFEST_ENTRY_NAME;
028
029/**
030 * A collection of utility methods for a couple of different areas, including creating the initial {@link
031 * org.apache.tapestry5.ioc.Registry}.
032 */
033public final class IOCUtilities
034{
035    private IOCUtilities()
036    {
037    }
038
039    /**
040     * Construct a default Registry, including modules identifed via the Tapestry-Module-Classes Manifest entry. The
041     * registry will have been {@linkplain Registry#performRegistryStartup() started up} before it is returned.
042     *
043     * @return constructed Registry, after startup
044     * @see #addDefaultModules(RegistryBuilder)
045     */
046    public static Registry buildDefaultRegistry()
047    {
048        RegistryBuilder builder = new RegistryBuilder();
049
050        addDefaultModules(builder);
051
052        Registry registry = builder.build();
053
054        registry.performRegistryStartup();
055
056        return registry;
057    }
058
059    /**
060     * Scans the classpath for JAR Manifests that contain the Tapestry-Module-Classes attribute and adds each
061     * corresponding class to the RegistryBuilder. In addition, looks for a system property named "tapestry.modules" and
062     * adds all of those modules as well. The tapestry.modules approach is intended for development.
063     * To prevent auto-loading of Manifest-defined modules the system property named "tapestry.manifest-modules-blacklist"
064     * can be used.
065     *
066     * @param builder
067     *         the builder to which modules will be added
068     * @see org.apache.tapestry5.ioc.annotations.ImportModule
069     * @see RegistryBuilder#add(String)
070     */
071    public static void addDefaultModules(RegistryBuilder builder)
072    {
073        Set<String> blacklistedManifestModules = new HashSet<>();
074        String modulesBlacklist = System.getProperty("tapestry.manifest-modules-blacklist");
075        if (modulesBlacklist != null)
076        {
077            String[] blacklistedClassnames = modulesBlacklist.split(",");
078
079            
080            for (String classname : blacklistedClassnames)
081            {
082                blacklistedManifestModules.add(classname.trim());
083            }
084        }
085
086        try
087        {
088            Enumeration<URL> urls = builder.getClassLoader().getResources("META-INF/MANIFEST.MF");
089
090            while (urls.hasMoreElements())
091            {
092                URL url = urls.nextElement();
093
094                addModulesInManifest(builder, url, blacklistedManifestModules);
095            }
096
097            addModulesInList(builder, System.getProperty("tapestry.modules"));
098
099        } catch (IOException ex)
100        {
101            throw new RuntimeException(ex.getMessage(), ex);
102        }
103    }
104
105    private static void addModulesInManifest(RegistryBuilder builder, URL url, Set<String> blacklist)
106    {
107        InputStream in = null;
108
109        Throwable fail = null;
110
111        try
112        {
113            in = url.openStream();
114
115            Manifest mf = new Manifest(in);
116
117            in.close();
118
119            in = null;
120
121            String list = mf.getMainAttributes().getValue(MODULE_BUILDER_MANIFEST_ENTRY_NAME);
122
123            addModulesInList(builder, list, blacklist);
124        } catch (RuntimeException ex)
125        {
126            fail = ex;
127        } catch (IOException ex)
128        {
129            fail = ex;
130        } finally
131        {
132            close(in);
133        }
134
135        if (fail != null)
136            throw new RuntimeException(String.format("Exception loading module(s) from manifest %s: %s",
137                    url.toString(),
138                    ExceptionUtils.toMessage(fail)), fail);
139
140    }
141
142    static void addModulesInList(RegistryBuilder builder, String list)
143    {
144        addModulesInList(builder, list, Collections.emptySet());
145    }
146
147    static void addModulesInList(RegistryBuilder builder, String list, Set<String> blacklist)
148    {
149        if (list == null) return;
150
151        String[] classnames = list.split(",");
152
153        for (String classname : classnames)
154        {
155            String trimmedClassname = classname.trim();
156            if (blacklist != null && blacklist.contains(trimmedClassname))
157            {
158                continue;
159            }
160
161            builder.add(trimmedClassname);
162        }
163    }
164
165    /**
166     * Closes an input stream (or other Closeable), ignoring any exception.
167     *
168     * @param closeable
169     *         the thing to close, or null to close nothing
170     */
171    private static void close(Closeable closeable)
172    {
173        if (closeable != null)
174        {
175            try
176            {
177                closeable.close();
178            } catch (IOException ex)
179            {
180                // Ignore.
181            }
182        }
183    }
184}