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 2008/08/02 15:49:09 UTC

svn commit: r681975 - in /tapestry/tapestry5/trunk: src/site/ src/site/apt/cookbook/ tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ tapestry-core/src/main/java/org/apache/tapestry5/internal/test/ tapestry-core/src/main/java/org/apa...

Author: hlship
Date: Sat Aug  2 06:49:08 2008
New Revision: 681975

URL: http://svn.apache.org/viewvc?rev=681975&view=rev
Log:
TAPESTRY-2550: Allow Tapestry to bind a service interface to a ServiceBuilder callback to create the service

Added:
    tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBuilder.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/ServiceBuilderModule.java
Modified:
    tapestry/tapestry5/trunk/src/site/site.xml
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageDocumentGeneratorImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableMarkupWriterFactoryImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBinder.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImpl.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceBinderImpl.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/IntegrationTest.java
    tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImplTest.java

Added: tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt?rev=681975&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt (added)
+++ tapestry/tapestry5/trunk/src/site/apt/cookbook/exceptions.apt Sat Aug  2 06:49:08 2008
@@ -0,0 +1,279 @@
+ ---
+ Overriding Exception Reporting
+ ---
+
+Overriding Exception Reporting
+
+  One of Tapestry's best features is its comprehensive exception reporting.  The level of detail is impressive
+  and useful.
+
+  Of course, one of the first questions anyone asks is "How do I turn it off?"  This exception reporting
+  is very helpful for developers but its easy to see it as terrifying for potential users.  Not that you'd have have
+  runtime exceptions in production, of course, but even so ...
+
+Version 1: Replacing the Exception Report Page
+
+  Let's start with a page that fires an exception from an event handler method.
+
+  <<Index.tml>>
+  
+----
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    <head>
+        <title>Index</title>
+    </head>
+    <body>
+        <p>
+            <t:actionlink t:id="fail">click for exception</t:actionlink>
+        </p>
+    </body>
+</html>
+----
+
+  <<Index.java>>
+
+----
+package com.example.tapestry2523.pages;
+
+public class Index
+{
+    void onActionFromFail()
+    {
+        throw new RuntimeException("Failure inside action event handler.");
+    }
+}
+----
+
+  With production mode disabled, clicking the link displays the default exception report page:
+
+[error1.png] Default exception report
+
+  The easy way to override the exception report is to provide an ExceptionReport page that overrides the one
+  provided with the framework.
+
+  This is as easy as providing a page named "ExceptionReport".  It must implement the
+  {{{../apidocs/org/apache/tapestry5/services/ExceptionReporter.html}ExceptionReporter}} interface.
+
+
+  <<ExceptionReport.tml>>
+
+----
+<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
+    <head>
+        <title>Exception</title>
+    </head>
+
+    <body>
+
+        <p>An exception has occurred:
+            <strong>${exception.message}</strong>
+        </p>
+
+        <p>
+            Click
+            <t:pagelink page="index">here</t:pagelink>
+            to restart.
+        </p>
+
+    </body>
+
+</html>
+----
+
+    <<ExceptionReport.java>>
+
+----
+package com.example.tapestry2523.pages;
+
+import org.apache.tapestry5.annotations.Property;
+import org.apache.tapestry5.services.ExceptionReporter;
+
+public class ExceptionReport implements ExceptionReporter
+{
+    @Property
+    private Throwable exception;
+
+    public void reportException(Throwable exception)
+    {
+        this.exception = exception;
+    }
+}
+----
+
+    The end result is a customized exception report page.
+
+[error2.png] Customized Exception Report Page
+
+    
+Version 2: Overriding the RequestExceptionHandler
+
+  The previous example will display a link back to the Index page of the application.
+  Another alternative is to display the error <on> the Index page.  This requires a different
+  approach: overriding the service responsible for reporting request exceptions.
+
+  The service
+  {{{../apidocs/org/apache/tapestry5/services/RequestExceptionHandler.html}RequestExceptionHandler}}
+  is responsible for this.
+
+  By replacing the default implementation of this service with our own implementation, we can take
+  control over exactly what happens when a request exception occurs.
+
+  We'll do this in two steps.  First, we'll extend the Index page to serve as an ExceptionReporter.
+  Second, we'll override the default RequestExceptionHandler to use the Index page instead
+  of the ExceptionReport page.  Of course, this is just one approach.
+
+  <<Index.tml>> (partial)
+
+----
+    <t:if test="message">
+        <p>
+            An unexpected exception has occurred:
+            <strong>${message}</strong>
+        </p>
+    </t:if>
+----
+
+  <<Index.java>>
+
+----
+public class Index implements ExceptionReporter
+{
+    @Property
+    @Persist(PersistenceConstants.FLASH)
+    private String message;
+
+    public void reportException(Throwable exception)
+    {
+        message = exception.getMessage();
+    }
+
+    void onActionFromFail()
+    {
+        throw new RuntimeException("Failure inside action event handler.");
+    }
+}
+----
+
+  The above defines a new property, message, on the Index page.  The @Persist
+  annotation indicates that values assigned to the field will persist from one
+  request to another.  The use of FLASH for the persistence strategy indicates
+  that the value will be used until the next time the page renders, then the value
+  will be discarded.
+
+  The message property is set from the thrown runtime exception.
+
+  The remaining changes take place inside AppModule.
+
+  <<AppModule.java>> (partial)
+
+----
+    public RequestExceptionHandler buildAppRequestExceptionHandler(
+            final Logger logger,
+            final ResponseRenderer renderer,
+            final ComponentSource componentSource)
+    {
+        return new RequestExceptionHandler()
+        {
+            public void handleRequestException(Throwable exception) throws IOException
+            {
+                logger.error("Unexpected runtime exception: " + exception.getMessage(), exception);
+
+                ExceptionReporter index = (ExceptionReporter) componentSource.getPage("Index");
+
+                index.reportException(exception);
+
+                renderer.renderPageMarkupResponse("Index");
+            }
+        };
+    }
+
+    public void contributeAlias(
+            Configuration<AliasContribution> configuration,
+
+            @InjectService("AppRequestExceptionHandler")
+            RequestExceptionHandler handler)
+    {
+        configuration.add(AliasContribution.create(RequestExceptionHandler.class, handler));
+    }  
+----
+
+  First we define the new service using a service builder method.  This is an alternative
+  to the bind() method; we define the service, its interface type (the return type of the method)
+  and the service id (the part that follows "build" is the method name) and provide the implementation
+  inline.  A service builder method must return the service implementation, here implemented as an inner class.
+
+  The Logger resource that is passed into the builder method is the Logger appropriate for the service.
+  ResponseRenderer and ComponentSource are two services defined by Tapestry.
+
+  With this in place, there are now two different services that implement the RequestExceptionHandler interface:
+  the default one built into Tapestry (whose service id is "RequestExceptionHandler") and the new one defined
+  in this module, "AppRequestExceptionHandler").  Without a little more work, Tapestry will be unable to determine
+  which one to use when an exception does occur.
+
+  The contributeAlias() method makes a contribution to the Alias service's configuration.  The Alias service
+  is used to disambiguate injections when just a service interface is given; one service will become the "Alias", hiding the
+  existence of the others.
+
+  Here we inject the AppRequestExceptionHandler service and contribute it as the alias for type RequestExceptionHandler.
+    
+  This finally brings us to the point where we can see the result:
+
+[error3.png] Errors show on Index page
+
+   
+Version 3: Decorating the RequestExceptionHandler
+
+  A third option is available: we don't define a <new> service, but instead <decorate> the existing
+  RequestExceptionHandler service. This approach means we don't have to make a contribution to the Alias service.
+
+  Service decoration is a powerful facility of Tapestry that is generally used to "wrap" an existing service in
+  an interceptor that provides new functionality such as logging, security, transaction management or other cross-cutting
+  concerns.
+
+  However, there's no requirement that an interceptor for a service actually invoke methods on the service; here
+  we contribute a new implementation that <replaces> the original:
+
+  <<AppModule.java>>
+
+----
+    public RequestExceptionHandler decorateRequestExceptionHandler(
+            final Logger logger,
+            final ResponseRenderer renderer,
+            final ComponentSource componentSource,
+            @Symbol(SymbolConstants.PRODUCTION_MODE)
+            boolean productionMode,
+            Object service)
+    {
+        if (!productionMode) return null;
+
+        return new RequestExceptionHandler()
+        {
+            public void handleRequestException(Throwable exception) throws IOException
+            {
+                logger.error("Unexpected runtime exception: " + exception.getMessage(), exception);
+
+                ExceptionReporter index = (ExceptionReporter) componentSource.getPage("Index");
+
+                index.reportException(exception);
+
+                renderer.renderPageMarkupResponse("Index");
+            }
+        };
+    }
+----
+
+  As with service builder methods and service configuration method, decorator methods are recognized
+  by the "decorate" prefix on the method name.  As used here, the rest of the method name is used
+  to identify the service to be decorated (there are other options that allow a decorator to be applied
+  to many different services).
+
+  A change in this version is that when in development mode (that is, when <not> in production mode) we use
+  the normal implementation.  Returning null from a service decoration method indicates that the decorator
+  chooses not to decorate.
+
+  The Logger injected here is the Logger for the service being decorated, the default RequestExceptionHandler service.
+
+  Otherwise, we return an interceptor whose implementation is the same as the new service in version #2.
+
+  The end result is that in development mode we get the full exception report, and in production mode we
+  get an abbreviated message on the application's Index page.

