You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2006/07/23 23:21:47 UTC

svn commit: r424822 - in /tapestry/tapestry5/tapestry-core/trunk/src: main/aspect/org/apache/tapestry/internal/aspects/ main/java/org/apache/tapestry/annotations/ main/java/org/apache/tapestry/internal/ioc/ main/java/org/apache/tapestry/internal/ioc/se...

Author: hlship
Date: Sun Jul 23 14:21:45 2006
New Revision: 424822

URL: http://svn.apache.org/viewvc?rev=424822&view=rev
Log:
Implement a basic LoggingDecorator service, that provides a logger interceptor for services.

Added:
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImplTest.java
Modified:
    tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/InternalOneShotChecks.aj
    tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/OneShotChecks.aj
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Match.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvoker.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFabImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/Logger.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImpl.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ServiceMessages.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/LoggingDecorator.java
    tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/decorator.apt
    tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvokerTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImplTest.java
    tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggerTest.java

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/InternalOneShotChecks.aj
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/InternalOneShotChecks.aj?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/InternalOneShotChecks.aj (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/InternalOneShotChecks.aj Sun Jul 23 14:21:45 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 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.tapestry.internal.aspects;
 
 public aspect InternalOneShotChecks extends OneShotChecks

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/OneShotChecks.aj
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/OneShotChecks.aj?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/OneShotChecks.aj (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/aspect/org/apache/tapestry/internal/aspects/OneShotChecks.aj Sun Jul 23 14:21:45 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 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.tapestry.internal.aspects;
 
 import org.apache.tapestry.internal.annotations.OneShot;

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Match.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Match.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Match.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/annotations/Match.java Sun Jul 23 14:21:45 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 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.tapestry.annotations;
 
 import static java.lang.annotation.ElementType.METHOD;

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvoker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvoker.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvoker.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvoker.java Sun Jul 23 14:21:45 2006
@@ -17,6 +17,7 @@
 import static org.apache.tapestry.internal.ioc.IOCUtilities.calculateParametersForMethod;
 import static org.apache.tapestry.util.CollectionFactory.newMap;
 
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Map;
 
@@ -74,18 +75,26 @@
             _log.debug(IOCMessages.invokingMethod(_builderMethod));
 
         Object result = null;
+        Throwable failure = null;
 
         try
         {
             result = _builderMethod.invoke(_moduleBuilder, parameters);
         }
+        catch (InvocationTargetException ite)
+        {
+            failure = ite.getTargetException();
+        }
         catch (Exception ex)
         {
+            failure = ex;
+        }
+
+        if (failure != null)
             throw new RuntimeException(IOCMessages.builderMethodError(
                     _builderMethod,
                     _serviceId,
-                    ex), ex);
-        }
+                    failure), failure);
 
         if (result == null)
             throw new RuntimeException(IOCMessages.builderMethodReturnedNull(

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImpl.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImpl.java Sun Jul 23 14:21:45 2006
@@ -17,6 +17,7 @@
 import static org.apache.tapestry.internal.ioc.IOCUtilities.calculateParametersForMethod;
 import static org.apache.tapestry.util.CollectionFactory.newMap;
 
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Map;
 
@@ -72,19 +73,31 @@
                 _resources,
                 parameterDefaults);
 
+        if (_log.isDebugEnabled())
+            _log.debug(IOCMessages.invokingMethod(_decoratorMethod));
+
         Object result = null;
+        Throwable failure = null;
 
         try
         {
             result = _decoratorMethod.invoke(_moduleBuilder, parameters);
         }
+        catch (InvocationTargetException ite)
+        {
+            failure = ite.getTargetException();
+        }
+
         catch (Exception ex)
         {
+            failure = ex;
+        }
+
+        if (failure != null)
             throw new RuntimeException(IOCMessages.decoratorMethodError(
                     _decoratorMethod,
                     _serviceId,
-                    ex), ex);
-        }
+                    failure), failure);
 
         if (result != null && !_serviceInterface.isInstance(result))
         {

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFabImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFabImpl.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFabImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ClassFabImpl.java Sun Jul 23 14:21:45 2006
@@ -14,9 +14,6 @@
 
 package org.apache.tapestry.internal.ioc.services;
 
-import static java.lang.String.format;
-import static org.apache.tapestry.util.CollectionFactory.newSet;
-
 import java.lang.reflect.Modifier;
 import java.util.Set;
 
@@ -34,6 +31,9 @@
 import org.apache.tapestry.ioc.services.MethodSignature;
 import org.apache.tapestry.util.Defense;
 
+import static java.lang.String.format;
+import static org.apache.tapestry.util.CollectionFactory.newSet;
+
 /**
  * Implementation of {@link org.apache.tapestry.ioc.services.ClassFab}. Hides, as much as possible,
  * the underlying library (Javassist).
@@ -61,6 +61,7 @@
      * 
      * @since 1.1
      */
+    @Override
     public String toString()
     {
         StringBuffer buffer = new StringBuffer("ClassFab[\n");

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/Logger.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/Logger.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/Logger.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/Logger.java Sun Jul 23 14:21:45 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 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.tapestry.internal.ioc.services;
 
 import static java.lang.String.format;

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImpl.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImpl.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImpl.java Sun Jul 23 14:21:45 2006
@@ -1,7 +1,34 @@
+// Copyright 2006 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.tapestry.internal.ioc.services;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.logging.Log;
+import org.apache.hivemind.service.BodyBuilder;
 import org.apache.tapestry.ioc.LoggingDecorator;
+import org.apache.tapestry.ioc.services.ClassFab;
+import org.apache.tapestry.ioc.services.ClassFabUtils;
 import org.apache.tapestry.ioc.services.ClassFactory;
+import org.apache.tapestry.ioc.services.MethodIterator;
+import org.apache.tapestry.ioc.services.MethodSignature;
+
+import static java.lang.String.format;
+import static org.apache.tapestry.ioc.services.ClassFabUtils.getJavaClassName;
 
 /**
  * @author Howard M. Lewis Ship
@@ -15,9 +42,136 @@
         _classFactory = classFactory;
     }
 
-    public <T> T createLoggingInterceptor(Class<T> serviceInterface, T delegate)
+    public <T> T createLoggingInterceptor(Class<T> serviceInterface, T delegate, String serviceId,
+            Log serviceLog)
+    {
+        Class interceptorClass = createInterceptorClass(serviceInterface, serviceId);
+
+        Logger logger = new Logger(serviceLog);
+
+        Constructor cc = interceptorClass.getConstructors()[0];
+
+        Object interceptor = null;
+        Throwable fail = null;
+
+        try
+        {
+            interceptor = cc.newInstance(delegate, logger);
+        }
+        catch (InvocationTargetException ite)
+        {
+            fail = ite.getTargetException();
+        }
+        catch (Exception ex)
+        {
+            fail = ex;
+        }
+
+        if (fail != null)
+            throw new RuntimeException(fail);
+
+        return serviceInterface.cast(interceptor);
+    }
+
+    private Class createInterceptorClass(Class serviceInterface, String serviceId)
+    {
+        String className = ClassFabUtils.generateClassName(serviceInterface);
+        ClassFab cf = _classFactory.newClass(className, Object.class);
+
+        addInfrastructure(cf, serviceInterface);
+
+        addMethods(cf, serviceInterface, serviceId);
+
+        return cf.createClass();
+    }
+
+    private void addMethods(ClassFab cf, Class serviceInterface, String serviceId)
+    {
+        MethodIterator mi = new MethodIterator(serviceInterface);
+
+        while (mi.hasNext())
+        {
+            addMethod(cf, mi.next());
+        }
+
+        if (!mi.getToString())
+            ClassFabUtils.addToStringMethod(cf, ServiceMessages.loggingInterceptor(
+                    serviceId,
+                    serviceInterface));
+    }
+
+    private void addMethod(ClassFab cf, MethodSignature signature)
+    {
+        String name = '"' + signature.getName() + '"';
+        Class returnType = signature.getReturnType();
+        boolean isVoid = returnType.equals(void.class);
+
+        // We'll see how well Javassist handles void methods with this setup
+
+        BodyBuilder builder = new BodyBuilder();
+        builder.begin();
+        builder.addln("boolean debug = _logger.isDebugEnabled();");
+
+        builder.addln("if (debug)");
+        builder.addln(format("  _logger.entry(%s, $args);", name));
+
+        builder.addln("try");
+        builder.begin();
+
+        if (!isVoid)
+            builder.add(format("%s result = ", getJavaClassName(returnType)));
+
+        builder.addln(format("_delegate.%s($$);", signature.getName()));
+
+        if (isVoid)
+        {
+            builder.addln("if (debug)");
+            builder.addln(format("  _logger.voidExit(%s);", name));
+            builder.addln("return;");
+        }
+        else
+        {
+            builder.addln("if (debug)");
+            builder.addln(format("  _logger.exit(%s, ($w)result);", name));
+            builder.addln("return result;");
+        }
+
+        builder.end(); // try
+
+        // Now, a catch for each declared exception (if any)
+
+        if (signature.getExceptionTypes() != null)
+            for (Class exceptionType : signature.getExceptionTypes())
+                addExceptionHandler(builder, name, exceptionType);
+
+        // And a catch for RuntimeException
+
+        addExceptionHandler(builder, name, RuntimeException.class);
+
+        builder.end();
+
+        cf.addMethod(Modifier.PUBLIC, signature, builder.toString());
+    }
+
+    private void addExceptionHandler(BodyBuilder builder, String quotedMethodName,
+            Class exceptionType)
     {
-        return null;
+        builder.addln(format("catch (%s ex)", exceptionType.getName()));
+        builder.begin();
+        builder.addln("if (debug)");
+        builder.addln(format("  _logger.fail(%s, ex);", quotedMethodName));
+        builder.addln("throw ex;");
+        builder.end();
     }
 
+    private void addInfrastructure(ClassFab cf, Class serviceInterface)
+    {
+        cf.addInterface(serviceInterface);
+
+        cf.addField("_delegate", serviceInterface);
+        cf.addField("_logger", Logger.class);
+
+        cf.addConstructor(new Class[]
+        { serviceInterface, Logger.class }, null, "{ _delegate = $1; _logger = $2; }");
+    }
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ServiceMessages.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ServiceMessages.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ServiceMessages.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/internal/ioc/services/ServiceMessages.java Sun Jul 23 14:21:45 2006
@@ -64,4 +64,9 @@
         return MESSAGES.format("duplicate-method-in-class", ms, fab.getName());
     }
 
+    static String loggingInterceptor(String serviceId, Class serviceInterface)
+    {
+        return MESSAGES.format("logging-interceptor", serviceId, serviceInterface.getName());
+    }
+
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/LoggingDecorator.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/LoggingDecorator.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/LoggingDecorator.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/java/org/apache/tapestry/ioc/LoggingDecorator.java Sun Jul 23 14:21:45 2006
@@ -1,8 +1,25 @@
+// Copyright 2006 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.tapestry.ioc;
 
+import org.apache.commons.logging.Log;
+
 /**
  * Service that can create a logging interceptor that wraps around a service implementation (or
- * interceptor).
+ * interceptor). The interceptor works with the service's log to log, at debug level, method entry
+ * (with arguments), method exit (with return value, if any) as well as any thrown exceptions.
  * 
  * @author Howard M. Lewis Ship
  */
@@ -16,8 +33,13 @@
      *            interface implemented by the delegate
      * @param delegate
      *            existing object to be wrapped
+     * @param serviceId
+     *            id of service
+     * @param serviceLog
+     *            log used for debug level logging messages by the interceptor
      * @return a new object implementing the interface that can be used in place of the delegate,
      *         providing logging behavior around each method call on the service interface
      */
-    <T> T createLoggingInterceptor(Class<T> serviceInterface, T delegate);
+    <T> T createLoggingInterceptor(Class<T> serviceInterface, T delegate, String serviceId,
+            Log serviceLog);
 }

Modified: tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/main/resources/org/apache/tapestry/internal/ioc/services/ServiceStrings.properties Sun Jul 23 14:21:45 2006
@@ -18,3 +18,4 @@
 unable-to-lookup-class=Unable to lookup class {0}: {1}
 unable-to-write-class=Unable to create class {0}: {1}
 duplicate-method-in-class=Attempt to redefine method {0} of class {1}.
+logging-interceptor=<Logging interceptor for {0}({1})>

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/decorator.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/decorator.apt?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/decorator.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/decorator.apt Sun Jul 23 14:21:45 2006
@@ -12,6 +12,19 @@
   sees this new object, termed an <<interceptor>>.  The interceptor implements the
   same service interface as the underlying service.
   
+  A common example for this is the Java I/O library.  The abstract InputStream
+  base class has a very simple API for reading bytes from a stream (and a few
+  other things).  Subclasses of InputStream provide a wide array of other options
+  such as buffering, encrypting or decrypting, as well as control over the source
+  of data read by the stream.  All of these <concerns> are encapsulated in different
+  implementations of InputStream, and all can be connected together using
+  the common InputStream API.
+  
+  Tapestry IoC uses a similar approach, where a one or more of interceptor objects,
+  all implementing the service interface, are strung together.  The service's
+  proxy (responsible for just-in-time instantiation of the service implementation)
+  is at one end of this pipeline, the core service implementation is at the other.
+  
   For each method in the service interface, the interceptor object can perform
   some operations before and after re-invoking the same method on the
   core service implementation.  This is another design pattern: <delegation>.  An interceptor
@@ -25,7 +38,8 @@
   This concern is supplied by the Tapestry IoC framework itself, but similar
   concerns are easily implemented as decorations.
   
-  Whereas the popular AspectJ framework changes your classes (it calls the process "weaving"),
+  Whereas the popular AspectJ framework changes the compiled bytecode of your
+  classes (it calls the process "weaving"),
   with Tapestry IoC, the approach is to wrap your existing classes in new objects. These
   wrapper objects are often dynamically created at runtime.
   
@@ -52,11 +66,12 @@
     return new IndexerImpl();
   }
   
