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();