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}