tapestry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hls...@apache.org
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 GMT
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



Mime
View raw message