-  public <T> T decorateIndexer(Class<T> serviceInterface, T delegate, Log serviceLog,
-    @InjectService("tapestry.ioc.LoggingInterceptorFactory")
+  public <T> T decorateIndexer(Class<T> serviceInterface, T delegate, 
+    String serviceId, Log serviceLog,
+    @InjectService("tapestry.ioc.LoggingDecorator")
     LoggingInterceptorFactory factory)
   {
-    return factory.createLoggingInterceptor(serviceInterface, serviceLog, delegate);
+    return factory.createLoggingInterceptor(serviceInterface, delegate, serviceId, serviceLog);
   } 
 }
 +---------------------+
@@ -113,4 +128,89 @@
   But as we'll see, its possible to have a single decorator method work on many different
   services by using annotations.
   
-  
\ No newline at end of file
+Targetting Multiple Services
+
+  By using the
+  {{{../apidocs/org/apache/tapestry/ioc/annotations/Match.html}@Match annnotation}},
+  you may identify which services are to be decorated.
+  
+  The value specified in the Match annotation is one or more patterns. These patterns
+  are used to match services. In a pattern, a "*" at the start or end of a string
+  match zero or more characters.
+  
+  For example, to target all the services in your module:
+  
++---------------------+    
+  @Match("*")
+  public <T> T decorateLogging(Class<T> serviceInterface, T delegate, 
+    String serviceId, Log serviceLog,
+    @InjectService("tapestry.ioc.LoggingDecorator")
+    LoggingInterceptorFactory factory)
+  {
+    return factory.createLoggingInterceptor(serviceInterface, delegate, serviceId, serviceLog);
+  }   
++---------------------+  
+
+  You can use multiple patterns with @Match, in which case, the decorator will be applied
+  to a service that matches <any> of the patterns. For instance, if you only wanted
+  logging for your data access and business logic services, you might end up with
+  <<<@Match("Data*", "*Logic")>>> (based, of course, on how you name your services). 
+  
+  Patterns that do not contain a period ('.') character are <simple> patterns; simple patterns
+  only match within their own module.  When a period does exist, it is the seperator between
+  the module id pattern and the service id pattern.  When there is more than one period, it is
+  the <last> period that is the divider (which makes sense, as unqualified service ids
+  can't contain a period).
+  
+  For example, you might add <<<@Match("myapp.internal.*.*")>>> to match all services within
+  any of your application's internal modules.
+  
+  Thus, <<<@Match("*.*")>>> is dangerous, because it will match every (public) service in every
+  module.  
+  
+  <Note: certain services, those with the primitive lifecycle, are never decorated.>
+  
+  <Note: It is possible that the module tapestry.ioc will be a special case that "rejects"
+  decorations from outside modules.  We may also look to limit the scope of @Match, perhaps
+  not allowing "*.*" as a pattern.>
+  
+  <Note: Another idea will be other ways of matching services: base on inheritance of the
+  service interface and/or based on the presence of particular class annotations on the
+  service interface.>
+  
+    
+Ordering of Decorators
+
+   In cases where multiple decorators will apply to a single service, you can control
+   the order in which decorators are applied using two additional annotations:
+   {{{../apidocs/org/apache/tapestry/ioc/annotations/Before.html}@Before}} and 
+   {{{../apidocs/org/apache/tapestry/ioc/annotations/After.html}@After}}.
+   
+   For each annotation, the value is a string containing a comma-separated list of
+   decorator ids.  The ids can be unqualified (if within the same module) or fully qualified
+   (if within another module).
+   
+   The value can also be "*" to force the decorator to be applied first (with @Before)
+   or last (with @After).
+   
+   For example, say you had decorators for logging, security checks, transaction management,
+   and request timing (to report on how long method invocations were taking).
+   It makes sense that logging should come first, so the logging decorator method would
+   use <<<@Before("*")>>>.  Likewise, timing should come before security checks and the rest
+   (because we want to account for the time it takes to make those checks and to manage those
+   transactions), so we might see
+   <<<@Before("Security")>>> on the timing decorator.  Starting and committing transactions
+   is likely the last thing to occur before invoking the core service implementation,
+   so we'll use <<<@After("*")>>> on its decorator method.
+    
+   If you reference services that don't exist, you will see warnings logged to the console
+   (or log file) as your services are decorated.
+   
+   Note: the ordering of decorators is in terms of the <effect> desired. In the previous 
+   example, the order of the effects is logging, then timing, then security, then transactions
+   (then the core service implementation). Internally, the decorators are invoked
+   last to first (since each once receives the "next" interceptor as its delegate).
+   So the core service implementation is created (via a service builder method)
+   and that is passed to the transaction decorator method. The interceptor created there
+   is passed to the the security decorator, and so forth.
+    
\ No newline at end of file

Modified: tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/site/apt/ioc/service.apt Sun Jul 23 14:21:45 2006
@@ -118,6 +118,11 @@
   The order of parameters is completely irrelevant. They can come first or last or be
   interspersed however you like.
   
+  Injecting in the ServiceResources can be handy when you want to calculate the name
+  of a service dependency on the fly.  However, in the general case (where the
+  id of service dependencies is known at build time), it is easier
+  to use the @InjectService annotation.
+  
 Automatic Dependency Resolution
 
   When injecting another service, you may choose to not provide the @InjectService annotation.
@@ -191,5 +196,10 @@
    This approach can be very powerful: I've (HLS) used it to break apart untestable
    monolithic code into two mutually dependent halves, each of which can be unit tested.
     
-        
+   The exception to this rule is a service that depends on itself <during construction>.
+   This can occur when (indirectly, through other services) building the service
+   trys to invoke a method on the service being built. This can happen when the service
+   implemention's constructor invoke methods on service dependencies passed into it,
+   or when the service builder method itself does the same. This is actually a very rare
+   case and difficult to illustrate.
         

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/IOCUtilitiesTest.java Sun Jul 23 14:21:45 2006
@@ -1,3 +1,17 @@
+// Copyright 2006 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.tapestry.internal.ioc;
 
 import static org.apache.tapestry.internal.ioc.IOCUtilities.decoratorMatch;

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvokerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvokerTest.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvokerTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceBuilderMethodInvokerTest.java Sun Jul 23 14:21:45 2006
@@ -14,10 +14,6 @@
 
 package org.apache.tapestry.internal.ioc;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertSame;
-
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
 import org.apache.commons.logging.Log;
@@ -28,6 +24,9 @@
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertSame;
+
 /**
  * @author Howard M. Lewis Ship
  */
@@ -217,12 +216,11 @@
         {
             assertEquals(ex.getMessage(), "Error invoking service builder method "
                     + getClass().getName() + ".build_fail() "
-                    + "(for service 'ioc.Fie'): java.lang.reflect.InvocationTargetException");
+                    + "(for service 'ioc.Fie'): Method failed.");
 
-            InvocationTargetException inner1 = (InvocationTargetException) ex.getCause();
-            RuntimeException inner2 = (RuntimeException) inner1.getTargetException();
+            Throwable cause = ex.getCause();
 
-            assertEquals(inner2.getMessage(), "Method failed.");
+            assertEquals(cause.getMessage(), "Method failed.");
         }
 
         verify();

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImplTest.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImplTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/ServiceDecoratorImplTest.java Sun Jul 23 14:21:45 2006
@@ -14,11 +14,6 @@
 
 package org.apache.tapestry.internal.ioc;
 
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertSame;
-
-import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
 import org.apache.commons.logging.Log;
