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}