Modified: tapestry/tapestry5/trunk/src/site/site.xml
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/site.xml?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/site.xml (original)
+++ tapestry/tapestry5/trunk/src/site/site.xml Sat Aug  2 06:49:08 2008
@@ -69,6 +69,7 @@
             <item name="Introduction" href="cookbook/"/>
             <item name="Supporting Informal Parameters" href="cookbook/informals.html"/>
             <item name="Default Parameter" href="cookbook/defaultparameter.html"/>
+            <item name="Exception Reporting" href="cookbook/exceptions.html"/>
         </menu>
 
         <menu name="Tapestry 5 Maven Support">

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/MarkupWriterFactoryImpl.java Sat Aug  2 06:49:08 2008
@@ -20,14 +20,25 @@
 import org.apache.tapestry5.dom.MarkupModel;
 import org.apache.tapestry5.dom.XMLMarkupModel;
 import org.apache.tapestry5.internal.InternalConstants;
+import org.apache.tapestry5.internal.structure.Page;
 import org.apache.tapestry5.services.MarkupWriterFactory;
 
 public class MarkupWriterFactoryImpl implements MarkupWriterFactory
 {
+    private final PageContentTypeAnalyzer analyzer;
+
+    private final RequestPageCache cache;
+
     private final MarkupModel htmlModel = new DefaultMarkupModel();
 
     private final MarkupModel xmlModel = new XMLMarkupModel();
 
+    public MarkupWriterFactoryImpl(PageContentTypeAnalyzer analyzer, RequestPageCache cache)
+    {
+        this.analyzer = analyzer;
+        this.cache = cache;
+    }
+
     public MarkupWriter newMarkupWriter(ContentType contentType)
     {
         boolean isHTML = contentType.getMimeType().equalsIgnoreCase("text/html");
@@ -40,4 +51,13 @@
         return new MarkupWriterImpl(model, contentType.getParameter(InternalConstants.CHARSET_CONTENT_TYPE_PARAMETER));
     }
 
+    public MarkupWriter newMarkupWriter(String pageName)
+    {
+        Page page = cache.get(pageName);
+
+        ContentType contentType = analyzer.findContentType(page);
+
+        return newMarkupWriter(contentType);
+    }
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageDocumentGeneratorImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageDocumentGeneratorImpl.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageDocumentGeneratorImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageDocumentGeneratorImpl.java Sat Aug  2 06:49:08 2008
@@ -14,7 +14,6 @@
 
 package org.apache.tapestry5.internal.services;
 
-import org.apache.tapestry5.ContentType;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.dom.Document;
 import org.apache.tapestry5.internal.InternalConstants;
@@ -31,17 +30,14 @@
 
     private final MarkupWriterFactory markupWriterFactory;
 
-    private final PageContentTypeAnalyzer pageContentTypeAnalyzer;
-
     private final Request request;
 
     public PageDocumentGeneratorImpl(RequestPageCache pageCache, PageMarkupRenderer markupRenderer,
                                      MarkupWriterFactory markupWriterFactory,
-                                     PageContentTypeAnalyzer pageContentTypeAnalyzer, Request request)
+                                     Request request)
     {
         this.markupRenderer = markupRenderer;
         this.markupWriterFactory = markupWriterFactory;
-        this.pageContentTypeAnalyzer = pageContentTypeAnalyzer;
         this.pageCache = pageCache;
         this.request = request;
     }
