You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2007/11/09 19:30:09 UTC
svn commit: r593617 - in
/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook: patterns.apt
servconf.apt
Author: hlship
Date: Fri Nov 9 10:30:08 2007
New Revision: 593617
URL: http://svn.apache.org/viewvc?rev=593617&view=rev
Log:
Start work on the Tapestry IoC Cookbook.
Added:
tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/patterns.apt
tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/servconf.apt
Added: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/patterns.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/patterns.apt?rev=593617&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/patterns.apt (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/patterns.apt Fri Nov 9 10:30:08 2007
@@ -0,0 +1,138 @@
+ ----
+ Using Patterns
+ ----
+
+Using Patterns
+
+ Tapestry IoC has support for implementing several of the
+ {{{http://en.wikipedia.org/wiki/Design_pattern_(computer_science)}Gang Of Four Design Patterns}}.
+ In fact, the IoC container itself is a pumped up version of the Factory pattern.
+
+ The basis for these patterns is often the use of <service builder methods>, where
+ a {{{servconf.html}configuration}} for the service is combined with a factory to produce
+ the service implementation on the fly.
+
+Chain of Command Pattern
+
+ Let's look at another example, again from the Tapestry code base. The
+ {{{../../apidocs/org/apache/tapestry/services/InjectionProvider.html}InjectProvider}} interface
+ is used to process the @Inject annotation on the fields of a Tapestry page or component.
+
+ The interface has only a single method (this is far from uncommon):
+
++---+
+public interface InjectionProvider
+{
+ boolean provideInjection(String fieldName, Class fieldType, ObjectLocator locator,
+ ClassTransformation transformation, MutableComponentModel componentModel);
+}
++---+
+
+ The return type indicates whether the provider was able to do something. For example,
+ the AssetInjectionProvider checks to see if there's an @Path annotation on the field, and
+ if so, converts the path to an asset, works with the ClassTransformation object to
+ implement injection, and returns true to indicate success. Returns true terminates
+ the chain early, and that true value is ultimately returned to the caller.
+
+ In other cases, it returns false and the chain of command continues down to the
+ next provider. If no provider is capable of
+ handling the injection, then the value false is ultimately returned.
+
+ The InjectionProvider service is built up via contributions. These are the contributions
+ from the TapestryModule:
+
++---+
+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());
+
+ configuration.add(
+ "CommonResources",
+ new CommonResourcesInjectionProvider(),
+ "after:Default");
+
+ configuration.add(
+ "Asset",
+ new AssetInjectionProvider(symbolSource, assetSource),
+ "before:Default");
+
+ configuration.add("Block", new BlockInjectionProvider(), "before:Default");
+ configuration.add("Service", new ServiceInjectionProvider(locator), "after:*");
+}
++---+
+
+ And, of course, other contributions could be made in other modules ... if you wanted to
+ add in your own form of injection.
+
+ The configuration is converted into a service via a service builder method:
+
++----+
+ public InjectionProvider build(List<InjectionProvider> configuration, ChainBuilder chainBuilder)
+ {
+ return chainBuilder.build(InjectionProvider.class, configuration);
+ }
++---+
+
+ Now, let's see how this is used. The InjectWorker class looks for fields with
+ the InjectAnnotation, and uses the chain of command to inject the appropriate value. However,
+ to InjectWorker, there is no chain ... just a <single> object that implements the InjectionProvider interface.
+
++----+
+public class InjectWorker implements ComponentClassTransformWorker
+{
+ private final ObjectLocator _locator;
+
+ // Really, a chain of command
+
+ private final InjectionProvider _injectionProvider;
+
+ public InjectWorker(ObjectLocator locator, InjectionProvider injectionProvider)
+ {
+ _locator = locator;
+ _injectionProvider = injectionProvider;
+ }
+
+ public final void transform(ClassTransformation transformation, MutableComponentModel model)
+ {
+ for (String fieldName : transformation.findFieldsWithAnnotation(Inject.class))
+ {
+ Inject annotation = transformation.getFieldAnnotation(fieldName, Inject.class);
+
+ try
+ {
+ String fieldType = transformation.getFieldType(fieldName);
+
+ Class type = transformation.toClass(fieldType);
+
+ boolean success = _injectionProvider.provideInjection(
+ fieldName,
+ type,
+ _locator,
+ transformation,
+ model);
+
+ if (success) transformation.claimField(fieldName, annotation);
+ }
+ catch (RuntimeException ex)
+ {
+ throw new RuntimeException(ServicesMessages.fieldInjectionError(transformation
+ .getClassName(), fieldName, ex), ex);
+ }
+
+ }
+ }
+}
++----+
+
+ Reducing the chain to a single object vastly simplifies the code: we've <factored out> the loop implicit in the chain of command.
+ That eliminates a lot of code, and that's less code to test, and fewer paths through InjectWorker, which lowers its complexity further.
+ We don't have to test the cases where the list of injection providers is empty, or consists of only a single object, or where it's the third
+ object in that returns true: it looks like a single object, it acts like a single object ... but its implementation uses many objects.
+
Added: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/servconf.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/servconf.apt?rev=593617&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/servconf.apt (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook/servconf.apt Fri Nov 9 10:30:08 2007
@@ -0,0 +1,205 @@
+ ----
+ Service Configurations
+ ----
+
+Service Configurations
+
+ This is an area of Tapestry IoC that is often least well understood. Tapestry services often must have some configuration to fine tune
+ exactly what they do. One of the interactions between modules is that these service configurations are shared: they may
+ be contributed into by any module.
+
+ Let's start with the most basic kind, the unordered configuration.
+
+Unordered Service Configurations
+
+ One of Tapestry's features is the ability to package assets (images, style sheets, javascript libraries, etc.) inside JAR files
+ and expose those to the client. For example, an application URL /assets/org/example/mylib/mylib.js would refer to
+ a file, myllib.js, stored on the classpath in the /org/example/mylib folder.
+
+ That's fine for most cases, but for certain file extensions, we don't want to allow a client browser to "troll" for the files, as the
+ contents could compromise security. For example, downloading a .class file is bad: a clever client might download one that contains
+ a hardcoded user name or password.
+
+ Thus, for certain file extensions, Tapestry guards the resource by attaching an MD5 digest for the resource to the URL.
+ The checksum is derived from the file contents; thus it can't be spoofed from the client unless the client already has the file contents.
+
+ This is controlled by the
+ {{{../../apidocs/org/apache/tapestry/services/ResourceDigestGenerator.html}ResourceDigestGenerator}} service, which uses its
+ configuration to determine which file extensions require an MD5 digest.
+
+* Contributing to a Service
+
+ The Tapestry module makes a contribution into the service configuration:
+
++-----+
+ public static void contributeResourceDigestGenerator(Configuration<String> configuration)
+ {
+ configuration.add("class");
+ configuration.add("tml");
+ }
++----+
+
+ This is a <service contribution method>, a method that is invoked to provide values for a configuration. We'll see how the
+ service receives these contributions shortly. The
+ {{{../../apidocs/org/apache/tapestry/ioc/Configuration.html}Configuration}} object is how
+ values are added to the service's configuration. Other parameters to a service configuration method are injected
+ much as with a service's constructor, or a service builder method.
+
+ How does Tapestry know which service configuration to update? It's from the name of the method, anything
+ after the "contribute" prefix is the id of the service to contribute to (the match against service id is
+ case insensitive).
+
+ Here, the configuration receives two values: "class" (a compiled Java class) and "tml" (a Tapestry component template).
+
+ Say your application stored a file on the classpath needed by your application; for illustrative purposes, perhaps it
+ is a PGP private key. You don't want any client to able to download a .pgp file, no matter how unlikely that
+ would be. Thus:
+
++----+
+public class MyAppModule
+{
+ public static void contributeResourceDigestGenerator(Configuration<String> configuration)
+ {
+ configuration.add("pgp");
+ }
+}
++----+
+
+ The contribution in MyAppModule doesn't <replace> the normal contribution, it is <combined>. The end result is that
+ .class, .tml and .pgp files would <all> be protected.
+
+* Receiving the Configuration
+
+ A service receives the configuration as an injected parameter ... not of type Configuration (that's used for <making> contributions), but
+ instead is of type Collection:
+
++----+
+public class ResourceDigestGeneratorImpl implements ResourceDigestGenerator
+{
+ private final Set<String> _digestExtensions;
+
+ public ResourceDigestGeneratorImpl(Collection<String> configuration)
+ {
+ _digestExtensions = new HashSet<String>(configuration);
+ }
+
+ . . .
+}
++---+
+
+ In many cases, the configuration is simply stored into an instance variable; in this example, the value is transformed
+ from a Collection to a Set.
+
+ These kinds of unordered configurations are surprisingly rare in Tapestry (the only other notable one is for the
+ {{{../coerce.html}TypeCoercer}} service). However, as you can see, setting up such a configuration is quite easy.
+
+Ordered Configurations
+
+ Ordered configurations are very similar to unordered configurations ... the difference is that the configuration
+ is provided to the service as a parameter of type List. This is used when the order of operations counts. Often
+ these configurations are related to a design pattern such as
+ {{{../command.html}Chain of Command}} or
+ {{{../pipeline.html}Pipeline}}.
+
+ Here, the example is the
+ {{{../../apidocs/org/apache/tapestry/services/Dispatcher.html}Dispatcher}} interface; a Dispatcher inside Tapestry
+ is roughly equivalent to a servlet, though a touch more active. It is passed a Request and decides if the URL
+ for the Request is something it can handle; if so it will process the request, send a response, and return true.
+
+ Alternately, if the Request can't be handled, the Dispatcher returns false.
+
++----+
+public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration, . . .)
+{
+ // Looks for the root path and renders the start page
+
+ configuration.add("RootPath", new RootPathDispatcher(. . .), "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",
+ new AssetDispatcher(. . .),
+ "before:PageRender");
+
+ configuration.add("PageRender", new PageRenderDispatcher(. . .));
+
+ configuration.add("ComponentAction", new ComponentActionDispatcher(. . .), "after:PageRender");
+}
++---+
+
+ With an {{{../../apidcos/org/apache/tapestry/ioc/OrderedConfiguration.html}OrderedConfiguration}},
+ each contribution gets a name, which must be unique. Here the names are RootPath, Asset, PageRender and ComponentAction.
+
+ The add() method takes a name, the contributed object for that name, and then zero or more optional constraints.
+ The constraints control the ordering. The "after:" constraint ensures that the contribution is ordered after
+ the other named contribution, the "before:" contribution is the opposite.
+
+ The ordering occurs on the complete set of contributions, from all modules.
+
+ Here, we need a specific order, used to make sure that the Dispatchers don't get confused about which URLs
+ are appropriate ... for example, an asset URL might be /assets/tapestry/tapestry.js. This looks just like
+ a component action URL (for page "assets/tapestry/tapestry" and component "js"). Given that software is totally lacking
+ in basic common-sense, we instead use careful ordering of the Dipstachers to ensure that AssetDispatcher is checked <before>
+ the ComponentAction dispatcher.
+
+* Receiving the Configuration
+
+ The configuration, once assembled and ordered, is provided as a List.
+
+ The MasterDispatcher service configuration defines a {{{../command.apt}Chain of Command}} and we can
+ provide the implementation using virtually no code:
+
++----+
+ public static Dispatcher buildMasterDispatcher(List<Dispatcher> configuration, ChainBuilder chainBuilder)
+ {
+ return chainBuilder.build(Dispatcher.class, configuration);
+ }
++----+
+
+ {{{../../apidocs/org/apache/tapestry/ioc/services/ChainBuilder.html}ChainBuilder}} is a service that
+ <builds other services>. Here it creates an object of type Dispatcher in terms of the list of Dispatchers.
+ This is one of the most common uses of service builder methods ... for when the service implementation
+ doesn't exist, but can be constructed at runtime.
+
+Mapped Configurations
+
+ The last type of service configuration is
+ the mapped service configuration. Here we relate a key, often a string, to some value. The contributions
+ are ultimately combined to form a Map.
+
+ Tapestry IoC's {{{../symbols.html}symbols}} mechanism allows configuration values to be defined and perhaps overridden, then
+ provided to services via injection, using
+ the {{{../../apidocs/org/apache/tapestry/ioc/annotations/Value.html}Value}} annotation.
+
+ The first step is to contribute values.
+
++----+
+ public static void contributeFactoryDefaults(MappedConfiguration<String, String> configuration)
+ {
+ configuration.add("tapestry.file-check-interval", "1000"); // 1 second
+ configuration.add("tapestry.file-check-update-timeout", "50"); // 50 milliseconds
+ configuration.add("tapestry.supported-locales", "en");
+ configuration.add("tapestry.default-cookie-max-age", "604800"); // One week
+ configuration.add("tapestry.start-page-name", "start");
+ configuration.add("tapestry.scriptaculous", "classpath:${tapestry.scriptaculous.path}");
+ configuration.add(
+ "tapestry.scriptaculous.path",
+ "org/apache/tapestry/scriptaculous_1_7_1_beta_3");
+ configuration.add("tapestry.jscalendar.path", "org/apache/tapestry/jscalendar-1.0");
+ configuration.add("tapestry.jscalendar", "classpath:${tapestry.jscalendar.path}");
+ }
++---+
+
+ These contribution set up a number of defaults used to configure various Tapestry services. As you can see, you
+ can even define symbol values in terms of other symbol values.
+
+ Mapped configurations don't have to be keyed on Strings (enums or Class are other common key types). When a mapped
+ configuration <is> keyed on String, then a case-insensitive map is used.
+
+
+
+
+
+
\ No newline at end of file