    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.http.internal;
    014
    015import java.util.Formatter;
    016import java.util.List;
    017import java.util.regex.Pattern;
    018
    019import org.apache.tapestry5.http.TapestryHttpSymbolConstants;
    020import org.apache.tapestry5.http.modules.TapestryHttpModule;
    021import org.apache.tapestry5.ioc.IOCUtilities;
    022import org.apache.tapestry5.ioc.Registry;
    023import org.apache.tapestry5.ioc.RegistryBuilder;
    024import org.apache.tapestry5.ioc.def.ContributionDef;
    025import org.apache.tapestry5.ioc.def.ModuleDef;
    026import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    027import org.apache.tapestry5.ioc.services.ServiceActivity;
    028import org.apache.tapestry5.ioc.services.ServiceActivityScoreboard;
    029import org.apache.tapestry5.ioc.services.Status;
    030import org.apache.tapestry5.ioc.services.SymbolProvider;
    031import org.apache.tapestry5.ioc.services.SymbolSource;
    032import org.slf4j.Logger;
    033
    034/**
    035 * This class is used to build the {@link Registry}. The Registry contains
    036 * {@link org.apache.tapestry5.ioc.modules.TapestryIOCModule} and {@link TapestryHttpModule}, any
    037 * modules identified by {@link #addModules(Class[])} )}, plus the application module.
    038 *
    039 * The application module is optional.
    040 *
    041 * The application module is identified as <em>package</em>.services.<em>appName</em>Module, where
    042 * <em>package</em> and the <em>appName</em> are specified by the caller.
    043 */
    044@SuppressWarnings("rawtypes")
    045public class TapestryAppInitializer
    046{
    047    private final Logger logger;
    048
    049    private final SymbolProvider appProvider;
    050
    051    private final String appName;
    052
    053    private final long startTime;
    054
    055    private final RegistryBuilder builder = new RegistryBuilder();
    056
    057    private long registryCreatedTime;
    058
    059    private Registry registry;
    060
    061    /**
    062     * @param logger
    063     *         logger for output confirmation
    064     * @param appPackage
    065     *         root package name to search for pages and components
    066     * @param appName
    067     *         the name of the application (i.e., the name of the application servlet)
    068     */
    069    public TapestryAppInitializer(Logger logger, String appPackage, String appName)
    070    {
    071        this(logger, new SingleKeySymbolProvider(TapestryHttpInternalConstants.TAPESTRY_APP_PACKAGE_PARAM, appPackage), appName,
    072                null);
    073    }
    074
    075    /**
    076     * @param logger
    077     *         logger for output confirmation
    078     * @param appProvider
    079     *         provides symbols for the application (normally, from the ServletContext init
    080     *         parameters), plus (as of 5.4) the value for symbol {@link TapestryHttpSymbolConstants#CONTEXT_PATH}
    081     * @param appName
    082     *         the name of the application (i.e., the name of the application servlet)
    083     * @param executionModes
    084     *         an optional, comma-separated list of execution modes, each of which is used
    085     *         to find a list of additional module classes to load (key
    086     *         <code>tapestry.<em>name</em>-modules</code> in appProvider, i.e., the servlet
    087     *         context)
    088     */
    089    public TapestryAppInitializer(Logger logger, SymbolProvider appProvider, String appName, String executionModes)
    090    {
    091        this.logger = logger;
    092        this.appProvider = appProvider;
    093
    094        String appPackage = appProvider.valueForSymbol(TapestryHttpInternalConstants.TAPESTRY_APP_PACKAGE_PARAM);
    095
    096        this.appName = appName;
    097
    098        startTime = System.currentTimeMillis();
    099
    100        if (!Boolean.parseBoolean(appProvider.valueForSymbol(TapestryHttpInternalConstants.DISABLE_DEFAULT_MODULES_PARAM)))
    101        {
    102            IOCUtilities.addDefaultModules(builder);
    103        }
    104
    105        // This gets added automatically.
    106
    107        addModules(TapestryHttpModule.class);
    108
    109        String className = appPackage + ".services." + InternalUtils.capitalize(this.appName) + "Module";
    110
    111        try
    112        {
    113            // This class is possibly loaded by a parent class loader of the application class
    114            // loader. The context class loader should have the appropriate view to the module
    115            // class,
    116            // if any.
    117
    118            Class moduleClass = Thread.currentThread().getContextClassLoader().loadClass(className);
    119
    120            builder.add(moduleClass);
    121        } catch (ClassNotFoundException ex)
    122        {
    123            // That's OK, not all applications will have a module class, even though any
    124            // non-trivial application will.
    125            logger.warn("Application Module class {} not found", className);
    126        }
    127
    128        // Add a synthetic module that contributes symbol sources.
    129
    130        addSyntheticSymbolSourceModule(appPackage);
    131
    132        for (String mode : splitAtCommas(executionModes))
    133        {
    134            String key = String.format("tapestry.%s-modules", mode);
    135            String moduleList = appProvider.valueForSymbol(key);
    136
    137            for (String moduleClassName : splitAtCommas(moduleList))
    138            {
    139                builder.add(moduleClassName);
    140            }
    141        }
    142    }
    143
    144    /**
    145     * Adds additional modules.
    146     *
    147     * @param moduleDefs
    148     */
    149    public void addModules(ModuleDef... moduleDefs)
    150    {
    151        for (ModuleDef def : moduleDefs)
    152            builder.add(def);
    153    }
    154
    155    public void addModules(Class... moduleClasses)
    156    {
    157        builder.add(moduleClasses);
    158    }
    159
    160    private void addSyntheticSymbolSourceModule(String appPackage)
    161    {
    162        ContributionDef appPathContribution = new SyntheticSymbolSourceContributionDef("AppPath",
    163                new SingleKeySymbolProvider(TapestryHttpInternalSymbols.APP_PACKAGE_PATH, appPackage.replace('.', '/')));
    164
    165        ContributionDef symbolSourceContribution = new SyntheticSymbolSourceContributionDef("ServletContext",
    166                appProvider, "before:ApplicationDefaults", "after:EnvironmentVariables");
    167
    168        ContributionDef appNameContribution = new SyntheticSymbolSourceContributionDef("AppName",
    169                new SingleKeySymbolProvider(TapestryHttpInternalSymbols.APP_NAME, appName), "before:ServletContext");
    170
    171        builder.add(new SyntheticModuleDef(symbolSourceContribution, appNameContribution, appPathContribution));
    172    }
    173
    174    public Registry createRegistry()
    175    {
    176        registryCreatedTime = System.currentTimeMillis();
    177
    178        registry = builder.build();
    179
    180        return registry;
    181    }
    182
    183    /**
    184     * Announce application startup, by logging (at INFO level) the names of all pages,
    185     * components, mixins and services.
    186     */
    187    public void announceStartup()
    188    {
    189        if (!logger.isInfoEnabled()) // if info logging is off we can stop now
    190        {
    191            return;
    192        }
    193        long toFinish = System.currentTimeMillis();
    194
    195        SymbolSource source = registry.getService("SymbolSource", SymbolSource.class);
    196
    197        StringBuilder buffer = new StringBuilder("Startup status:\n\nServices:\n\n");
    198        Formatter f = new Formatter(buffer);
    199
    200
    201        int unrealized = 0;
    202
    203        ServiceActivityScoreboard scoreboard = registry.getService(ServiceActivityScoreboard.class);
    204
    205        List<ServiceActivity> serviceActivity = scoreboard.getServiceActivity();
    206
    207        int longest = 0;
    208
    209        // One pass to find the longest name, and to count the unrealized services.
    210
    211        for (ServiceActivity activity : serviceActivity)
    212        {
    213            Status status = activity.getStatus();
    214
    215            longest = Math.max(longest, activity.getServiceId().length());
    216
    217            if (status == Status.DEFINED || status == Status.VIRTUAL)
    218                unrealized++;
    219        }
    220
    221        String formatString = "%" + longest + "s: %s\n";
    222
    223        // A second pass to output all the services
    224
    225        for (ServiceActivity activity : serviceActivity)
    226        {
    227            f.format(formatString, activity.getServiceId(), activity.getStatus().name());
    228        }
    229
    230        f.format("\n%4.2f%% unrealized services (%d/%d)\n", 100. * unrealized / serviceActivity.size(), unrealized,
    231                serviceActivity.size());
    232
    233
    234        f.format("\nApplication '%s' (version %s) startup time: %,d ms to build IoC Registry, %,d ms overall.", appName,
    235                source.valueForSymbol(TapestryHttpSymbolConstants.APPLICATION_VERSION),
    236                registryCreatedTime - startTime,
    237                toFinish - startTime);
    238
    239        String version = source.valueForSymbol(TapestryHttpSymbolConstants.TAPESTRY_VERSION);
    240        boolean productionMode = Boolean.parseBoolean(source.valueForSymbol(TapestryHttpSymbolConstants.PRODUCTION_MODE));
    241
    242
    243        buffer.append("\n\n");
    244        buffer.append(" ______                  __             ____\n");
    245        buffer.append("/_  __/__ ____  ___ ___ / /_______ __  / __/\n");
    246        buffer.append(" / / / _ `/ _ \\/ -_|_-</ __/ __/ // / /__ \\ \n");
    247        buffer.append("/_/  \\_,_/ .__/\\__/___/\\__/_/  \\_, / /____/\n");
    248        f.format     ("        /_/                   /___/  %s%s\n\n",
    249                version, productionMode ? "" : " (development mode)");
    250
    251        // log multi-line string with OS-specific line endings (TAP5-2294)
    252        logger.info(buffer.toString().replaceAll("\\n", System.getProperty("line.separator")));
    253    }
    254
    255    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    256
    257    private static final Pattern COMMA_PATTERN = Pattern.compile("\\s*,\\s*");
    258
    259    /**
    260     * Splits a value around commas. Whitespace around the commas is removed, as is leading and trailing whitespace.
    261     *
    262     * @since 5.1.0.0
    263     */
    264    public static String[] splitAtCommas(String value)
    265    {
    266        if (InternalUtils.isBlank(value))
    267            return EMPTY_STRING_ARRAY;
    268
    269        return COMMA_PATTERN.split(value.trim());
    270    }
    271
    272}