@@ -50,9 +46,7 @@
     {
         Page page = pageCache.get(logicalPageName);
 
-        ContentType contentType = pageContentTypeAnalyzer.findContentType(page);
-
-        MarkupWriter writer = markupWriterFactory.newMarkupWriter(contentType);
+        MarkupWriter writer = markupWriterFactory.newMarkupWriter(logicalPageName);
 
         // value will almost certainly be null, unless a page that is being rendered to a document
         // itself decides to render another page to a document.

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableMarkupWriterFactoryImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableMarkupWriterFactoryImpl.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableMarkupWriterFactoryImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/TestableMarkupWriterFactoryImpl.java Sat Aug  2 06:49:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -43,10 +43,20 @@
 
     public MarkupWriter newMarkupWriter(ContentType contentType)
     {
-        MarkupWriter result = delegate.newMarkupWriter(contentType);
+        return save(delegate.newMarkupWriter(contentType));
+    }
 
-        lastCreated = result;
+    public MarkupWriter newMarkupWriter(String pageName)
+    {
+        return save(delegate.newMarkupWriter(pageName));
+    }
 
-        return result;
+    private MarkupWriter save(MarkupWriter writer)
+    {
+        lastCreated = writer;
+
+        return writer;
     }
+
+
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/MarkupWriterFactory.java Sat Aug  2 06:49:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -30,4 +30,12 @@
      *                    the backs the markup writer.
      */
     MarkupWriter newMarkupWriter(ContentType contentType);
+
+    /**
+     * Obtains a markup writer that will render the content for the provided logical page name.
+     *
+     * @param pageName logical page name
+     * @return writer configured for the page
+     */
+    MarkupWriter newMarkupWriter(String pageName);
 }

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBinder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBinder.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBinder.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBinder.java Sat Aug  2 06:49:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -31,12 +31,22 @@
      * fully on first reference (ignoring its scope, if any) and will not be decorated.
      *
      * @param <T>
