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