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