-     * @param implementationClass
-     * @return
+     * @param implementationClass class to instantiate as the service
+     * @return binding options, used to specify additional details about the service
      */
     <T> ServiceBindingOptions bind(Class<T> implementationClass);
 
     /**
+     * Alternative implementation that supports a callback to build the service, rather than instantiating a particular
+     * class.
+     *
+     * @param serviceInterface interface implemented by the service
+     * @param builder          constructs the core service implementation
+     * @return binding options, used to specify additional details about the service
+     */
+    <T> ServiceBindingOptions bind(Class<T> serviceInterface, ServiceBuilder<T> builder);
+
+    /**
      * Binds the service interface to a service implementation class. The default service name is the unqualified name
      * of the service interface. The default service scope is "singleton", unless the service implementation class
      * includes the {@link Scope} annotation.
@@ -44,7 +54,7 @@
      * @param <T>
      * @param serviceInterface      service interface (used when locating services, and when building proxies)
      * @param serviceImplementation implementation class that implements the service interface
-     * @return binding options, used to specify additional details about the service.
+     * @return binding options, used to specify additional details about the service
      */
     <T> ServiceBindingOptions bind(Class<T> serviceInterface,
                                    Class<? extends T> serviceImplementation);

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBuilder.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBuilder.java?rev=681975&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBuilder.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/ServiceBuilder.java Sat Aug  2 06:49:08 2008
@@ -0,0 +1,29 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.ioc;
+
+/**
+ * A callback used to create a service implementation.
+ */
+public interface ServiceBuilder<T>
+{
+    /**
+     * Construct the service.  A non-null object that implements the service interface must be returned.
+     *
+     * @param resources used to lookup dependencies or access resources
+     * @return the core service implementation
+     */
+    T buildService(ServiceResources resources);
+}

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImpl.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImpl.java Sat Aug  2 06:49:08 2008
@@ -247,7 +247,7 @@
 
         // TODO: Validate constraints here?
 
