Return-Path: Delivered-To: apmail-tapestry-commits-archive@locus.apache.org Received: (qmail 79571 invoked from network); 9 Nov 2007 18:30:42 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.2) by minotaur.apache.org with SMTP; 9 Nov 2007 18:30:42 -0000 Received: (qmail 82115 invoked by uid 500); 9 Nov 2007 18:30:29 -0000 Delivered-To: apmail-tapestry-commits-archive@tapestry.apache.org Received: (qmail 82086 invoked by uid 500); 9 Nov 2007 18:30:29 -0000 Mailing-List: contact commits-help@tapestry.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@tapestry.apache.org Delivered-To: mailing list commits@tapestry.apache.org Received: (qmail 82077 invoked by uid 99); 9 Nov 2007 18:30:29 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 09 Nov 2007 10:30:29 -0800 X-ASF-Spam-Status: No, hits=-100.0 required=10.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.3] (HELO eris.apache.org) (140.211.11.3) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 09 Nov 2007 18:30:29 +0000 Received: by eris.apache.org (Postfix, from userid 65534) id A67561A9832; Fri, 9 Nov 2007 10:30:09 -0800 (PST) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r593617 - in /tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/cookbook: patterns.apt servconf.apt Date: Fri, 09 Nov 2007 18:30:09 -0000 To: commits@tapestry.apache.org From: hlship@apache.org X-Mailer: svnmailer-1.0.8 Message-Id: <20071109183009.A67561A9832@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org 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 , 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 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 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 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 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 configuration) + { + configuration.add("class"); + configuration.add("tml"); + } ++----+ + + This is a , 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 configuration) + { + configuration.add("pgp"); + } +} ++----+ + + The contribution in MyAppModule doesn't the normal contribution, it is . The end result is that + .class, .tml and .pgp files would be protected. + +* Receiving the Configuration + + A service receives the configuration as an injected parameter ... not of type Configuration (that's used for contributions), but + instead is of type Collection: + ++----+ +public class ResourceDigestGeneratorImpl implements ResourceDigestGenerator +{ + private final Set _digestExtensions; + + public ResourceDigestGeneratorImpl(Collection configuration) + { + _digestExtensions = new HashSet(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 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 + 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 configuration, ChainBuilder chainBuilder) + { + return chainBuilder.build(Dispatcher.class, configuration); + } ++----+ + + {{{../../apidocs/org/apache/tapestry/ioc/services/ChainBuilder.html}ChainBuilder}} is a service that + . 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 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 keyed on String, then a case-insensitive map is used. + + + + + + \ No newline at end of file