@@ -26,6 +21,10 @@
 import org.apache.tapestry.ioc.ServiceResources;
 import org.testng.annotations.Test;
 
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNull;
+import static org.testng.Assert.assertSame;
+
 /**
  * @author Howard M. Lewis Ship
  */
@@ -39,9 +38,16 @@
 
     private RuntimeException _exception;
 
+    /**
+     * Also, test logging of decorator method invocation.
+     * 
+     * @throws Exception
+     */
     @Test
     public void decorator_returns_interceptor() throws Exception
     {
+        Method m = getClass().getMethod("decoratorReturnsInterceptor", Class.class, Object.class);
+
         ServiceResources resources = newServiceResources();
         Log log = newLog();
         _expectedDelegate = newFieService();
@@ -49,13 +55,15 @@
 
         trainForConstructor(resources, log);
 
+        trainIsDebugEnabled(log, true);
+
+        log.debug(IOCMessages.invokingMethod(m));
+
         replay();
 
         // Check that the delegate gets passed in; check that the return value of the
         // decorator method is the return value of the ServiceDecorator.
 
-        Method m = getClass().getMethod("decoratorReturnsInterceptor", Class.class, Object.class);
-
         ServiceDecoratorImpl decorator = new ServiceDecoratorImpl(m, this, resources);
 
         Object interceptor = decorator.createInterceptor(_expectedDelegate);
@@ -89,6 +97,8 @@
 
         trainForConstructor(resources, log);
 
+        trainIsDebugEnabled(log, false);
+
         replay();
 
         Method m = getClass().getMethod("decorateReturnNull", Object.class);
@@ -114,6 +124,8 @@
 
         trainForConstructor(resources, log);
 
+        trainIsDebugEnabled(log, false);
+
         log.warn(IOCMessages.decoratorReturnedWrongType(
                 m,
                 SERVICE_ID,
@@ -141,6 +153,8 @@
 
         trainForConstructor(resources, log);
 
+        trainIsDebugEnabled(log, false);
+
         replay();
 
         Method m = getClass().getMethod("decoratorThrowsException", Object.class);
@@ -157,14 +171,12 @@
             assertEquals(ex.getMessage(), IOCMessages.decoratorMethodError(m, SERVICE_ID, ex
                     .getCause()));
 
-            InvocationTargetException te = (InvocationTargetException) ex.getCause();
-            RuntimeException inner = (RuntimeException) te.getCause();
+            Throwable cause = ex.getCause();
 
-            assertSame(inner, _exception);
+            assertSame(cause, _exception);
         }
 
         verify();
-
     }
 
     public Object decoratorThrowsException(Object delegate)

Modified: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggerTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggerTest.java?rev=424822&r1=424821&r2=424822&view=diff
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggerTest.java (original)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggerTest.java Sun Jul 23 14:21:45 2006
@@ -1,15 +1,28 @@
-package org.apache.tapestry.internal.ioc.services;
+// Copyright 2006 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.
 
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertTrue;
+package org.apache.tapestry.internal.ioc.services;
 
 import java.util.Arrays;
 
 import org.apache.commons.logging.Log;
 import org.apache.tapestry.test.BaseTestCase;
-import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
 /**
  * @author Howard M. Lewis Ship
  */
@@ -72,6 +85,22 @@
         replay();
 
         new Logger(log).voidExit("wilma");
+
+        verify();
+    }
+
+    @Test
+    public void fail_test()
+    {
+        Log log = newLog();
+
+        RuntimeException t = new RuntimeException("Ouch!");
+
+        log.debug("[ FAIL] wilma -- " + t.getClass().getName(), t);
+
+        replay();
+
+        new Logger(log).fail("wilma", t);
 
         verify();
     }