-        String[] patterns = match == null ? new String[] { decoratorId } : match.value();
+        String[] patterns = match == null ? new String[]{decoratorId} : match.value();
 
         DecoratorDef def = new DecoratorDefImpl(decoratorId, method, patterns, constraints, classFactory);
 
@@ -385,7 +385,7 @@
                 return;
             }
 
-            ServiceBinderImpl binder = new ServiceBinderImpl(this, classFactory, defaultMarkers);
+            ServiceBinderImpl binder = new ServiceBinderImpl(this, bindMethod, classFactory, defaultMarkers);
 
             bindMethod.invoke(null, binder);
 

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceBinderImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceBinderImpl.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceBinderImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/internal/ServiceBinderImpl.java Sat Aug  2 06:49:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2007 The Apache Software Foundation
+// Copyright 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
 import org.apache.tapestry5.ioc.annotations.Scope;
 import org.apache.tapestry5.ioc.def.ServiceDef;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.Defense;
 import static org.apache.tapestry5.ioc.internal.util.Defense.notBlank;
 import static org.apache.tapestry5.ioc.internal.util.Defense.notNull;
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
@@ -28,6 +29,7 @@
 
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
 import java.util.Arrays;
 import java.util.Set;
 
@@ -35,15 +37,20 @@
 {
     private final OneShotLock lock = new OneShotLock();
 
+    private final Method bindMethod;
+
     private final ServiceDefAccumulator accumulator;
 
     private final ClassFactory classFactory;
 
     private final Set<Class> defaultMarkers;
 
-    public ServiceBinderImpl(ServiceDefAccumulator accumulator, ClassFactory classFactory, Set<Class> defaultMarkers)
+    public ServiceBinderImpl(ServiceDefAccumulator accumulator, Method bindMethod,
+                             ClassFactory classFactory,
+                             Set<Class> defaultMarkers)
     {
         this.accumulator = accumulator;
+        this.bindMethod = bindMethod;
         this.classFactory = classFactory;
         this.defaultMarkers = defaultMarkers;
     }
@@ -52,9 +59,11 @@
 
     private Class serviceInterface;
 
+    private Class serviceImplementation;
+
     private final Set<Class> markers = CollectionFactory.newSet();
 
-    private Class serviceImplementation;
+    private ObjectCreatorSource source;
 
     private boolean eagerLoad;
 
@@ -71,20 +80,11 @@
     {
         if (serviceInterface == null) return;
 
-        final Constructor constructor = findConstructor();
+        // source will be null when the implementation class is provided; non-null when using
+        // a ServiceBuilder callback
 
-        ObjectCreatorSource source = new ObjectCreatorSource()
-        {
-            public ObjectCreator constructCreator(ServiceBuilderResources resources)
-            {
-                return new ConstructorServiceCreator(resources, getDescription(), constructor);
-            }
-
-            public String getDescription()
-            {
-                return classFactory.getConstructorLocation(constructor).toString();
-            }
-        };
+        if (source == null)
+            source = createObjectCreatorSourceFromImplementationClass();
 
         // Combine service-specific markers with those inherited form the module.
         Set<Class> markers = CollectionFactory.newSet(defaultMarkers);
@@ -96,31 +96,43 @@
 
         serviceId = null;
         serviceInterface = null;
-        this.markers.clear();
         serviceImplementation = null;
+        source = null;
+        this.markers.clear();
         eagerLoad = false;
         scope = null;
     }
 
-    private Constructor findConstructor()
+    private ObjectCreatorSource createObjectCreatorSourceFromImplementationClass()
     {
-        Constructor result = InternalUtils.findAutobuildConstructor(serviceImplementation);
+        final Constructor constructor = InternalUtils.findAutobuildConstructor(serviceImplementation);
 
-        if (result == null) throw new RuntimeException(IOCMessages
-                .noConstructor(serviceImplementation, serviceId));
+        if (constructor == null)
+            throw new RuntimeException(IOCMessages.noConstructor(serviceImplementation, serviceId));
 
-        return result;
+        return new ObjectCreatorSource()
+        {
+            public ObjectCreator constructCreator(ServiceBuilderResources resources)
+            {
+                return new ConstructorServiceCreator(resources, getDescription(), constructor);
+            }
+
+            public String getDescription()
+            {
+                return classFactory.getConstructorLocation(constructor).toString();
+            }
+        };
     }
 
     public <T> ServiceBindingOptions bind(Class<T> serviceClass)
     {
-        if(serviceClass.isInterface())
+        if (serviceClass.isInterface())
         {
             try
             {
-                Class<T> implementationClass = (Class<T>) Class.forName(serviceClass.getName()+"Impl");
-                
-                if(!implementationClass.isInterface() && serviceClass.isAssignableFrom(implementationClass))
+                Class<T> implementationClass = (Class<T>) Class.forName(serviceClass.getName() + "Impl");
+
+                if (!implementationClass.isInterface() && serviceClass.isAssignableFrom(implementationClass))
                 {
                     return bind(serviceClass, implementationClass);
                 }
@@ -131,9 +143,46 @@
                 throw new RuntimeException(IOCMessages.noConventionServiceImplementationFound(serviceClass));
             }
         }
+
         return bind(serviceClass, serviceClass);
     }
 
+    public <T> ServiceBindingOptions bind(Class<T> serviceInterface, final ServiceBuilder<T> builder)
+    {
+        Defense.notNull(serviceInterface, "serviceInterface");
+        Defense.notNull(builder, "builder");
+
+        lock.check();
+
+        flush();
+
+        this.serviceInterface = serviceInterface;
+        this.scope = IOCConstants.DEFAULT_SCOPE;
+
+        serviceId = serviceInterface.getSimpleName();
+
+        this.source = new ObjectCreatorSource()
+        {
+            public ObjectCreator constructCreator(final ServiceBuilderResources resources)
+            {
+                return new ObjectCreator()
+                {
+                    public Object createObject()
+                    {
+                        return builder.buildService(resources);
+                    }
+                };
+            }
+
+            public String getDescription()
+            {
+                return classFactory.getMethodLocation(bindMethod).toString();
+            }
+        };
+
+        return this;
+    }
+
     public <T> ServiceBindingOptions bind(Class<T> serviceInterface, Class<? extends T> serviceImplementation)
     {
         notNull(serviceInterface, "serviceIterface");
@@ -144,6 +193,7 @@
         flush();
 
         this.serviceInterface = serviceInterface;
+
         this.serviceImplementation = serviceImplementation;
 
         // Set defaults for the other properties.

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/site/apt/service.apt Sat Aug  2 06:49:08 2008
@@ -109,8 +109,8 @@
   addition, inspired by the terseness of the {{{http://code.google.com/p/google-guice/}Guice}} IoC container.
 
 
-  Following the convention over configuration principle the autobuilding of services can be done even less verbose.
-  If a service interface is passed as a single argument to the bind() method Tapestry will try to find an implementation in the
+  Following the convention over configuration principle, the autobuilding of services can be even less verbose.
+  If a service interface is passed as a single argument to the bind() method, Tapestry will try to find an implementation in the
   same package whose name matches the name of the service interface followed by the suffix <Impl>.
   
 +------+
@@ -590,7 +590,15 @@
   
   Here, the alertEmail parameter will recieve the configured alerts email (see
   {{{symbols.html}the symbols documentation}} for more about this syntax) rather than the service id.
-  
+
+Binding ServiceBuilders
+
+  Yet another option is available: instead of binding an interface to a implemention class,
+  you can bind a service to a
+  {{{../apidocs/org/apache/tapestry5/ioc/ServiceBuilder.html}ServiceBuilder}}, a callback used
+  to create the service implementation.  This is very useful in very rare circumstances.
+
+
 Builtin Services
 
   A few services within the Tapestry IOC Module are "builtin"; there is no 

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/IntegrationTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/IntegrationTest.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/IntegrationTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/IntegrationTest.java Sat Aug  2 06:49:08 2008
@@ -401,7 +401,7 @@
 
         r.shutdown();
     }
-    
+
     @Test
     public void convention_over_configuration_service()
     {
@@ -409,14 +409,14 @@
 
 
         StringHolder holder = r.getService(StringHolder.class);
-        
+
         holder.setValue("Bar");
 
         assertEquals(holder.getValue(), "Bar");
 
         r.shutdown();
     }
-    
+
     @Test
     public void convention_over_configuration_service_impl_not_found()
     {
@@ -424,12 +424,14 @@
         {
             buildRegistry(ConventionModuleImplementationNotFound.class);
             unreachable();
-        }catch (RuntimeException ex) {
+        }
+        catch (RuntimeException ex)
+        {
             assertMessageContains(ex,
-            "No service implements the interface "+StringTransformer.class.getName()+". Please provide");
+                                  "No service implements the interface " + StringTransformer.class.getName() + ". Please provide");
         }
     }
-    
+
     @Test
     public void convention_over_configuration_service_wrong_impl_found()
     {
@@ -437,9 +439,11 @@
         {
             buildRegistry(ConventionFailureModule.class);
             unreachable();
-        }catch (RuntimeException ex) {
+        }
+        catch (RuntimeException ex)
+        {
             assertMessageContains(ex,
-            "No service implements the interface "+Pingable.class.getName());
+                                  "No service implements the interface " + Pingable.class.getName());
         }
     }
 
@@ -822,4 +826,38 @@
 
         r.shutdown();
     }
+
+    @Test
+    public void bind_to_service_builder()
+    {
+        Registry r = buildRegistry(ServiceBuilderModule.class);
+
+        Greeter g = r.getService("Greeter", Greeter.class);
+
+        assertEquals(g.getGreeting(), "Greetings from service Greeter.");
+
+        r.shutdown();
+    }
+
+    @Test
+    public void bind_to_service_binder_that_throws_exception()
+    {
+        Registry r = buildRegistry(ServiceBuilderModule.class);
+
+        Greeter g = r.getService("BrokenGreeter", Greeter.class);
+
+        try
+        {
+            g.getGreeting();
+            unreachable();
+        }
+        catch (Exception ex)
+        {
+            assertEquals(ex.getMessage(),
+                         "Exception constructing service 'BrokenGreeter': Failure inside ServiceBuilder callback.");
+        }
+
+        r.shutdown();
+
+    }
 }

