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