    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}