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