Added: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/ServiceBuilderModule.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/ServiceBuilderModule.java?rev=681975&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/ServiceBuilderModule.java (added)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/ServiceBuilderModule.java Sat Aug  2 06:49:08 2008
@@ -0,0 +1,47 @@
+//  Copyright 2008 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.ioc;
+
+/**
+ * Used to test the functionality related to {@link org.apache.tapestry5.ioc.ServiceBinder#bind(Class,
+ * ServiceBuilder)}.
+ */
+public class ServiceBuilderModule
+{
+    public static void bind(ServiceBinder binder)
+    {
+        binder.bind(Greeter.class, new ServiceBuilder<Greeter>()
+        {
+            public Greeter buildService(ServiceResources resources)
+            {
+                return new Greeter()
+                {
+                    public String getGreeting()
+                    {
+                        return "Greetings from service Greeter.";
+                    }
+                };
+            }
+        });
+
+        binder.bind(Greeter.class, new ServiceBuilder<Greeter>()
+        {
+            public Greeter buildService(ServiceResources resources)
+            {
+                throw new RuntimeException("Failure inside ServiceBuilder callback.");
+            }
+        }).withId("BrokenGreeter");
+    }
+}

Modified: tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImplTest.java?rev=681975&r1=681974&r2=681975&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImplTest.java (original)
+++ tapestry/tapestry5/trunk/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/DefaultModuleDefImplTest.java Sat Aug  2 06:49:08 2008
@@ -1,4 +1,4 @@
-// Copyright 2006, 2007 The Apache Software Foundation
+// Copyright 2006, 2007, 2008 The Apache Software Foundation
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -384,8 +384,8 @@
         }
         catch (RuntimeException ex)
         {
-            assertEquals(ex.getMessage(),
-                         "Class org.apache.tapestry5.ioc.internal.RunnableServiceImpl (implementation of service \'Runnable\') does not contain any public constructors.");
+            assertMessageContains(ex,
+                                  "Class org.apache.tapestry5.ioc.internal.RunnableServiceImpl (implementation of service \'Runnable\') does not contain any public constructors.");
         }
 
         verify();