| // Copyright 2006, 2007, 2008 The Apache Software Foundation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package org.apache.tapestry5.services; |
| |
| import org.apache.tapestry5.*; |
| import org.apache.tapestry5.annotations.*; |
| import org.apache.tapestry5.beaneditor.Validate; |
| import org.apache.tapestry5.corelib.data.BlankOption; |
| import org.apache.tapestry5.corelib.data.GridPagerPosition; |
| import org.apache.tapestry5.corelib.data.InsertPosition; |
| import org.apache.tapestry5.grid.GridDataSource; |
| import org.apache.tapestry5.internal.*; |
| import org.apache.tapestry5.internal.beaneditor.PrimitiveFieldConstraintGenerator; |
| import org.apache.tapestry5.internal.beaneditor.ValidateAnnotationConstraintGenerator; |
| import org.apache.tapestry5.internal.bindings.*; |
| import org.apache.tapestry5.internal.events.InvalidationListener; |
| import org.apache.tapestry5.internal.grid.CollectionGridDataSource; |
| import org.apache.tapestry5.internal.grid.NullDataSource; |
| import org.apache.tapestry5.internal.renderers.*; |
| import org.apache.tapestry5.internal.services.*; |
| import org.apache.tapestry5.internal.transform.*; |
| import org.apache.tapestry5.internal.translator.*; |
| import org.apache.tapestry5.internal.util.IntegerRange; |
| import org.apache.tapestry5.ioc.*; |
| import org.apache.tapestry5.ioc.annotations.*; |
| import org.apache.tapestry5.ioc.internal.util.CollectionFactory; |
| import org.apache.tapestry5.ioc.internal.util.IdAllocator; |
| import org.apache.tapestry5.ioc.services.*; |
| import org.apache.tapestry5.ioc.util.StrategyRegistry; |
| import org.apache.tapestry5.ioc.util.TimeInterval; |
| import org.apache.tapestry5.runtime.Component; |
| import org.apache.tapestry5.runtime.ComponentResourcesAware; |
| import org.apache.tapestry5.runtime.RenderCommand; |
| import org.apache.tapestry5.util.StringToEnumCoercion; |
| import org.apache.tapestry5.validator.*; |
| import org.apache.tapestry5.json.JSONObject; |
| import org.apache.tapestry5.services.*; |
| import org.slf4j.Logger; |
| |
| import javax.servlet.ServletContext; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import java.io.IOException; |
| import java.lang.annotation.Annotation; |
| import java.net.URL; |
| import java.util.*; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * The root module for Tapestry. |
| */ |
| @SuppressWarnings({ "JavaDoc" }) |
| @Marker(Core.class) |
| @SubModule(InternalModule.class) |
| public final class TapestryModule |
| { |
| private final PipelineBuilder pipelineBuilder; |
| |
| private final ApplicationGlobals applicationGlobals; |
| |
| private final PropertyShadowBuilder shadowBuilder; |
| |
| private final Environment environment; |
| |
| private final StrategyBuilder strategyBuilder; |
| |
| private final PropertyAccess propertyAccess; |
| |
| private final ComponentInstantiatorSource componentInstantiatorSource; |
| |
| private final UpdateListenerHub updateListenerHub; |
| |
| private final ChainBuilder chainBuilder; |
| |
| private final Request request; |
| |
| private final Response response; |
| |
| private final ThreadLocale threadLocale; |
| |
| private final RequestGlobals requestGlobals; |
| |
| private final ActionRenderResponseGenerator actionRenderResponseGenerator; |
| |
| private final EnvironmentalShadowBuilder environmentalBuilder; |
| |
| |
| /** |
| * We inject all sorts of common dependencies (including builders) into the module itself (note: even though some of |
| * these service are defined by the module itself, that's ok because services are always lazy proxies). This isn't |
| * about efficiency (it may be slightly more efficient, but not in any noticable way), it's about eliminating the |
| * need to keep injecting these dependencies into invividual service builder and contribution methods. |
| */ |
| public TapestryModule(PipelineBuilder pipelineBuilder, |
| |
| PropertyShadowBuilder shadowBuilder, |
| |
| RequestGlobals requestGlobals, |
| |
| ApplicationGlobals applicationGlobals, |
| |
| ChainBuilder chainBuilder, |
| |
| Environment environment, |
| |
| StrategyBuilder strategyBuilder, |
| |
| ComponentInstantiatorSource componentInstantiatorSource, |
| |
| PropertyAccess propertyAccess, |
| |
| UpdateListenerHub updateListenerHub, |
| |
| Request request, |
| |
| Response response, |
| |
| ThreadLocale threadLocale, |
| |
| ActionRenderResponseGenerator actionRenderResponseGenerator, |
| |
| EnvironmentalShadowBuilder environmentalBuilder) |
| { |
| this.pipelineBuilder = pipelineBuilder; |
| this.shadowBuilder = shadowBuilder; |
| this.requestGlobals = requestGlobals; |
| this.applicationGlobals = applicationGlobals; |
| this.chainBuilder = chainBuilder; |
| this.environment = environment; |
| this.strategyBuilder = strategyBuilder; |
| this.componentInstantiatorSource = componentInstantiatorSource; |
| this.propertyAccess = propertyAccess; |
| this.updateListenerHub = updateListenerHub; |
| this.request = request; |
| this.response = response; |
| this.threadLocale = threadLocale; |
| this.actionRenderResponseGenerator = actionRenderResponseGenerator; |
| this.environmentalBuilder = environmentalBuilder; |
| } |
| |
| public static void bind(ServiceBinder binder) |
| { |
| // Public Services |
| |
| binder.bind(ClasspathAssetAliasManager.class, ClasspathAssetAliasManagerImpl.class); |
| binder.bind(PersistentLocale.class, PersistentLocaleImpl.class); |
| binder.bind(ApplicationStateManager.class, ApplicationStateManagerImpl.class); |
| binder.bind(ApplicationStatePersistenceStrategySource.class, |
| ApplicationStatePersistenceStrategySourceImpl.class); |
| binder.bind(BindingSource.class, BindingSourceImpl.class); |
| binder.bind(FieldValidatorSource.class, FieldValidatorSourceImpl.class); |
| binder.bind(ApplicationGlobals.class, ApplicationGlobalsImpl.class); |
| binder.bind(AssetSource.class, AssetSourceImpl.class); |
| binder.bind(Cookies.class, CookiesImpl.class); |
| binder.bind(Environment.class, EnvironmentImpl.class); |
| binder.bind(FieldValidatorDefaultSource.class, FieldValidatorDefaultSourceImpl.class); |
| binder.bind(RequestGlobals.class, RequestGlobalsImpl.class); |
| binder.bind(ResourceDigestGenerator.class, ResourceDigestGeneratorImpl.class); |
| binder.bind(ValidationConstraintGenerator.class, ValidationConstraintGeneratorImpl.class); |
| binder.bind(EnvironmentalShadowBuilder.class, EnvironmentalShadowBuilderImpl.class); |
| binder.bind(ComponentSource.class, ComponentSourceImpl.class); |
| binder.bind(BeanModelSource.class, BeanModelSourceImpl.class); |
| binder.bind(BeanBlockSource.class, BeanBlockSourceImpl.class); |
| binder.bind(ComponentDefaultProvider.class, ComponentDefaultProviderImpl.class); |
| binder.bind(MarkupWriterFactory.class, MarkupWriterFactoryImpl.class); |
| binder.bind(FieldValidationSupport.class, FieldValidationSupportImpl.class); |
| binder.bind(ObjectRenderer.class, LocationRenderer.class).withId("LocationRenderer"); |
| binder.bind(ObjectProvider.class, AssetObjectProvider.class).withId("AssetObjectProvider"); |
| binder.bind(RequestExceptionHandler.class, DefaultRequestExceptionHandler.class); |
| binder.bind(ComponentEventResultProcessor.class, ComponentInstanceResultProcessor.class).withId( |
| "ComponentInstanceResultProcessor"); |
| binder.bind(NullFieldStrategySource.class, NullFieldStrategySourceImpl.class); |
| binder.bind(HttpServletRequestFilter.class, IgnoredPathsFilter.class).withId("IgnoredPathsFilter"); |
| binder.bind(ContextValueEncoder.class, ContextValueEncoderImpl.class); |
| binder.bind(BaseURLSource.class, BaseURLSourceImpl.class); |
| binder.bind(BeanBlockOverrideSource.class, BeanBlockOverrideSourceImpl.class); |
| |
| binder.bind(AliasManager.class, AliasManagerImpl.class).withId("AliasOverrides"); |
| } |
| |
| // ======================================================================== |
| // |
| // Service Builder Methods (static) |
| // |
| // ======================================================================== |
| |
| |
| public static Alias build(Logger logger, |
| |
| @Inject @Symbol(InternalConstants.TAPESTRY_ALIAS_MODE_SYMBOL) |
| String mode, |
| |
| @InjectService("AliasOverrides") |
| AliasManager overridesManager, |
| |
| Collection<AliasContribution> configuration) |
| { |
| AliasManager manager = new AliasManagerImpl(logger, configuration); |
| |
| return new AliasImpl(manager, mode, overridesManager); |
| } |
| |
| // ======================================================================== |
| // |
| // Service Contribution Methods (static) |
| // |
| // ======================================================================== |
| |
| /** |
| * Contributes the factory for serveral built-in binding prefixes ("asset", "block", "component", "literal", prop", |
| * "nullfieldstrategy", "message", "validate", "translate", "var"). |
| */ |
| public static void contributeBindingSource(MappedConfiguration<String, BindingFactory> configuration, |
| |
| @InjectService("PropBindingFactory") |
| BindingFactory propBindingFactory, |
| |
| ObjectLocator locator) |
| { |
| configuration.add(BindingConstants.LITERAL, new LiteralBindingFactory()); |
| configuration.add(BindingConstants.PROP, propBindingFactory); |
| |
| configuration.add(BindingConstants.COMPONENT, new ComponentBindingFactory()); |
| configuration.add(BindingConstants.MESSAGE, new MessageBindingFactory()); |
| configuration.add(BindingConstants.VALIDATE, locator.autobuild(ValidateBindingFactory.class)); |
| configuration.add(BindingConstants.TRANSLATE, locator.autobuild(TranslateBindingFactory.class)); |
| configuration.add(BindingConstants.BLOCK, new BlockBindingFactory()); |
| configuration.add(BindingConstants.ASSET, locator.autobuild(AssetBindingFactory.class)); |
| configuration.add(BindingConstants.VAR, new RenderVariableBindingFactory()); |
| configuration.add(BindingConstants.NULLFIELDSTRATEGY, |
| locator.autobuild(NullFieldStrategyBindingFactory.class)); |
| } |
| |
| public static void contributeClasspathAssetAliasManager(MappedConfiguration<String, String> configuration, |
| |
| @Symbol(SymbolConstants.TAPESTRY_VERSION) |
| String version, |
| |
| // @Inject not needed, because this isn't a service builder method |
| @Symbol("tapestry.scriptaculous.path") |
| String scriptaculousPath, |
| |
| @Symbol("tapestry.datepicker.path") |
| String datepickerPath) |
| { |
| // TAPESTRY-2159: All the classpath assets are inside a version numbered folder (i.e., 5.0.12). |
| // For scriptaculous, etc., this version is not the version of the library, but the version |
| // bundled with Tapestry. |
| |
| configuration.add("tapestry/" + version, "org/apache/tapestry5"); |
| |
| configuration.add("scriptaculous/" + version, scriptaculousPath); |
| |
| configuration.add("datepicker/" + version, datepickerPath); |
| } |
| |
| public static void contributeComponentClassResolver(Configuration<LibraryMapping> configuration) |
| { |
| configuration.add(new LibraryMapping("core", "org.apache.tapestry5.corelib")); |
| } |
| |
| /** |
| * Adds a number of standard component class transform workers: <dl> <dt>Retain </dt> <dd>Allows fields to retain |
| * their values between requests</dd> <dt>Persist </dt> <dd>Allows fields to store their their value persistently |
| * between requests</dd> <dt>Parameter </dt> <dd>Identifies parameters based on the {@link |
| * org.apache.tapestry5.annotations.Parameter} annotation</dd> <dt>Component </dt> <dd>Defines embedded components |
| * based on the {@link org.apache.tapestry5.annotations.Component} annotation</dd> <dt>Mixin </dt> <dd>Adds a mixin as |
| * part of a component's implementation</dd> <dt>Environment </dt> <dd>Allows fields to contain values extracted |
| * from the {@link org.apache.tapestry5.services.Environment} service</dd> <dt>Inject </dt> <dd>Used with the {@link |
| * org.apache.tapestry5.ioc.annotations.Inject} annotation, when a value is supplied</dd> <dt>InjectPage</dt> <dd>Adds |
| * code to allow access to other pages via the {@link org.apache.tapestry5.annotations.InjectPage} field |
| * annotation</dd> <dt>InjectBlock </dt> <dd>Allows a block from the template to be injected into a field</dd> |
| * <dt>IncludeStylesheet </dt> <dd>Supports the {@link org.apache.tapestry5.annotations.IncludeStylesheet} |
| * annotation</dd> <dt>IncludeJavaScriptLibrary </dt> <dd>Supports the {@link org.apache.tapestry5.annotations.IncludeJavaScriptLibrary} |
| * annotation</dd> <dt>SupportsInformalParameters </dt> <dd>Checks for the annotation</dd> <dt>Meta </dt> <dd>Checks |
| * for meta data and adds it to the component model</dd> <dt>ApplicationState </dt> <dd>Converts fields that |
| * reference application state objects <dt>UnclaimedField </dt> <dd>Identifies unclaimed fields and resets them to |
| * null/0/false at the end of the request</dd> <dt>RenderCommand </dt> <dd>Ensures all components also implement |
| * {@link org.apache.tapestry5.runtime.RenderCommand}</dd> <dt>SetupRender, BeginRender, etc. </dt> <dd>Correspond to |
| * component render phases and annotations</dd> <dt>InvokePostRenderCleanupOnResources </dt> <dd>Makes sure {@link |
| * org.apache.tapestry5.internal.InternalComponentResources#postRenderCleanup()} is invoked after a component |
| * finishes rendering</dd> <dt>Secure</dt> <dd>Checks for the {@link org.apache.tapestry5.annotations.Secure} |
| * annotation</dd> <dt>ContentType</dt> <dd>Checks for {@link org.apache.tapestry5.annotations.ContentType} |
| * annotation</dd> <dt>ResponseEncoding</dt> <dd>Checks for the {@link org.apache.tapestry5.annotations.ResponseEncoding} |
| * annotation</dd> <dt>GenerateAccessors</dt> <dd>Generates accessor methods if {@link |
| * org.apache.tapestry5.annotations.Property} annotation is present </dd> <dt>Cached</dt> <dd>Checks for the {@link |
| * org.apache.tapestry5.annotations.Cached} annotation</dd><dt>Log</dt> <dd>Checks for the {@link |
| * org.apache.tapestry5.annotations.Log} annotation</dd></dl> |
| */ |
| public static void contributeComponentClassTransformWorker( |
| OrderedConfiguration<ComponentClassTransformWorker> configuration, |
| |
| ObjectLocator locator, |
| |
| InjectionProvider injectionProvider, |
| |
| ComponentClassResolver resolver) |
| { |
| // TODO: Proper scheduling of all of this. Since a given field or method should |
| // only have a single annotation, the order doesn't matter so much, as long as |
| // UnclaimedField is last. |
| |
| configuration.add("Cached", locator.autobuild(CachedWorker.class)); |
| |
| configuration.add("Meta", new MetaWorker()); |
| |
| configuration.add("Inject", new InjectWorker(locator, injectionProvider)); |
| |
| configuration.add("Secure", new SecureWorker()); |
| |
| configuration.add("MixinAfter", new MixinAfterWorker()); |
| configuration.add("Component", new ComponentWorker(resolver)); |
| configuration.add("Mixin", new MixinWorker(resolver)); |
| configuration.add("OnEvent", new OnEventWorker()); |
| configuration.add("SupportsInformalParameters", new SupportsInformalParametersWorker()); |
| configuration.add("InjectPage", locator.autobuild(InjectPageWorker.class)); |
| configuration.add("InjectContainer", new InjectContainerWorker()); |
| configuration.add("InjectComponent", new InjectComponentWorker()); |
| configuration.add("RenderCommand", new RenderCommandWorker()); |
| |
| // Default values for parameters are often some form of injection, so make sure |
| // that Parameter fields are processed after injections. |
| |
| configuration.add("Parameter", locator.autobuild(ParameterWorker.class), "after:Inject*"); |
| |
| // Workers for the component rendering state machine methods; this is in typical |
| // execution order. |
| |
| add(configuration, TransformConstants.SETUP_RENDER_SIGNATURE, SetupRender.class, false); |
| add(configuration, TransformConstants.BEGIN_RENDER_SIGNATURE, BeginRender.class, false); |
| add(configuration, TransformConstants.BEFORE_RENDER_TEMPLATE_SIGNATURE, BeforeRenderTemplate.class, false); |
| add(configuration, TransformConstants.BEFORE_RENDER_BODY_SIGNATURE, BeforeRenderBody.class, false); |
| |
| // These phases operate in reverse order. |
| |
| add(configuration, TransformConstants.AFTER_RENDER_BODY_SIGNATURE, AfterRenderBody.class, true); |
| add(configuration, TransformConstants.AFTER_RENDER_TEMPLATE_SIGNATURE, AfterRenderTemplate.class, true); |
| add(configuration, TransformConstants.AFTER_RENDER_SIGNATURE, AfterRender.class, true); |
| add(configuration, TransformConstants.CLEANUP_RENDER_SIGNATURE, CleanupRender.class, true); |
| |
| // Ideally, these should be ordered pretty late in the process to make sure there are no |
| // side effects with other workers that do work inside the page lifecycle methods. |
| |
| add(configuration, PageLoaded.class, TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE, "pageLoaded"); |
| add(configuration, PageAttached.class, TransformConstants.CONTAINING_PAGE_DID_ATTACH_SIGNATURE, "pageAttached"); |
| add(configuration, PageDetached.class, TransformConstants.CONTAINING_PAGE_DID_DETACH_SIGNATURE, "pageDetached"); |
| |
| configuration.add("Retain", new RetainWorker()); |
| configuration.add("Persist", new PersistWorker()); |
| |
| configuration.add("IncludeStylesheet", locator.autobuild(IncludeStylesheetWorker.class), "after:SetupRender"); |
| configuration.add("IncludeJavaScriptLibrary", locator.autobuild(IncludeJavaScriptLibraryWorker.class), |
| "after:SetupRender"); |
| |
| configuration.add("InvokePostRenderCleanupOnResources", new InvokePostRenderCleanupOnResourcesWorker()); |
| |
| configuration.add("ContentType", new ContentTypeWorker()); |
| configuration.add("ResponseEncoding", new ResponseEncodingWorker()); |
| |
| configuration.add("Property", new PropertyWorker()); |
| |
| // These must come after Property, since they actually delete fields that may still have the annotation |
| configuration.add("ApplicationState", locator.autobuild(ApplicationStateWorker.class), |
| "after:Property"); |
| configuration.add("Environment", locator.autobuild(EnvironmentalWorker.class), "after:Property"); |
| |
| configuration.add("Log", locator.autobuild(LogWorker.class)); |
| |
| // This one is always last. Any additional private fields that aren't annotated will |
| // be converted to clear out at the end of the request. |
| |
| configuration.add("UnclaimedField", new UnclaimedFieldWorker(), "after:*"); |
| } |
| |
| /** |
| * <dl> <dt>Annotation</dt> <dd>Checks for {@link org.apache.tapestry5.beaneditor.DataType} annotation</dd> |
| * <dt>Default (ordered last)</dt> <dd>{@link org.apache.tapestry5.internal.services.DefaultDataTypeAnalyzer} |
| * service ({@link #contributeDefaultDataTypeAnalyzer(org.apache.tapestry5.ioc.MappedConfiguration)} })</dd> </dl> |
| */ |
| public static void contributeDataTypeAnalyzer(OrderedConfiguration<DataTypeAnalyzer> configuration, |
| @InjectService("DefaultDataTypeAnalyzer") |
| DataTypeAnalyzer defaultDataTypeAnalyzer) |
| { |
| configuration.add("Annotation", new AnnotationDataTypeAnalyzer()); |
| configuration.add("Default", defaultDataTypeAnalyzer, "after:*"); |
| } |
| |
| /** |
| * Maps property types to data type names: <ul> <li>String --> text <li>Number --> number <li>Enum --> enum |
| * <li>Boolean --> boolean <li>Date --> date </ul> |
| */ |
| public static void contributeDefaultDataTypeAnalyzer(MappedConfiguration<Class, String> configuration) |
| { |
| // This is a special case contributed to avoid exceptions when a property type can't be |
| // matched. DefaultDataTypeAnalyzer converts the empty string to null. |
| |
| configuration.add(Object.class, ""); |
| |
| configuration.add(String.class, "text"); |
| configuration.add(Number.class, "number"); |
| configuration.add(Enum.class, "enum"); |
| configuration.add(Boolean.class, "boolean"); |
| configuration.add(Date.class, "date"); |
| } |
| |
| public static void contributeBeanBlockSource(Configuration<BeanBlockContribution> configuration) |
| { |
| addEditBlock(configuration, "text"); |
| addEditBlock(configuration, "number"); |
| addEditBlock(configuration, "enum"); |
| addEditBlock(configuration, "boolean"); |
| addEditBlock(configuration, "date"); |
| addEditBlock(configuration, "password"); |
| |
| // longtext uses a text area, not a text field |
| |
| addEditBlock(configuration, "longtext"); |
| |
| addDisplayBlock(configuration, "enum"); |
| addDisplayBlock(configuration, "date"); |
| |
| // Password and long text have special output needs. |
| addDisplayBlock(configuration, "password"); |
| addDisplayBlock(configuration, "longtext"); |
| |
| } |
| |
| private static void addEditBlock(Configuration<BeanBlockContribution> configuration, String dataType) |
| { |
| addEditBlock(configuration, dataType, dataType); |
| } |
| |
| private static void addEditBlock(Configuration<BeanBlockContribution> configuration, String dataType, |
| String blockId) |
| { |
| configuration.add(new BeanBlockContribution(dataType, "PropertyEditBlocks", blockId, true)); |
| } |
| |
| private static void addDisplayBlock(Configuration<BeanBlockContribution> configuration, String dataType) |
| { |
| addDisplayBlock(configuration, dataType, dataType); |
| } |
| |
| private static void addDisplayBlock(Configuration<BeanBlockContribution> configuration, String dataType, |
| String blockId) |
| { |
| configuration.add(new BeanBlockContribution(dataType, "PropertyDisplayBlocks", blockId, false)); |
| } |
| |
| /** |
| * Contributes the basic set of validators: <ul> <li>required</li> <li>minlength</li> <li>maxlength</li> |
| * <li>min</li> <li>max</li> <li>regexp</li> </ul> |
| */ |
| public static void contributeFieldValidatorSource(MappedConfiguration<String, Validator> configuration) |
| { |
| configuration.add("required", new Required()); |
| configuration.add("minlength", new MinLength()); |
| configuration.add("maxlength", new MaxLength()); |
| configuration.add("min", new Min()); |
| configuration.add("max", new Max()); |
| configuration.add("regexp", new Regexp()); |
| } |
| |
| /** |
| * Contributes the base set of injection providers: <dl> <dt>Default</dt> <dd>based on {@link |
| * MasterObjectProvider}</dd> <dt>Block</dt> <dd>injects fields of type Block</dd> <dt>ComponentResources</dt> |
| * <dd>give component access to its resources</dd> <dt>CommonResources</dt> <dd>access to properties of resources |
| * (log, messages, etc.)</dd> <dt>Asset</dt> <dd>injection of assets (triggered via {@link Path} annotation), with |
| * the path relative to the component class</dd> <dt>Service</dt> <dd>ordered last, for use when Inject is present |
| * and nothing else works, matches field type against Tapestry IoC services</dd> </dl> |
| */ |
| public static void contributeInjectionProvider(OrderedConfiguration<InjectionProvider> configuration, |
| |
| MasterObjectProvider masterObjectProvider, |
| |
| ObjectLocator locator, |
| |
| SymbolSource symbolSource, |
| |
| AssetSource assetSource) |
| { |
| configuration.add("Default", new DefaultInjectionProvider(masterObjectProvider, locator)); |
| |
| configuration.add("ComponentResources", new ComponentResourcesInjectionProvider()); |
| |
| // This comes after default, to deal with conflicts between injecting a String as the |
| // component id, and injecting a string with @Symbol or @Value. |
| |
| configuration.add("CommonResources", new CommonResourcesInjectionProvider(), "after:Default"); |
| |
| configuration.add("Asset", new AssetInjectionProvider(symbolSource, assetSource), "before:Default"); |
| |
| configuration.add("Block", new BlockInjectionProvider(), "before:Default"); |
| |
| // This needs to be the last one, since it matches against services |
| // and might blow up if there is no match. |
| configuration.add("Service", new ServiceInjectionProvider(locator), "after:*"); |
| } |
| |
| /** |
| * Contributes two object providers: <dl> <dt>Alias</dt> <dd> Searches by type among {@linkplain AliasContribution |
| * contributions} to the {@link Alias} service</dd> <dt>Asset<dt> <dd> Checks for the {@link Path} annotation, and |
| * injects an {@link Asset}</dd> <dt>Service</dt> <dd>Injects based on the {@link Service} annotation, if |
| * present</dd> </dl> |
| */ |
| public static void contributeMasterObjectProvider(OrderedConfiguration<ObjectProvider> configuration, |
| |
| @InjectService("Alias") |
| final Alias alias, |
| |
| @InjectService("AssetObjectProvider") |
| ObjectProvider assetObjectProvider) |
| { |
| // There's a nasty web of dependencies related to Alias; this wrapper class lets us |
| // defer instantiating the Alias service implementation just long enough to defuse those |
| // dependencies. |
| |
| ObjectProvider wrapper = new ObjectProvider() |
| { |
| public <T> T provide(Class<T> objectType, AnnotationProvider annotationProvider, ObjectLocator locator) |
| { |
| return alias.getObjectProvider().provide(objectType, annotationProvider, locator); |
| } |
| }; |
| |
| configuration.add("Alias", wrapper, "after:Value"); |
| |
| configuration.add("Asset", assetObjectProvider, "before:Alias"); |
| |
| configuration.add("Service", new ServiceAnnotationObjectProvider(), "before:Alias"); |
| } |
| |
| |
| public static void contributeHttpServletRequestHandler(OrderedConfiguration<HttpServletRequestFilter> configuration, |
| |
| @InjectService("IgnoredPathsFilter") |
| HttpServletRequestFilter ignoredPathsFilter) |
| { |
| configuration.add("IgnoredPaths", ignoredPathsFilter); |
| } |
| |
| /** |
| * Continues a number of filters into the RequestHandler service: <dl> <dt>StaticFiles</dt> <dd>Checks to see if the |
| * request is for an actual file, if so, returns true to let the servlet container process the request</dd> |
| * <dt>CheckForUpdates</dt> <dd>Periodically fires events that checks to see if the file system sources for any |
| * cached data has changed (see {@link org.apache.tapestry5.internal.services.CheckForUpdatesFilter}). |
| * <dt>ErrorFilter</dt> <dd>Catches request errors and lets the {@link org.apache.tapestry5.services.RequestExceptionHandler} |
| * handle them</dd> <dt>Localization</dt> <dd>Determines the locale for the current request from header data or |
| * cookies in the request</dd> <dt>StoreIntoGlobals</dt> <dd>Stores the request and response into the {@link |
| * org.apache.tapestry5.services.RequestGlobals} service (this is repeated at the end of the pipeline, in case any |
| * filter substitutes the request or response). </dl> |
| */ |
| public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration, Context context, |
| |
| // @Inject not needed because its a long, not a String |
| @Symbol(SymbolConstants.FILE_CHECK_INTERVAL) |
| @IntermediateType(TimeInterval.class) |
| long checkInterval, |
| |
| @Symbol(SymbolConstants.FILE_CHECK_UPDATE_TIMEOUT) |
| @IntermediateType(TimeInterval.class) |
| long updateTimeout, |
| |
| LocalizationSetter localizationSetter, |
| |
| ObjectLocator locator) |
| { |
| RequestFilter staticFilesFilter = new StaticFilesFilter(context); |
| |
| RequestFilter storeIntoGlobals = new RequestFilter() |
| { |
| public boolean service(Request request, Response response, RequestHandler handler) throws IOException |
| { |
| requestGlobals.storeRequestResponse(request, response); |
| |
| return handler.service(request, response); |
| } |
| }; |
| |
| configuration.add("CheckForUpdates", |
| new CheckForUpdatesFilter(updateListenerHub, checkInterval, updateTimeout), "before:*"); |
| |
| configuration.add("StaticFiles", staticFilesFilter); |
| |
| configuration.add("ErrorFilter", locator.autobuild(RequestErrorFilter.class)); |
| |
| configuration.add("StoreIntoGlobals", storeIntoGlobals); |
| |
| configuration.add("Localization", new LocalizationFilter(localizationSetter), "after:ErrorFilter"); |
| } |
| |
| /** |
| * Contributes the basic set of named translators: <ul> <li>string</li> <li>byte</li> <li>integer</li> |
| * <li>long</li> <li>float</li> <li>double</li> </ul> |
| */ |
| public static void contributeTranslatorSource(MappedConfiguration<String, Translator> configuration) |
| { |
| |
| configuration.add("string", new StringTranslator()); |
| configuration.add("byte", new ByteTranslator()); |
| configuration.add("integer", new IntegerTranslator()); |
| configuration.add("long", new LongTranslator()); |
| configuration.add("float", new FloatTranslator()); |
| configuration.add("double", new DoubleTranslator()); |
| } |
| |
| /** |
| * Adds coercions: <ul> <li>String to {@link org.apache.tapestry5.SelectModel} <li>String to {@link |
| * org.apache.tapestry5.corelib.data.InsertPosition} <li>Map to {@link org.apache.tapestry5.SelectModel} |
| * <li>Collection to {@link GridDataSource} <li>null to {@link org.apache.tapestry5.grid.GridDataSource} <li>String |
| * to {@link org.apache.tapestry5.corelib.data.GridPagerPosition} <li>List to {@link org.apache.tapestry5.SelectModel} |
| * <li>{@link org.apache.tapestry5.runtime.ComponentResourcesAware} (typically, a component) to {@link |
| * org.apache.tapestry5.ComponentResources} <li>String to {@link org.apache.tapestry5.corelib.data.BlankOption} </ul> |
| */ |
| public static void contributeTypeCoercer(Configuration<CoercionTuple> configuration) |
| { |
| add(configuration, String.class, SelectModel.class, new Coercion<String, SelectModel>() |
| { |
| public SelectModel coerce(String input) |
| { |
| return TapestryInternalUtils.toSelectModel(input); |
| } |
| }); |
| |
| add(configuration, Map.class, SelectModel.class, new Coercion<Map, SelectModel>() |
| { |
| @SuppressWarnings("unchecked") |
| public SelectModel coerce(Map input) |
| { |
| return TapestryInternalUtils.toSelectModel(input); |
| } |
| }); |
| |
| add(configuration, Collection.class, GridDataSource.class, new Coercion<Collection, GridDataSource>() |
| { |
| public GridDataSource coerce(Collection input) |
| { |
| return new CollectionGridDataSource(input); |
| } |
| }); |
| |
| add(configuration, void.class, GridDataSource.class, new Coercion<Void, GridDataSource>() |
| { |
| private final GridDataSource source = new NullDataSource(); |
| |
| public GridDataSource coerce(Void input) |
| { |
| return source; |
| } |
| }); |
| |
| add(configuration, String.class, GridPagerPosition.class, |
| StringToEnumCoercion.create(GridPagerPosition.class)); |
| |
| add(configuration, String.class, InsertPosition.class, StringToEnumCoercion.create(InsertPosition.class)); |
| |
| add(configuration, String.class, BlankOption.class, StringToEnumCoercion.create(BlankOption.class)); |
| |
| add(configuration, List.class, SelectModel.class, new Coercion<List, SelectModel>() |
| { |
| @SuppressWarnings("unchecked") |
| public SelectModel coerce(List input) |
| { |
| return TapestryInternalUtils.toSelectModel(input); |
| } |
| }); |
| |
| add(configuration, String.class, Pattern.class, new Coercion<String, Pattern>() |
| { |
| public Pattern coerce(String input) |
| { |
| return Pattern.compile(input); |
| } |
| }); |
| |
| add(configuration, ComponentResourcesAware.class, ComponentResources.class, |
| new Coercion<ComponentResourcesAware, ComponentResources>() |
| { |
| |
| public ComponentResources coerce(ComponentResourcesAware input) |
| { |
| return input.getComponentResources(); |
| } |
| }); |
| } |
| |
| /** |
| * Adds built-in constraint generators: <ul> <li>PrimtiveField -- primitive fields are always required |
| * <li>ValidateAnnotation -- adds constraints from a {@link Validate} annotation </ul> |
| */ |
| public static void contributeValidationConstraintGenerator( |
| OrderedConfiguration<ValidationConstraintGenerator> configuration) |
| { |
| configuration.add("PrimitiveField", new PrimitiveFieldConstraintGenerator()); |
| configuration.add("ValidateAnnotation", new ValidateAnnotationConstraintGenerator()); |
| } |
| |
| private static <S, T> void add(Configuration<CoercionTuple> configuration, Class<S> sourceType, Class<T> targetType, |
| Coercion<S, T> coercion) |
| { |
| CoercionTuple<S, T> tuple = new CoercionTuple<S, T>(sourceType, targetType, coercion); |
| |
| configuration.add(tuple); |
| } |
| |
| private static void add(OrderedConfiguration<ComponentClassTransformWorker> configuration, |
| Class<? extends Annotation> annotationClass, |
| TransformMethodSignature lifecycleMethodSignature, String methodAlias) |
| { |
| ComponentClassTransformWorker worker = new PageLifecycleAnnotationWorker(annotationClass, |
| lifecycleMethodSignature, methodAlias); |
| |
| String name = TapestryInternalUtils.lastTerm(annotationClass.getName()); |
| |
| configuration.add(name, worker); |
| } |
| |
| private static void add(OrderedConfiguration<ComponentClassTransformWorker> configuration, |
| TransformMethodSignature signature, Class<? extends Annotation> annotationClass, |
| boolean reverse) |
| { |
| // make the name match the annotation class name. |
| |
| String name = annotationClass.getSimpleName(); |
| |
| configuration.add(name, new ComponentLifecycleMethodWorker(signature, annotationClass, reverse)); |
| } |
| |
| // ======================================================================== |
| // |
| // Service Builder Methods (instance) |
| // |
| // ======================================================================== |
| |
| public Context buildContext(ApplicationGlobals globals) |
| { |
| return shadowBuilder.build(globals, "context", Context.class); |
| } |
| |
| public ComponentClassResolver buildComponentClassResolver(ServiceResources resources) |
| { |
| ComponentClassResolverImpl service = resources.autobuild(ComponentClassResolverImpl.class); |
| |
| // Allow the resolver to clean its cache when the source is invalidated |
| |
| componentInstantiatorSource.addInvalidationListener(service); |
| |
| return service; |
| } |
| |
| @Marker(ClasspathProvider.class) |
| public AssetFactory buildClasspathAssetFactory(ResourceCache resourceCache, ClasspathAssetAliasManager aliasManager) |
| { |
| ClasspathAssetFactory factory = new ClasspathAssetFactory(resourceCache, aliasManager); |
| |
| resourceCache.addInvalidationListener(factory); |
| |
| return factory; |
| } |
| |
| @Marker(ContextProvider.class) |
| public AssetFactory buildContextAssetFactory(ApplicationGlobals globals, RequestPathOptimizer optimizer) |
| { |
| return new ContextAssetFactory(request, globals.getContext(), optimizer); |
| } |
| |
| /** |
| * Builds the PropBindingFactory as a chain of command. The terminator of the chain is responsible for ordinary |
| * property names (and property paths). Contributions to the service cover additional special cases, such as simple |
| * literal values. |
| * |
| * @param configuration contributions of special factories for some constants, each contributed factory may return a |
| * binding if applicable, or null otherwise |
| */ |
| public BindingFactory buildPropBindingFactory(List<BindingFactory> configuration, |
| PropertyConduitSource propertyConduitSource) |
| { |
| PropBindingFactory service = new PropBindingFactory(propertyConduitSource); |
| |
| configuration.add(service); |
| |
| return chainBuilder.build(BindingFactory.class, configuration); |
| } |
| |
| /** |
| * Builds the source of {@link Messages} containing validation messages. The contributions are paths to message |
| * bundles (resource paths within the classpath); the default contribution is "org/apache/tapestry5/internal/ValidationMessages". |
| */ |
| public ValidationMessagesSource buildValidationMessagesSource(Collection<String> configuration, |
| |
| @ClasspathProvider AssetFactory classpathAssetFactory) |
| { |
| ValidationMessagesSourceImpl service = new ValidationMessagesSourceImpl(configuration, |
| classpathAssetFactory.getRootResource()); |
| |
| updateListenerHub.addUpdateListener(service); |
| |
| return service; |
| } |
| |
| public MetaDataLocator buildMetaDataLocator(ServiceResources resources) |
| { |
| MetaDataLocatorImpl service = resources.autobuild(MetaDataLocatorImpl.class); |
| |
| componentInstantiatorSource.addInvalidationListener(service); |
| |
| return service; |
| } |
| |
| public PersistentFieldStrategy buildClientPersistentFieldStrategy(LinkFactory linkFactory, |
| ServiceResources resources) |
| { |
| ClientPersistentFieldStrategy service = resources.autobuild(ClientPersistentFieldStrategy.class); |
| |
| linkFactory.addListener(service); |
| |
| return service; |
| } |
| |
| /** |
| * Builds a proxy to the current {@link org.apache.tapestry5.RenderSupport} inside this thread's {@link |
| * Environment}. |
| */ |
| public RenderSupport buildRenderSupport() |
| { |
| return environmentalBuilder.build(RenderSupport.class); |
| } |
| |
| /** |
| * Builds a proxy to the current {@link org.apache.tapestry5.services.FormSupport} inside this thread's {@link |
| * org.apache.tapestry5.services.Environment}. |
| */ |
| public FormSupport buildFormSupport() |
| { |
| return environmentalBuilder.build(FormSupport.class); |
| } |
| |
| /** |
| * Allows the exact steps in the component class transformation process to be defined. |
| */ |
| public ComponentClassTransformWorker buildComponentClassTransformWorker( |
| List<ComponentClassTransformWorker> configuration) |
| { |
| return chainBuilder.build(ComponentClassTransformWorker.class, configuration); |
| } |
| |
| /** |
| * Analyzes properties to determine the data types, used to {@linkplain #contributeBeanBlockSource(org.apache.tapestry5.ioc.Configuration)} |
| * locale display and edit blocks} for properties. The default behaviors look for a {@link |
| * org.apache.tapestry5.beaneditor.DataType} annotation before deriving the data type from the property type. |
| */ |
| @Marker(Primary.class) |
| public DataTypeAnalyzer buildDataTypeAnalyzer(List<DataTypeAnalyzer> configuration) |
| { |
| return chainBuilder.build(DataTypeAnalyzer.class, configuration); |
| } |
| |
| /** |
| * A chain of command for providing values for {@link Inject}-ed fields in component classes. The service's |
| * configuration can be extended to allow for different automatic injections (based on some combination of field |
| * type and field name). |
| */ |
| |
| public InjectionProvider buildInjectionProvider(List<InjectionProvider> configuration) |
| { |
| return chainBuilder.build(InjectionProvider.class, configuration); |
| } |
| |
| |
| /** |
| * Initializes the application. |
| */ |
| @Marker(Primary.class) |
| public ApplicationInitializer buildApplicationInitializer(Logger logger, |
| List<ApplicationInitializerFilter> configuration) |
| { |
| ApplicationInitializer terminator = new ApplicationInitializer() |
| { |
| public void initializeApplication(Context context) |
| { |
| applicationGlobals.storeContext(context); |
| } |
| }; |
| |
| return pipelineBuilder.build(logger, ApplicationInitializer.class, ApplicationInitializerFilter.class, |
| configuration, terminator); |
| } |
| |
| public HttpServletRequestHandler buildHttpServletRequestHandler(Logger logger, |
| List<HttpServletRequestFilter> configuration, |
| |
| @Primary |
| final RequestHandler handler) |
| { |
| HttpServletRequestHandler terminator = new HttpServletRequestHandler() |
| { |
| public boolean service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) |
| throws IOException |
| { |
| requestGlobals.storeServletRequestResponse(servletRequest, servletResponse); |
| |
| Request request = new RequestImpl(servletRequest); |
| Response response = new ResponseImpl(servletResponse); |
| |
| // Transition from the Servlet API-based pipeline, to the Tapestry-based pipeline. |
| |
| return handler.service(request, response); |
| } |
| }; |
| |
| return pipelineBuilder.build(logger, HttpServletRequestHandler.class, HttpServletRequestFilter.class, |
| configuration, terminator); |
| } |
| |
| @Marker(Primary.class) |
| public RequestHandler buildRequestHandler(Logger logger, List<RequestFilter> configuration, |
| |
| @Primary |
| final Dispatcher masterDispatcher) |
| { |
| RequestHandler terminator = new RequestHandler() |
| { |
| public boolean service(Request request, Response response) throws IOException |
| { |
| requestGlobals.storeRequestResponse(request, response); |
| |
| return masterDispatcher.dispatch(request, response); |
| } |
| }; |
| |
| return pipelineBuilder.build(logger, RequestHandler.class, RequestFilter.class, configuration, terminator); |
| } |
| |
| public ServletApplicationInitializer buildServletApplicationInitializer(Logger logger, |
| List<ServletApplicationInitializerFilter> configuration, |
| |
| @Primary |
| final ApplicationInitializer initializer) |
| { |
| ServletApplicationInitializer terminator = new ServletApplicationInitializer() |
| { |
| public void initializeApplication(ServletContext context) |
| { |
| applicationGlobals.storeServletContext(context); |
| |
| // And now, down the (Web) ApplicationInitializer pipeline ... |
| |
| initializer.initializeApplication(new ContextImpl(context)); |
| } |
| }; |
| |
| return pipelineBuilder.build(logger, ServletApplicationInitializer.class, |
| ServletApplicationInitializerFilter.class, configuration, terminator); |
| } |
| |
| /** |
| * The component event result processor used for normal component requests. |
| */ |
| @Marker({ Primary.class, Traditional.class }) |
| public ComponentEventResultProcessor buildComponentEventResultProcessor( |
| Map<Class, ComponentEventResultProcessor> configuration) |
| { |
| return constructComponentEventResultProcessor(configuration); |
| } |
| |
| /** |
| * The component event result processor used for Ajax-oriented component requests. |
| */ |
| @Marker(Ajax.class) |
| public ComponentEventResultProcessor buildAjaxComponentEventResultProcessor( |
| Map<Class, ComponentEventResultProcessor> configuration) |
| { |
| return constructComponentEventResultProcessor(configuration); |
| |
| } |
| |
| private ComponentEventResultProcessor constructComponentEventResultProcessor( |
| Map<Class, ComponentEventResultProcessor> configuration) |
| { |
| Set<Class> handledTypes = CollectionFactory.newSet(configuration.keySet()); |
| |
| // A slight hack! |
| |
| configuration.put(Object.class, new ObjectComponentEventResultProcessor(handledTypes)); |
| |
| StrategyRegistry<ComponentEventResultProcessor> registry = StrategyRegistry.newInstance( |
| ComponentEventResultProcessor.class, configuration); |
| |
| return strategyBuilder.build(registry); |
| } |
| |
| /** |
| * The default data type analyzer is the final analyzer consulted and identifies the type entirely pased on the |
| * property type, working against its own configuration (mapping property type class to data type). |
| */ |
| public DataTypeAnalyzer buildDefaultDataTypeAnalyzer(ServiceResources resources) |
| { |
| DefaultDataTypeAnalyzer service = resources.autobuild(DefaultDataTypeAnalyzer.class); |
| |
| componentInstantiatorSource.addInvalidationListener(service); |
| |
| return service; |
| } |
| |
| public TranslatorSource buildTranslatorSource(ServiceResources resources) |
| { |
| TranslatorSourceImpl service = resources.autobuild(TranslatorSourceImpl.class); |
| |
| componentInstantiatorSource.addInvalidationListener(service); |
| |
| return service; |
| } |
| |
| @Marker(Primary.class) |
| public ObjectRenderer buildObjectRenderer(Map<Class, ObjectRenderer> configuration) |
| { |
| StrategyRegistry<ObjectRenderer> registry = StrategyRegistry.newInstance(ObjectRenderer.class, configuration); |
| |
| return strategyBuilder.build(registry); |
| } |
| |
| public ComponentMessagesSource buildComponentMessagesSource( |
| @ContextProvider |
| AssetFactory contextAssetFactory, |
| |
| @Inject |
| @Value("WEB-INF/${tapestry.app-name}.properties") |
| String appCatalog) |
| { |
| ComponentMessagesSourceImpl service = new ComponentMessagesSourceImpl(contextAssetFactory |
| .getRootResource(), appCatalog); |
| |
| updateListenerHub.addUpdateListener(service); |
| |
| return service; |
| } |
| |
| /** |
| * Returns a {@link org.apache.tapestry5.ioc.services.ClassFactory} that can be used to create extra classes around |
| * component classes. This ClassFactory will be cleared whenever an underlying component class is discovered to have |
| * changed. Use of this class factory implies that your code will become aware of this (if necessary) to discard any |
| * cached object (alas, this currently involves dipping into the internals side to register for the correct |
| * notifications). Failure to properly clean up can result in really nasty PermGen space memory leaks. |
| */ |
| @Marker(ComponentLayer.class) |
| public ClassFactory buildComponentClassFactory() |
| { |
| return shadowBuilder.build(componentInstantiatorSource, "classFactory", ClassFactory.class); |
| } |
| |
| |
| /** |
| * Ordered contributions to the MasterDispatcher service allow different URL matching strategies to occur. |
| */ |
| @Marker(Primary.class) |
| public Dispatcher buildMasterDispatcher(List<Dispatcher> configuration) |
| { |
| return chainBuilder.build(Dispatcher.class, configuration); |
| } |
| |
| public PropertyConduitSource buildPropertyConduitSource(@ComponentLayer ClassFactory componentClassFactory) |
| { |
| PropertyConduitSourceImpl service = new PropertyConduitSourceImpl(propertyAccess, componentClassFactory); |
| |
| componentInstantiatorSource.addInvalidationListener(service); |
| |
| return service; |
| } |
| |
| /** |
| * Builds a shadow of the RequestGlobals.request property. Note again that the shadow can be an ordinary singleton, |
| * even though RequestGlobals is perthread. |
| */ |
| public Request buildRequest() |
| { |
| return shadowBuilder.build(requestGlobals, "request", Request.class); |
| } |
| |
| /** |
| * Builds a shadow of the RequestGlobals.HTTPServletRequest property. Generally, you should inject the {@link |
| * Request} service instead, as future version of Tapestry may operate beyond just the servlet API. |
| */ |
| public HttpServletRequest buildHttpServletRequest() |
| { |
| return shadowBuilder.build(requestGlobals, "HTTPServletRequest", HttpServletRequest.class); |
| } |
| |
| /** |
| * Builds a shadow of the RequestGlobals.response property. Note again that the shadow can be an ordinary singleton, |
| * even though RequestGlobals is perthread. |
| */ |
| public Response buildResponse() |
| { |
| return shadowBuilder.build(requestGlobals, "response", Response.class); |
| } |
| |
| |
| /** |
| * The MarkupRenderer service is used to render a full page as markup. Supports an ordered configuration of {@link |
| * org.apache.tapestry5.services.MarkupRendererFilter}s. |
| * |
| * @param pageRenderQueue handles the bulk of the work |
| * @param logger used to log errors building the pipeline |
| * @param configuration filters on this service |
| * @return the service |
| * @see #contributeMarkupRenderer(org.apache.tapestry5.ioc.OrderedConfiguration, org.apache.tapestry5.Asset, |
| * org.apache.tapestry5.Asset, ValidationMessagesSource, org.apache.tapestry5.ioc.services.SymbolSource, |
| * AssetSource) |
| */ |
| public MarkupRenderer buildMarkupRenderer(final PageRenderQueue pageRenderQueue, Logger logger, |
| List<MarkupRendererFilter> configuration) |
| { |
| MarkupRenderer terminator = new MarkupRenderer() |
| { |
| public void renderMarkup(MarkupWriter writer) |
| { |
| pageRenderQueue.render(writer); |
| } |
| }; |
| |
| return pipelineBuilder.build(logger, MarkupRenderer.class, MarkupRendererFilter.class, configuration, |
| terminator); |
| } |
| |
| /** |
| * A wrapper around {@link org.apache.tapestry5.internal.services.PageRenderQueue} used for partial page renders. |
| * Supports an ordered configuration of {@link org.apache.tapestry5.services.PartialMarkupRendererFilter}s. |
| * |
| * @param logger used to log warnings creating the pipeline |
| * @param configuration filters for the service |
| * @param renderQueue does most of the work |
| * @return the service |
| * @see #contributePartialMarkupRenderer(org.apache.tapestry5.ioc.OrderedConfiguration, org.apache.tapestry5.Asset, |
| * org.apache.tapestry5.ioc.services.SymbolSource, AssetSource, ValidationMessagesSource) |
| */ |
| public PartialMarkupRenderer buildPartialMarkupRenderer(Logger logger, |
| List<PartialMarkupRendererFilter> configuration, |
| final PageRenderQueue renderQueue) |
| { |
| |
| PartialMarkupRenderer terminator = new PartialMarkupRenderer() |
| { |
| public void renderMarkup(MarkupWriter writer, JSONObject reply) |
| { |
| renderQueue.renderPartial(writer, reply); |
| } |
| }; |
| |
| return pipelineBuilder.build(logger, PartialMarkupRenderer.class, PartialMarkupRendererFilter.class, |
| configuration, terminator); |
| } |
| |
| public PageRenderRequestHandler buildPageRenderRequestHandler(List<PageRenderRequestFilter> configuration, |
| Logger logger, ServiceResources resources) |
| { |
| return pipelineBuilder.build(logger, PageRenderRequestHandler.class, PageRenderRequestFilter.class, |
| configuration, resources.autobuild(PageRenderRequestHandlerImpl.class)); |
| } |
| |
| |
| /** |
| * Builds the component action request handler for traditional (non-Ajax) requests. These typically result in a |
| * redirect to a Tapestry render URL. |
| * |
| * @see org.apache.tapestry5.internal.services.ComponentEventRequestHandlerImpl |
| */ |
| @Marker(Traditional.class) |
| public ComponentEventRequestHandler buildComponentEventRequestHandler( |
| List<ComponentEventRequestFilter> configuration, Logger logger, ServiceResources resources) |
| { |
| return pipelineBuilder.build(logger, ComponentEventRequestHandler.class, ComponentEventRequestFilter.class, |
| configuration, resources.autobuild(ComponentEventRequestHandlerImpl.class)); |
| } |
| |
| /** |
| * Builds the action request handler for Ajax requests, based on {@link org.apache.tapestry5.internal.services.AjaxComponentEventRequestHandler}. |
| * Filters on the request handler are supported here as well. |
| */ |
| @Marker(Ajax.class) |
| public ComponentEventRequestHandler buildAjaxComponentEventRequestHandler( |
| List<ComponentEventRequestFilter> configuration, Logger logger, ServiceResources resources) |
| { |
| return pipelineBuilder.build(logger, ComponentEventRequestHandler.class, ComponentEventRequestFilter.class, |
| configuration, resources.autobuild(AjaxComponentEventRequestHandler.class)); |
| } |
| |
| // ======================================================================== |
| // |
| // Service Contribution Methods (instance) |
| // |
| // ======================================================================== |
| |
| /** |
| * Contributes the default "session" strategy. |
| */ |
| public void contributeApplicationStatePersistenceStrategySource( |
| MappedConfiguration<String, ApplicationStatePersistenceStrategy> configuration, |
| |
| Request request) |
| { |
| configuration.add("session", new SessionApplicationStatePersistenceStrategy(request)); |
| } |
| |
| public void contributeAssetSource(MappedConfiguration<String, AssetFactory> configuration, |
| @ContextProvider AssetFactory contextAssetFactory, |
| |
| @ClasspathProvider AssetFactory classpathAssetFactory) |
| { |
| configuration.add("context", contextAssetFactory); |
| configuration.add("classpath", classpathAssetFactory); |
| } |
| |
| /** |
| * Contributes handlers for the following types: <dl> <dt>Object</dt> <dd>Failure case, added to provide a more |
| * useful exception message</dd> <dt>{@link Link}</dt> <dd>Sends a redirect to the link (which is typically a page |
| * render link)</dd> <dt>String</dt> <dd>Sends a page render redirect</dd> <dt>Class</dt> <dd>Interpreted as the |
| * class name of a page, sends a page render render redirect (this is more refactoring safe than the page name)</dd> |
| * <dt>{@link Component}</dt> <dd>A page's root component (though a non-root component will work, but will generate |
| * a warning). A direct to the containing page is sent.</dd> <dt>{@link org.apache.tapestry5.StreamResponse}</dt> |
| * <dd>The stream response is sent as the actual reply.</dd> <dt>URL</dt> <dd>Sends a redirect to a (presumably) |
| * external URL</dd> </dl> |
| */ |
| public void contributeComponentEventResultProcessor( |
| |
| @InjectService("ComponentInstanceResultProcessor") |
| ComponentEventResultProcessor componentInstanceProcessor, |
| |
| ComponentClassResolver componentClassResolver, |
| |
| final RequestPageCache requestPageCache, |
| |
| MappedConfiguration<Class, ComponentEventResultProcessor> configuration) |
| { |
| configuration.add(Link.class, new ComponentEventResultProcessor<Link>() |
| { |
| public void processResultValue(Link value) throws IOException |
| { |
| response.sendRedirect(value); |
| } |
| }); |
| |
| configuration.add(URL.class, new ComponentEventResultProcessor<URL>() |
| { |
| public void processResultValue(URL value) throws IOException |
| { |
| response.sendRedirect(value.toExternalForm()); |
| } |
| }); |
| |
| configuration.add(String.class, new StringResultProcessor(requestPageCache, actionRenderResponseGenerator)); |
| |
| configuration.add(Class.class, new ClassResultProcessor(componentClassResolver, requestPageCache, |
| actionRenderResponseGenerator)); |
| |
| configuration.add(Component.class, componentInstanceProcessor); |
| |
| configuration.add(StreamResponse.class, new StreamResponseResultProcessor(response)); |
| } |
| |
| |
| /** |
| * Contributes handlers for the following types: <dl> <dt>Object</dt> <dd>Failure case, added to provide more useful |
| * exception message</dd> <dt>{@link RenderCommand}</dt> <dd>Typically, a {@link org.apache.tapestry5.Block}</dd> |
| * <dt>{@link Component}</dt> <dd>Renders the component and its body</dd> <dt>{@link |
| * org.apache.tapestry5.json.JSONObject}</dt> <dd>The JSONObject is returned as a text/javascript response</dd> |
| * <dt>{@link org.apache.tapestry5.StreamResponse}</dt> <dd>The stream response is sent as the actual response</dd> |
| * </dl> |
| */ |
| |
| public void contributeAjaxComponentEventResultProcessor( |
| MappedConfiguration<Class, ComponentEventResultProcessor> configuration, ObjectLocator locator) |
| { |
| configuration.add(RenderCommand.class, locator.autobuild(RenderCommandComponentEventResultProcessor.class)); |
| configuration.add(Component.class, locator.autobuild(AjaxComponentInstanceEventResultProcessor.class)); |
| configuration.add(JSONObject.class, new JSONObjectEventResultProcessor(response)); |
| configuration.add(StreamResponse.class, new StreamResponseResultProcessor(response)); |
| } |
| |
| /** |
| * The MasterDispatcher is a chain-of-command of individual Dispatchers, each handling (like a servlet) a particular |
| * kind of incoming request. <dl> <dt>RootPath</dt> <dd>Renders the start page for the "/" request</dd> |
| * <dt>Asset</dt> <dd>Provides access to classpath assets</dd> <dt>PageRender</dt> <dd>Identifies the {@link |
| * org.apache.tapestry5.services.PageRenderRequestParameters} and forwards onto {@link PageRenderRequestHandler}</dd> |
| * <dt>ComponentEvent</dt> <dd>Identifies the {@link ComponentEventRequestParameters} and forwards onto the {@link |
| * ComponentEventRequestHandler}</dd> </dl> |
| */ |
| public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration, |
| ObjectLocator locator) |
| { |
| // Looks for the root path and renders the start page. This is maintained for compatibility |
| // with earlier versions of Tapestry 5, it is recommended that an Index page be used instead. |
| |
| configuration.add("RootPath", |
| locator.autobuild(RootPathDispatcher.class), |
| "before:Asset"); |
| |
| // This goes first because an asset to be streamed may have an file extension, such as |
| // ".html", that will confuse the later dispatchers. |
| |
| configuration.add("Asset", |
| locator.autobuild(AssetDispatcher.class), "before:ComponentEvent"); |
| |
| |
| configuration.add("ComponentEvent", locator.autobuild(ComponentEventDispatcher.class), |
| "before:PageRender"); |
| |
| configuration.add("PageRender", |
| locator.autobuild(PageRenderDispatcher.class)); |
| } |
| |
| /** |
| * Contributes a default object renderer for type Object, plus specialized renderers for {@link |
| * org.apache.tapestry5.services.Request}, {@link org.apache.tapestry5.ioc.Location}, {@link |
| * org.apache.tapestry5.ComponentResources}, {@link org.apache.tapestry5.EventContext}, List, and Object[]. |
| */ |
| public void contributeObjectRenderer(MappedConfiguration<Class, ObjectRenderer> configuration, |
| |
| @InjectService("LocationRenderer") |
| ObjectRenderer locationRenderer, |
| |
| final TypeCoercer typeCoercer, |
| |
| ObjectLocator locator) |
| { |
| configuration.add(Object.class, new ObjectRenderer() |
| { |
| public void render(Object object, MarkupWriter writer) |
| { |
| writer.write(String.valueOf(object)); |
| } |
| }); |
| |
| configuration.add(Request.class, new RequestRenderer()); |
| |
| configuration.add(Location.class, locationRenderer); |
| |
| ObjectRenderer preformatted = new ObjectRenderer<Object>() |
| { |
| public void render(Object object, MarkupWriter writer) |
| { |
| writer.element("pre"); |
| writer.write(typeCoercer.coerce(object, String.class)); |
| writer.end(); |
| } |
| }; |
| |
| configuration.add(ClassTransformation.class, preformatted); |
| |
| configuration.add(List.class, locator.autobuild(ListRenderer.class)); |
| configuration.add(Object[].class, locator.autobuild(ObjectArrayRenderer.class)); |
| configuration.add(ComponentResources.class, locator.autobuild(ComponentResourcesRenderer.class)); |
| configuration.add(EventContext.class, locator.autobuild(EventContextRenderer.class)); |
| } |
| |
| |
| /** |
| * Adds page render filters, each of which provides an {@link org.apache.tapestry5.annotations.Environmental} service. |
| * Filters often provide {@link Environmental} services needed by components as they render. <dl> |
| * <dt>PageRenderSupport</dt> <dd>Provides {@link org.apache.tapestry5.RenderSupport}</dd> |
| * <dt>ClientBehaviorSupport</dt> <dd>Provides {@link org.apache.tapestry5.internal.services.ClientBehaviorSupport}</dd> |
| * <dt>Heartbeat</dt> <dd>Provides {@link org.apache.tapestry5.services.Heartbeat}</dd> |
| * <dt>DefaultValidationDecorator</dt> <dd>Provides {@link org.apache.tapestry5.ValidationDecorator} (as an instance |
| * of {@link org.apache.tapestry5.internal.DefaultValidationDecorator})</dd> </dl> |
| */ |
| public void contributeMarkupRenderer(OrderedConfiguration<MarkupRendererFilter> configuration, |
| |
| @Symbol(SymbolConstants.PRODUCTION_MODE) |
| final boolean productionMode, |
| |
| @Path("${tapestry.default-stylesheet}") |
| final Asset stylesheetAsset, |
| |
| @Path("${tapestry.field-error-marker}") |
| final Asset fieldErrorIcon, |
| |
| final ValidationMessagesSource validationMessagesSource, |
| |
| final SymbolSource symbolSource, |
| |
| final AssetSource assetSource) |
| { |
| MarkupRendererFilter pageRenderSupport = new MarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) |
| { |
| DocumentLinkerImpl linker = new DocumentLinkerImpl(productionMode); |
| |
| RenderSupportImpl support = new RenderSupportImpl(linker, symbolSource, assetSource, |
| |
| // Core scripts added to any page that uses scripting |
| |
| "${tapestry.scriptaculous}/prototype.js", |
| "${tapestry.scriptaculous}/scriptaculous.js", |
| "${tapestry.scriptaculous}/effects.js", |
| |
| // Uses functions defined by the prior three |
| |
| "org/apache/tapestry5/tapestry.js"); |
| |
| support.addStylesheetLink(stylesheetAsset, null); |
| |
| environment.push(RenderSupport.class, support); |
| |
| renderer.renderMarkup(writer); |
| |
| support.commit(); |
| |
| linker.updateDocument(writer.getDocument()); |
| |
| environment.pop(RenderSupport.class); |
| } |
| }; |
| |
| MarkupRendererFilter clientBehaviorSupport = new MarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) |
| { |
| RenderSupport renderSupport = environment.peekRequired(RenderSupport.class); |
| |
| ClientBehaviorSupportImpl clientBehaviorSupport = new ClientBehaviorSupportImpl(renderSupport); |
| |
| environment.push(ClientBehaviorSupport.class, clientBehaviorSupport); |
| |
| renderer.renderMarkup(writer); |
| |
| environment.pop(ClientBehaviorSupport.class); |
| |
| clientBehaviorSupport.commit(); |
| } |
| }; |
| |
| MarkupRendererFilter heartbeat = new MarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) |
| { |
| Heartbeat heartbeat = new HeartbeatImpl(); |
| |
| heartbeat.begin(); |
| |
| environment.push(Heartbeat.class, heartbeat); |
| |
| renderer.renderMarkup(writer); |
| |
| environment.pop(Heartbeat.class); |
| |
| heartbeat.end(); |
| } |
| }; |
| |
| MarkupRendererFilter defaultValidationDecorator = new MarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) |
| { |
| Messages messages = validationMessagesSource.getValidationMessages(threadLocale.getLocale()); |
| |
| ValidationDecorator decorator = new DefaultValidationDecorator(environment, messages, fieldErrorIcon, |
| writer); |
| |
| environment.push(ValidationDecorator.class, decorator); |
| |
| renderer.renderMarkup(writer); |
| |
| environment.pop(ValidationDecorator.class); |
| } |
| }; |
| |
| |
| configuration.add("RenderSupport", pageRenderSupport); |
| configuration.add("ClientBehaviorSupport", clientBehaviorSupport, "after:RenderSupport"); |
| configuration.add("Heartbeat", heartbeat, "after:RenderSupport"); |
| configuration.add("DefaultValidationDecorator", defaultValidationDecorator, "after:Heartbeat"); |
| } |
| |
| |
| /** |
| * Contributes {@link PartialMarkupRendererFilter}s used when rendering a partial Ajax response. This is an analog |
| * to {@link #contributeMarkupRenderer(org.apache.tapestry5.ioc.OrderedConfiguration, org.apache.tapestry5.Asset, |
| * org.apache.tapestry5.Asset, ValidationMessagesSource, org.apache.tapestry5.ioc.services.SymbolSource, AssetSource)} |
| * } and overlaps it to some degree. <dl> <dt> PageRenderSupport </dt> <dd>Provides {@link |
| * org.apache.tapestry5.RenderSupport}</dd> <dt>ClientBehaviorSupport</dt> <dd>Provides {@link |
| * org.apache.tapestry5.internal.services.ClientBehaviorSupport}</dd> <dt>Heartbeat</dt> <dd>Provides {@link |
| * org.apache.tapestry5.services.Heartbeat}</dd> <dt>DefaultValidationDecorator</dt> <dd>Provides {@link |
| * org.apache.tapestry5.ValidationDecorator} (as an instance of {@link org.apache.tapestry5.internal.DefaultValidationDecorator})</dd> |
| * </dl> |
| */ |
| public void contributePartialMarkupRenderer(OrderedConfiguration<PartialMarkupRendererFilter> configuration, |
| |
| @Path("${tapestry.field-error-marker}") |
| final Asset fieldErrorIcon, |
| |
| final SymbolSource symbolSource, |
| |
| final AssetSource assetSource, |
| |
| final ValidationMessagesSource validationMessagesSource) |
| { |
| PartialMarkupRendererFilter pageRenderSupport = new PartialMarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer) |
| { |
| String uid = Long.toHexString(System.currentTimeMillis()); |
| |
| String namespace = ":" + uid; |
| |
| final StringBuilder buffer = new StringBuilder(1000); |
| |
| IdAllocator idAllocator = new IdAllocator(namespace); |
| |
| DocumentLinker builder = new DocumentLinker() |
| { |
| public void addScriptLink(String scriptURL) |
| { |
| } |
| |
| public void addStylesheetLink(String styleURL, String media) |
| { |
| } |
| |
| public void addScript(String script) |
| { |
| buffer.append(script); |
| buffer.append("\n"); |
| } |
| }; |
| |
| |
| RenderSupportImpl support = new RenderSupportImpl(builder, symbolSource, assetSource, |
| idAllocator); |
| |
| environment.push(RenderSupport.class, support); |
| |
| renderer.renderMarkup(writer, reply); |
| |
| support.commit(); |
| |
| environment.pop(RenderSupport.class); |
| |
| if (buffer.length() > 0) |
| reply.put("script", buffer.toString()); |
| } |
| }; |
| |
| PartialMarkupRendererFilter clientBehaviorSupport = new PartialMarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer) |
| { |
| RenderSupport renderSupport = environment.peekRequired(RenderSupport.class); |
| |
| ClientBehaviorSupportImpl support = new ClientBehaviorSupportImpl(renderSupport); |
| |
| environment.push(ClientBehaviorSupport.class, support); |
| |
| renderer.renderMarkup(writer, reply); |
| |
| environment.pop(ClientBehaviorSupport.class); |
| |
| support.commit(); |
| } |
| }; |
| |
| PartialMarkupRendererFilter heartbeat = new PartialMarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer) |
| { |
| Heartbeat heartbeat = new HeartbeatImpl(); |
| |
| heartbeat.begin(); |
| |
| environment.push(Heartbeat.class, heartbeat); |
| |
| renderer.renderMarkup(writer, reply); |
| |
| environment.pop(Heartbeat.class); |
| |
| heartbeat.end(); |
| } |
| }; |
| |
| PartialMarkupRendererFilter defaultValidationDecorator = new PartialMarkupRendererFilter() |
| { |
| public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer) |
| { |
| Messages messages = validationMessagesSource.getValidationMessages(threadLocale.getLocale()); |
| |
| ValidationDecorator decorator = new DefaultValidationDecorator(environment, messages, fieldErrorIcon, |
| writer); |
| |
| environment.push(ValidationDecorator.class, decorator); |
| |
| renderer.renderMarkup(writer, reply); |
| |
| environment.pop(ValidationDecorator.class); |
| } |
| }; |
| |
| |
| configuration.add("RenderSupport", pageRenderSupport); |
| configuration.add("ClientBehaviorSupport", clientBehaviorSupport, "after:RenderSupport"); |
| configuration.add("Heartbeat", heartbeat, "after:RenderSupport"); |
| configuration.add("DefaultValidationDecorator", defaultValidationDecorator, "after:Heartbeat"); |
| } |
| |
| /** |
| * Contributes several strategies: <dl> <dt>session <dd>Values are stored in the {@link Session} <dt>flash |
| * <dd>Values are stored in the {@link Session}, until the next request (for the page) <dt>client <dd>Values are |
| * encoded into URLs (or hidden form fields) </dl> |
| */ |
| public void contributePersistentFieldManager(MappedConfiguration<String, PersistentFieldStrategy> configuration, |
| |
| Request request, |
| |
| @InjectService("ClientPersistentFieldStrategy") |
| PersistentFieldStrategy clientStrategy) |
| { |
| configuration.add("session", new SessionPersistentFieldStrategy(request)); |
| configuration.add(PersistenceConstants.FLASH, new FlashPersistentFieldStrategy(request)); |
| configuration.add("client", clientStrategy); |
| } |
| |
| public void contributeValidationMessagesSource(Configuration<String> configuration) |
| { |
| configuration.add("org/apache/tapestry5/internal/ValidationMessages"); |
| } |
| |
| public ValueEncoderSource buildValueEncoderSource(Map<Class, ValueEncoderFactory> configuration) |
| { |
| ValueEncoderSourceImpl service = new ValueEncoderSourceImpl(configuration); |
| |
| componentInstantiatorSource.addInvalidationListener(service); |
| |
| return service; |
| } |
| |
| /** |
| * Contributes {@link ValueEncoderFactory}s for types: <ul> <li>Object <li>String <li>Enum </ul> |
| */ |
| @SuppressWarnings("unchecked") |
| public static void contributeValueEncoderSource(MappedConfiguration<Class, ValueEncoderFactory> configuration, |
| ObjectLocator locator) |
| { |
| configuration.add(Object.class, locator.autobuild(TypeCoercedValueEncoderFactory.class)); |
| configuration.add(String.class, GenericValueEncoderFactory.create(new StringValueEncoder())); |
| configuration.add(Enum.class, new EnumValueEncoderFactory()); |
| } |
| |
| |
| /** |
| * Contributes a single filter, "Secure", which checks for non-secure requests that access secure pages. |
| */ |
| public void contributePageRenderRequestHandler(OrderedConfiguration<PageRenderRequestFilter> configuration, |
| final RequestSecurityManager securityManager) |
| { |
| PageRenderRequestFilter secureFilter = new PageRenderRequestFilter() |
| { |
| public void handle(PageRenderRequestParameters parameters, PageRenderRequestHandler handler) throws |
| IOException |
| { |
| |
| if (securityManager.checkForInsecureRequest(parameters.getLogicalPageName())) return; |
| |
| handler.handle(parameters); |
| } |
| }; |
| |
| configuration.add("Secure", secureFilter); |
| } |
| |
| |
| /** |
| * Configures the extensions that will require a digest to be downloaded via the asset dispatcher. Most resources |
| * are "safe", they don't require a digest. For unsafe resources, the digest is incorporated into the URL to ensure |
| * that the client side isn't just "fishing". |
| * <p/> |
| * The extensions must be all lower case. |
| * <p/> |
| * This contributes "class" and "tml" (the template extension). |
| * |
| * @param configuration collection of extensions |
| */ |
| public static void contributeResourceDigestGenerator(Configuration<String> configuration) |
| { |
| // Java class files always require a digest. |
| configuration.add("class"); |
| |
| // Likewise, we don't want people fishing for templates. |
| configuration.add(InternalConstants.TEMPLATE_EXTENSION); |
| } |
| |
| public static void contributeTemplateParser(MappedConfiguration<String, URL> config) |
| { |
| // Any class inside the internal module would do. Or we could move all these |
| // files to o.a.t.services. |
| |
| Class c = UpdateListenerHub.class; |
| config.add("-//W3C//DTD XHTML 1.0 Strict//EN", c.getResource("xhtml1-strict.dtd")); |
| config.add("-//W3C//DTD XHTML 1.0 Transitional//EN", c |
| .getResource("xhtml1-transitional.dtd")); |
| config.add("-//W3C//DTD XHTML 1.0 Frameset//EN", c.getResource("xhtml1-frameset.dtd")); |
| config.add("-//W3C//DTD HTML 4.01//EN", c.getResource("xhtml1-strict.dtd")); |
| config.add("-//W3C//DTD HTML 4.01 Transitional//EN", c |
| .getResource("xhtml1-transitional.dtd")); |
| config.add("-//W3C//DTD HTML 4.01 Frameset//EN", c.getResource("xhtml1-frameset.dtd")); |
| config.add("-//W3C//ENTITIES Latin 1 for XHTML//EN", c.getResource("xhtml-lat1.ent")); |
| config.add("-//W3C//ENTITIES Symbols for XHTML//EN", c.getResource("xhtml-symbol.ent")); |
| config.add("-//W3C//ENTITIES Special for XHTML//EN", c.getResource("xhtml-special.ent")); |
| } |
| |
| /** |
| * Contributes factory defaults that map be overridden. |
| * |
| * @see TapestryModule#contributeClasspathAssetAliasManager(MappedConfiguration, String, String) |
| */ |
| public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration) |
| { |
| // Remember this is request-to-request time, presumably it'll take the developer more than |
| // one second to make a change, save it, and switch back to the browser. |
| |
| configuration.add(SymbolConstants.FILE_CHECK_INTERVAL, "1 s"); |
| configuration.add(SymbolConstants.FILE_CHECK_UPDATE_TIMEOUT, "50 ms"); |
| |
| // This should be overridden for particular applications. |
| configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en,it,zh_CN"); |
| |
| configuration.add(SymbolConstants.TAPESTRY_VERSION, |
| VersionUtils.readVersionNumber( |
| "META-INF/maven/org.apache.tapestry/tapestry-core/pom.properties")); |
| |
| configuration.add("tapestry.default-cookie-max-age", "7 d"); |
| |
| configuration.add("tapestry.start-page-name", "start"); |
| |
| configuration.add("tapestry.default-stylesheet", "org/apache/tapestry5/default.css"); |
| configuration.add("tapestry.field-error-marker", "org/apache/tapestry5/field-error-marker.gif"); |
| |
| configuration.add("tapestry.page-pool.soft-limit", "5"); |
| configuration.add("tapestry.page-pool.soft-wait", "10 ms"); |
| configuration.add("tapestry.page-pool.hard-limit", "20"); |
| configuration.add("tapestry.page-pool.active-window", "10 m"); |
| |
| configuration.add(SymbolConstants.SUPPRESS_REDIRECT_FROM_ACTION_REQUESTS, "false"); |
| |
| configuration.add(SymbolConstants.FORCE_ABSOLUTE_URIS, "false"); |
| |
| configuration.add(SymbolConstants.PRODUCTION_MODE, "true"); |
| |
| configuration.add(SymbolConstants.COMPRESS_WHITESPACE, "true"); |
| |
| configuration.add(MetaDataConstants.SECURE_PAGE, "false"); |
| |
| // This is designed to make it easy to keep synchronized with script.aculo.ous. As we |
| // support a new version, we create a new folder, and update the path entry. We can then |
| // delete the old version folder (or keep it around). This should be more manageable than |
| // overwriting the local copy with updates (it's too easy for files deleted between scriptaculous |
| // releases to be accidentally left lying around). There's also a ClasspathAliasManager |
| // contribution based on the path. |
| |
| configuration.add("tapestry.scriptaculous", "classpath:${tapestry.scriptaculous.path}"); |
| configuration.add("tapestry.scriptaculous.path", "org/apache/tapestry5/scriptaculous_1_8"); |
| |
| // Likewise for WebFX DatePicker, currently version 1.0.6 |
| |
| configuration.add("tapestry.datepicker.path", "org/apache/tapestry5/datepicker_106"); |
| configuration.add("tapestry.datepicker", "classpath:${tapestry.datepicker.path}"); |
| |
| configuration.add(PersistentFieldManagerImpl.META_KEY, PersistentFieldManagerImpl.DEFAULT_STRATEGY); |
| |
| configuration.add(MetaDataConstants.RESPONSE_CONTENT_TYPE, "text/html"); |
| configuration.add(MetaDataConstants.RESPONSE_ENCODING, "UTF-8"); |
| } |
| |
| |
| /** |
| * Adds content types for "css" and "js" file extensions. <dl> <dt>css</dt> <dd>test/css</dd> <dt>js</dt> |
| * <dd>text/javascript</dd> </dl> |
| */ |
| @SuppressWarnings({ "JavaDoc" }) |
| public void contributeResourceStreamer(MappedConfiguration<String, String> configuration) |
| { |
| configuration.add("css", "text/css"); |
| configuration.add("js", "text/javascript"); |
| } |
| |
| /** |
| * Adds a listener to the {@link org.apache.tapestry5.internal.services.ComponentInstantiatorSource} that clears the |
| * {@link PropertyAccess} and {@link TypeCoercer} caches on a class loader invalidation. In addition, forces the |
| * realization of {@link ComponentClassResolver} at startup. |
| */ |
| public void contributeApplicationInitializer(OrderedConfiguration<ApplicationInitializerFilter> configuration, |
| final TypeCoercer typeCoercer, |
| final ComponentClassResolver componentClassResolver) |
| { |
| final InvalidationListener listener = new InvalidationListener() |
| { |
| public void objectWasInvalidated() |
| { |
| propertyAccess.clearCache(); |
| |
| typeCoercer.clearCache(); |
| } |
| }; |
| |
| ApplicationInitializerFilter clearCaches = new ApplicationInitializerFilter() |
| { |
| public void initializeApplication(Context context, ApplicationInitializer initializer) |
| { |
| // Snuck in here is the logic to clear the PropertyAccess service's cache whenever |
| // the component class loader is invalidated. |
| |
| componentInstantiatorSource.addInvalidationListener(listener); |
| |
| initializer.initializeApplication(context); |
| |
| // We don't care about the result, but this forces a load of the service |
| // at application startup, rather than on first request. |
| |
| componentClassResolver.isPageName("ForceLoadAtStartup"); |
| } |
| }; |
| |
| configuration.add("ClearCachesOnInvalidation", clearCaches); |
| } |
| |
| public void contributePropBindingFactory(OrderedConfiguration<BindingFactory> configuration) |
| { |
| BindingFactory keywordFactory = new BindingFactory() |
| { |
| private final Map<String, Object> keywords = CollectionFactory.newCaseInsensitiveMap(); |
| |
| { |
| keywords.put("true", Boolean.TRUE); |
| keywords.put("false", Boolean.FALSE); |
| keywords.put("null", null); |
| } |
| |
| public Binding newBinding(String description, ComponentResources container, ComponentResources component, |
| String expression, Location location) |
| { |
| String key = expression.trim(); |
| |
| if (keywords.containsKey(key)) return new LiteralBinding(description, keywords.get(key), location); |
| |
| return null; |
| } |
| }; |
| |
| BindingFactory thisFactory = new BindingFactory() |
| { |
| |
| public Binding newBinding(String description, ComponentResources container, ComponentResources component, |
| String expression, Location location) |
| { |
| if ("this".equalsIgnoreCase(expression.trim())) |
| return new LiteralBinding(description, container.getComponent(), location); |
| |
| return null; |
| } |
| }; |
| |
| BindingFactory longFactory = new BindingFactory() |
| { |
| private final Pattern pattern = Pattern.compile("^\\s*(-?\\d+)\\s*$"); |
| |
| public Binding newBinding(String description, ComponentResources container, ComponentResources component, |
| String expression, Location location) |
| { |
| Matcher matcher = pattern.matcher(expression); |
| |
| if (matcher.matches()) |
| { |
| String value = matcher.group(1); |
| |
| return new LiteralBinding(description, new Long(value), location); |
| } |
| |
| return null; |
| } |
| }; |
| |
| BindingFactory intRangeFactory = new BindingFactory() |
| { |
| private final Pattern pattern = Pattern |
| .compile("^\\s*(-?\\d+)\\s*\\.\\.\\s*(-?\\d+)\\s*$"); |
| |
| public Binding newBinding(String description, ComponentResources container, ComponentResources component, |
| String expression, Location location) |
| { |
| Matcher matcher = pattern.matcher(expression); |
| |
| if (matcher.matches()) |
| { |
| int start = Integer.parseInt(matcher.group(1)); |
| int finish = Integer.parseInt(matcher.group(2)); |
| |
| IntegerRange range = new IntegerRange(start, finish); |
| |
| return new LiteralBinding(description, range, location); |
| } |
| |
| return null; |
| } |
| }; |
| |
| BindingFactory doubleFactory = new BindingFactory() |
| { |
| // So, either 1234. or 1234.56 or .78 |
| private final Pattern pattern = Pattern |
| .compile("^\\s*(\\-?((\\d+\\.)|(\\d*\\.\\d+)))\\s*$"); |
| |
| public Binding newBinding(String description, ComponentResources container, ComponentResources component, |
| String expression, Location location) |
| { |
| Matcher matcher = pattern.matcher(expression); |
| |
| if (matcher.matches()) |
| { |
| String value = matcher.group(1); |
| |
| return new LiteralBinding(description, new Double(value), location); |
| } |
| |
| return null; |
| } |
| }; |
| |
| BindingFactory stringFactory = new BindingFactory() |
| { |
| // This will match embedded single quotes as-is, no escaping necessary. |
| |
| private final Pattern pattern = Pattern.compile("^\\s*'(.*)'\\s*$"); |
| |
| public Binding newBinding(String description, ComponentResources container, ComponentResources component, |
| String expression, Location location) |
| { |
| Matcher matcher = pattern.matcher(expression); |
| |
| if (matcher.matches()) |
| { |
| String value = matcher.group(1); |
| |
| return new LiteralBinding(description, value, location); |
| } |
| |
| return null; |
| } |
| }; |
| |
| // To be honest, order probably doesn't matter. |
| |
| configuration.add("Keyword", keywordFactory); |
| configuration.add("This", thisFactory); |
| configuration.add("Long", longFactory); |
| configuration.add("IntRange", intRangeFactory); |
| configuration.add("Double", doubleFactory); |
| configuration.add("StringLiteral", stringFactory); |
| } |
| |
| /** |
| * Contributes filters: <dl> <dt>Ajax</dt> <dd>Determines if the request is Ajax oriented, and redirects to an |
| * alternative handler if so</dd> <dt>ImmediateRender</dt> <dd>When {@linkplain |
| * org.apache.tapestry5.SymbolConstants#SUPPRESS_REDIRECT_FROM_ACTION_REQUESTS immediate action response rendering} |
| * is enabled, generates the markup response (instead of a page redirect response, which is the normal behavior) |
| * </dd> <dt>Secure</dt> <dd>Sends a redirect if an non-secure request accesses a secure page</dd></dl> |
| */ |
| public void contributeComponentEventRequestHandler(OrderedConfiguration<ComponentEventRequestFilter> configuration, |
| final RequestSecurityManager requestSecurityManager, |
| @Ajax ComponentEventRequestHandler ajaxHandler, |
| ObjectLocator locator) |
| { |
| ComponentEventRequestFilter secureFilter = new ComponentEventRequestFilter() |
| { |
| public void handle(ComponentEventRequestParameters parameters, ComponentEventRequestHandler handler) |
| throws IOException |
| { |
| if (requestSecurityManager.checkForInsecureRequest(parameters.getActivePageName())) return; |
| |
| handler.handle(parameters); |
| } |
| }; |
| |
| configuration.add("Ajax", new AjaxFilter(request, ajaxHandler)); |
| |
| configuration.add("ImmediateRender", locator.autobuild(ImmediateActionRenderResponseFilter.class)); |
| |
| configuration.add("Secure", secureFilter, "before:Ajax"); |
| } |
| |
| |
| /** |
| * Contributes strategies accessible via the {@link NullFieldStrategySource} service. |
| * <p/> |
| * <dl> <dt>default</dt> <dd>Does nothing, nulls stay null.</dd> <dt>zero</dt> <dd>Null values are converted to |
| * zero.</dd> </dl> |
| */ |
| public static void contributeNullFieldStrategySource(MappedConfiguration<String, NullFieldStrategy> configuration) |
| { |
| configuration.add("default", new DefaultNullFieldStrategy()); |
| configuration.add("zero", new ZeroNullFieldStrategy()); |
| } |
| |
| } |