Added: tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImplTest.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImplTest.java?rev=424822&view=auto
==============================================================================
--- tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImplTest.java (added)
+++ tapestry/tapestry5/tapestry-core/trunk/src/test/java/org/apache/tapestry/internal/ioc/services/LoggingDecoratorImplTest.java Sun Jul 23 14:21:45 2006
@@ -0,0 +1,247 @@
+// Copyright 2006 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.tapestry.internal.ioc.services;
+
+import org.apache.commons.logging.Log;
+import org.apache.tapestry.ioc.LoggingDecorator;
+import org.apache.tapestry.test.BaseTestCase;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.xml.sax.SAXParseException;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * Use the LoggingDecorator in a number of ways to verify its behavior. In some ways we are testing
+ * the code dynamically generated by the LoggingDecorator as much as we are testing the decorator
+ * itself -- one proves the other.
+ * 
+ * @author Howard M. Lewis Ship
+ */
+public class LoggingDecoratorImplTest extends BaseTestCase
+{
+    public interface UpcaseService
+    {
+        String upcase(String input);
+    }
+
+    public interface AdderService
+    {
+        long add(long operand1, long operand2);
+    }
+
+    public interface ToStringService
+    {
+        String toString();
+    }
+
+    public interface ExceptionService
+    {
+        void parse() throws SAXParseException;
+    }
+
+    @Test
+    public void void_method()
+    {
+        Log log = newLog();
+        Runnable delegate = newMock(Runnable.class);
+
+        trainIsDebugEnabled(log, true);
+        log.debug("[ENTER] run()");
+
+        delegate.run();
+
+        log.debug("[ EXIT] run");
+
+        replay();
+
+        LoggingDecorator ld = new LoggingDecoratorImpl(new ClassFactoryImpl());
+        Runnable interceptor = ld
+                .createLoggingInterceptor(Runnable.class, delegate, "foo.Bar", log);
+
+        interceptor.run();
+
+        assertEquals(
+                interceptor.toString(),
+                "<Logging interceptor for foo.Bar(java.lang.Runnable)>");
+
+        verify();
+    }
+
+    @Test
+    public void method_throws_runtime_exception()
+    {
+        Throwable t = new RuntimeException("From delegate.");
+        Log log = newLog();
+        Runnable delegate = newMock(Runnable.class);
+
+        trainIsDebugEnabled(log, true);
+        log.debug("[ENTER] run()");
+
+        delegate.run();
+        setThrowable(t);
+
+        log.debug("[ FAIL] run -- " + t.getClass().getName(), t);
+
+        replay();
+
+        LoggingDecorator ld = new LoggingDecoratorImpl(new ClassFactoryImpl());
+        Runnable interceptor = ld
+                .createLoggingInterceptor(Runnable.class, delegate, "foo.Bar", log);
+
+        try
+        {
+            interceptor.run();
+            unreachable();
+        }
+        catch (RuntimeException ex)
+        {
+            Assert.assertSame(ex, t);
+        }
+
+        verify();
+    }
+
+    @Test
+    public void method_throws_checked_exception() throws Exception
+    {
+        Throwable t = new SAXParseException("From delegate.", null);
+        Log log = newLog();
+        ExceptionService delegate = newMock(ExceptionService.class);
+
+        trainIsDebugEnabled(log, true);
+        log.debug("[ENTER] parse()");
+
+        delegate.parse();
+        setThrowable(t);
+
+        log.debug("[ FAIL] parse -- " + t.getClass().getName(), t);
+
+        replay();
+
+        LoggingDecorator ld = new LoggingDecoratorImpl(new ClassFactoryImpl());
+        ExceptionService interceptor = ld.createLoggingInterceptor(
+                ExceptionService.class,
+                delegate,
+                "foo.Bar",
+                log);
+
+        try
+        {
+            interceptor.parse();
+            unreachable();
+        }
+        catch (SAXParseException ex)
+        {
+            Assert.assertSame(ex, t);
+        }
+
+        verify();
+    }
+
+    @Test
+    public void object_parameter_and_return_type()
+    {
+        Log log = newLog();
+        UpcaseService delegate = new UpcaseService()
+        {
+            public String upcase(String input)
+            {
+                return input.toUpperCase();
+            }
+        };
+
+        trainIsDebugEnabled(log, true);
+        log.debug("[ENTER] upcase(\"barney\")");
+
+        log.debug("[ EXIT] upcase [\"BARNEY\"]");
+
+        replay();
+
+        LoggingDecorator ld = new LoggingDecoratorImpl(new ClassFactoryImpl());
+        UpcaseService interceptor = ld.createLoggingInterceptor(
+                UpcaseService.class,
+                delegate,
+                "foo.Bar",
+                log);
+
+        assertEquals(interceptor.upcase("barney"), "BARNEY");
+
+        verify();
+    }
+
+    @Test
+    public void primitive_parameter_and_return_type()
+    {
+        Log log = newLog();
+        AdderService delegate = new AdderService()
+        {
+            public long add(long operand1, long operand2)
+            {
+                return operand1 + operand2;
+            }
+        };
+
+        trainIsDebugEnabled(log, true);
+        log.debug("[ENTER] add(6, 13)");
+
+        log.debug("[ EXIT] add [19]");
+
+        replay();
+
+        LoggingDecorator ld = new LoggingDecoratorImpl(new ClassFactoryImpl());
+        AdderService interceptor = ld.createLoggingInterceptor(
+                AdderService.class,
+                delegate,
+                "foo.Bar",
+                log);
+
+        assertEquals(interceptor.add(6, 13), 19);
+
+        verify();
+    }
+
+    @Test
+    public void to_string_method_in_service_interface_is_delegated()
+    {
+        Log log = newLog();
+        ToStringService delegate = new ToStringService()
+        {
+            @Override
+            public String toString()
+            {
+                return "FROM DELEGATE";
+            }
+        };
+
+        trainIsDebugEnabled(log, true);
+        log.debug("[ENTER] toString()");
+
+        log.debug("[ EXIT] toString [\"FROM DELEGATE\"]");
+
+        replay();
+
+        LoggingDecorator ld = new LoggingDecoratorImpl(new ClassFactoryImpl());
+        ToStringService interceptor = ld.createLoggingInterceptor(
+                ToStringService.class,
+                delegate,
+                "foo.Bar",
+                log);
+
+        assertEquals(interceptor.toString(), "FROM DELEGATE");
+
+        verify();
+    }
+}