package jumpstart.web.services; import java.util.Arrays; import java.util.HashSet; import jumpstart.util.JodaTimeUtil; import jumpstart.web.translators.YesNoTranslator; import jumpstart.web.validators.Letters; import org.apache.tapestry5.SymbolConstants; import org.apache.tapestry5.Translator; import org.apache.tapestry5.Validator; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.ioc.Configuration; import org.apache.tapestry5.ioc.MappedConfiguration; import org.apache.tapestry5.ioc.OrderedConfiguration; import org.apache.tapestry5.ioc.ServiceBinder; import org.apache.tapestry5.ioc.annotations.Contribute; import org.apache.tapestry5.ioc.annotations.EagerLoad; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Primary; import org.apache.tapestry5.ioc.annotations.Symbol; import org.apache.tapestry5.ioc.services.ClasspathURLConverter; import org.apache.tapestry5.ioc.services.Coercion; import org.apache.tapestry5.ioc.services.CoercionTuple; import org.apache.tapestry5.services.ComponentRequestFilter; import org.apache.tapestry5.services.PageRenderLinkSource; import org.apache.tapestry5.services.Request; import org.apache.tapestry5.services.RequestFilter; import org.apache.tapestry5.services.security.ClientWhitelist; import org.apache.tapestry5.services.security.WhitelistAnalyzer; import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2; import org.apache.tapestry5.upload.services.UploadSymbols; import org.got5.tapestry5.jquery.JQuerySymbolConstants; import org.joda.time.DateMidnight; import org.joda.time.LocalDate; import org.slf4j.Logger; /** * This module is automatically included as part of the Tapestry IoC Registry, it's a good place to configure and extend * Tapestry, or to place your own service definitions. See http://tapestry.apache.org/5.3.3/tapestry-ioc/module.html */ public class AppModule { private static final String UPLOADS_PATH = "jumpstart.upload-path"; @Inject @Symbol(SymbolConstants.PRODUCTION_MODE) @Property(write = false) private static boolean productionMode; // Add 2 services to those provided by Tapestry. // - CountryNames, and SelectIdModelFactory are used by pages which ask Tapestry to @Inject them. public static void bind(ServiceBinder binder) { binder.bind(CountryNames.class); binder.bind(SelectIdModelFactory.class, SelectIdModelFactoryImpl.class); // This next line addresses an issue affecting GlassFish and JBoss - see http://blog.progs.be/?p=52 javassist.runtime.Desc.useContextClassLoader = true; } // Tell Tapestry about our custom validators, translators, and their message files. // We do this by contributing to Tapestry's FieldValidatorSource service, TranslatorSource service, // and ComponentMessagesSource service. @SuppressWarnings("rawtypes") public static void contributeFieldValidatorSource(MappedConfiguration configuration) { configuration.add("letters", new Letters()); } @SuppressWarnings("rawtypes") public static void contributeTranslatorSource(MappedConfiguration configuration) { configuration.add(Boolean.class, new YesNoTranslator()); } public void contributeComponentMessagesSource(OrderedConfiguration configuration) { configuration.add("myValidationMessages", "jumpstart/web/validators/ValidationMessages"); configuration.add("myTranslationMessages", "jumpstart/web/translators/TranslationMessages"); } // Tell Tapestry about our custom ValueEncoders. // We do this by contributing to Tapestry's ValueEncoderSource service. // @SuppressWarnings("rawtypes") // public static void contributeValueEncoderSource(MappedConfiguration configuration) { // configuration.addInstance(Person.class, PersonEncoder.class); // } // Tell Tapestry which locales we support, and tell Tapestry5jQuery not to suppress Tapestry's built-in Prototype // and Scriptaculous (see the JQuery example for more information). // We do this by contributing to Tapestry's ApplicationDefaults service. public static void contributeApplicationDefaults(MappedConfiguration configuration) { configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en_US,en_GB,fr"); // We have Tapestry5jQuery installed. Tell it we don't want it to suppress Prototype and Scriptaculous. configuration.add(JQuerySymbolConstants.SUPPRESS_PROTOTYPE, "false"); } // Tell Tapestry how to block access to WEB-INF/, META-INF/, and assets that are not in our assets "whitelist". // We do this by contributing a custom RequestFilter to Tapestry's RequestHandler service. // - This is necessary due to https://issues.apache.org/jira/browse/TAP5-815 . // - RequestHandler is shown in http://tapestry.apache.org/request-processing.html#RequestProcessing-Overview . // - RequestHandler is described in http://tapestry.apache.org/request-processing.html // - Based on an entry in the Tapestry Users mailing list by martijn.list on 15 Aug 09. public void contributeRequestHandler(OrderedConfiguration configuration, PageRenderLinkSource pageRenderLinkSource) { final HashSet ASSETS_WHITE_LIST = new HashSet(Arrays.asList("jpg", "jpeg", "png", "gif", "js", "css", "ico")); configuration.add("AssetProtectionFilter", new AssetProtectionFilter(ASSETS_WHITE_LIST, pageRenderLinkSource), "before:*"); } // Tell Tapestry how to prevent access to pages marked as @ProtectedPage unless logged in. // We do this by contributing a custom ComponentRequestFilter to Tapestry's ComponentRequestHandler service. // - ComponentRequestHandler is shown in // http://tapestry.apache.org/request-processing.html#RequestProcessing-Overview // - Based on http://tapestryjava.blogspot.com/2009/12/securing-tapestry-pages-with.html public void contributeComponentRequestHandler(OrderedConfiguration configuration) { configuration.addInstance("PageProtectionFilter", PageProtectionFilter.class); } // Tell Tapestry how to handle JBoss 5, 6.0, or 6.1's classpath URLs - JBoss uses a "virtual file system". // See "Running Tapestry on JBoss" in http://wiki.apache.org/tapestry/Tapestry5HowTos . @SuppressWarnings("rawtypes") public static void contributeServiceOverride(MappedConfiguration configuration) { // configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterJBoss5()); // configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterJBoss60()); configuration.add(ClasspathURLConverter.class, new ClasspathURLConverterJBoss61()); } // Tell Tapestry how to handle @EJB in page and component classes. // We do this by contributing to Tapestry's ComponentClassTransformWorker service. // - Based on http://wiki.apache.org/tapestry/JEE-Annotation, adapted to EJB 3.0 (from 3.1). @Primary public static void contributeComponentClassTransformWorker( OrderedConfiguration configuration) { configuration.addInstance("EJB", EJBAnnotationWorker.class, "before:Property"); } // Tell Tapestry how to handle pages annotated with @WhitelistAccessOnly, eg. Tapestry's ServiceStatus and // PageCatalog. // The default WhitelistAnalyzer allows localhost only and only in non-production mode. // Our aim is to make the servicestatus page available to ALL clients when not in production mode. // We do this by providing our own WhitelistAnalyzer. @Contribute(ClientWhitelist.class) public static void provideWhitelistAnalyzer(OrderedConfiguration configuration) { if (!productionMode) { configuration.add("NonProductionWhitelistAnalyzer", new WhitelistAnalyzer() { public boolean isRequestOnWhitelist(Request request) { if (request.getPath().startsWith("/core/servicestatus")) { return true; } else { // This is copied from org.apache.tapestry5.internal.services.security.LocalhostOnly String remoteHost = request.getRemoteHost(); return remoteHost.equals("localhost") || remoteHost.equals("127.0.0.1") || remoteHost.equals("0:0:0:0:0:0:0:1%0") || remoteHost.equals("0:0:0:0:0:0:0:1"); } } }, "before:*"); } } // Tell tapestry how to build our Filer service (used in the FileUpload example). // Annotate it with EagerLoad to force resolution of symbols at startup rather than when it is first used. @EagerLoad public static IFiler buildFiler(Logger logger, @Inject @Symbol(UPLOADS_PATH) final String uploadsPath, @Inject @Symbol(UploadSymbols.FILESIZE_MAX) final long fileSizeMax) { return new Filer(logger, UPLOADS_PATH, uploadsPath, UploadSymbols.FILESIZE_MAX, fileSizeMax); } // Tell Tapestry how to coerce Joda Time types to and from Java Date types for the JodaTime example. // We do this by contributing to Tapestry's TypeCoercer service. // - Based on http://tapestry.apache.org/typecoercer-service.html @SuppressWarnings("rawtypes") public static void contributeTypeCoercer(Configuration configuration) { // From java.util.Date to DateMidnight Coercion toDateMidnight = new Coercion() { public DateMidnight coerce(java.util.Date input) { // TODO - confirm this conversion always works, esp. across timezones return JodaTimeUtil.toDateMidnight(input); } }; configuration.add(new CoercionTuple(java.util.Date.class, DateMidnight.class, toDateMidnight)); // From DateMidnight to java.util.Date Coercion fromDateMidnight = new Coercion() { public java.util.Date coerce(DateMidnight input) { // TODO - confirm this conversion always works, esp. across timezones return JodaTimeUtil.toJavaDate(input); } }; configuration.add(new CoercionTuple(DateMidnight.class, java.util.Date.class, fromDateMidnight)); // From java.util.Date to LocalDate Coercion toLocalDate = new Coercion() { public LocalDate coerce(java.util.Date input) { // TODO - confirm this conversion always works, esp. across timezones return JodaTimeUtil.toLocalDate(input); } }; configuration.add(new CoercionTuple(java.util.Date.class, LocalDate.class, toLocalDate)); // From LocalDate to java.util.Date Coercion fromLocalDate = new Coercion() { public java.util.Date coerce(LocalDate input) { // TODO - confirm this conversion always works, esp. across timezones return JodaTimeUtil.toJavaDate(input); } }; configuration.add(new CoercionTuple(LocalDate.class, java.util.Date.class, fromLocalDate)); } }