Added by Ken Ballard , last edited by Ken Ballard on Feb 10, 2006 (view change ) div.auto_complete { width: 350px; background: #fff; } div.auto_complete ul { border: 1px solid #888; margin: 0; padding: 0; width: 100%; list-style-type: none; } div.auto_complete ul li { margin: 0; padding: 3px; } div.auto_complete ul li.selected { background-color: #ffb; } div.auto_complete ul strong.highlight { color: #800; margin: 0; padding: 0; } <!----> <!----> <script type="text/javascript"> function doAddLabel(hideTextfieldAfterAddParam) { // stub } function onAddLabel() { // stub } function showLabelsInput() { // stub } </script> <!---->
Labels:
(None)
<!----> <script type="text/javascript"> // if there is javascript support, enable the [Edit] link that enables AJAX label adding/removing functionality if (document.getElementById('editLabelsLink')) { document.getElementById('editLabelsLink').style.display = 'inline'; } </script> |
Context
Certain systems require that an audit trail be maintained for every change made to records in the database (what the data looked like before and after, who changed it, and when it was changed) and, in some cases, they want an audit trail of "reads" of certain data.
Problem
The system architecture is comprised of stateless services. The use cases for which the services exist don't require the principal (user name, etc.) on their own. You don't want the principal in every method signature. It's just one more thing that tends to add confusion and the developer has to remember to add it to every method signature. The auditing requirements are a separate concern that cuts across all of the services. It is to the business service developer's great advantage to be able to focus on the use cases that that specific service is meant to, well, "serve." The developer doesn't want to worry about concerns that cut across the entire system like security and auditing.
In addition, your system might be distributed (e.g., the services reside on the app server and they can be invoked from the web server -- probably with Spring's EJB support).
Forces
Solution
I came up with a solution and did an initial proof of concept. My solution is heavily influenced by Spring (especially in its use of ThreadLocal). It extends several Spring classes and uses Spring AOP (for instance, I have advice classes that advise DAOs). It is also influenced by, but is different than, Kåre Kjelstrøm's article "Create an application-wide user session for J2EE" [http://www.javaworld.com/javaworld/jw-03-2005/jw-0314-usersession_p.html#resources] and by Rob Harrop and Jan Machacek's writing on auditing requirements in (Harrop, Rob and Jan Machacek. Pro Spring. Berkeley, CA: Apress, 2005).
I'm currently working on the company I work for to let me release all of the code (it isn't that much) for public review. I'll include code samples on this page if I get approval. I will say that one of the main concepts I use is that of a "Method Invocation Context." This context is then propagated so that it can be accessed by an auditing service later without the "Context" actually being passed in the service method signatures. The business service developer doesn't even need to know about the auditing. [I'll show you once I get the approval.]
Please post comments if you think this would be a useful feature to add to Spring or one of its sub-projects or if you think that it would not be useful. I don't want to take up space on the wiki with code samples if no one thinks that it would be useful.
Let's look at an example without auditing. I have a distributed app. I have a .war deployed on the web server. In the .war, I have Struts Action classes that talk to business service interfaces. They don't know it, but behind those interfaces they're talking to the service implementations deployed in the .ear on the app server by way of Spring's EJB support. The wiring is done in the Spring application context file, like this:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="myExampleService"
class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
<property name="jndiName">
<value>ejb/MyExampleService </value>
</property>
<property name="jndiEnvironment">
<props>
<prop key="java.naming.factory.initial">org.jnp.interfaces.NamingContextFactory</prop>
<prop key="java.naming.factory.url.pkgs">org.jboss.naming:org.jnp.interfaces</prop>
<prop key="java.naming.provider.url">jnp://127.0.0.1:1099</prop>
</props>
</property>
<property name="resourceRef">
<value>false</value>
</property>
<property name="businessInterface">
<value>com.some.company.service.example.MyExampleService</value>
</property>
</bean>
</beans>
Everything is working just fine. The use cases are being accomplished. But then auditing requirements are added into the mix. They don't change the use cases themselves. The requirements cut across the entire system.
The auditing code is essentially there to "spy" on the use cases. A lot of the information that the auditing code needs to collect on the app server is readily available, i.e. the "what", "when", and "how". You have that information because you are performing the use case and it's needed to do that. But certain information like the "who" (user information) and the "where" (e.g. ip address from which the request came) are left on the web server. They are things that you know when you are in the context in which the method was invoked, but are lost when you leave that context unless you explicitly pass them in the method signature. I have a class called MethodInvocationContext that extends Serializable. Its purpose in life is to be a vessel for that information that was previously left behind.
That and a dollar will get you a ride on the bus. It's not worth anything by itself. We need to be able to propagate that context. But first we need to capture it without making the Struts Action class (and the junior person you have writing that class) have to do extra work that's outside the use case. Where can you do this? In the web app scenario, you can use a servlet filter (there are probably other ways that would accomplish the same thing). I have a class called MethodInvocationContextCaptureFilter that implements javax.servlet.Filter. That takes care of capturing it, but where can we put it so that we can grab it and propagate it later during the life of the request without needing access to the ServletRequest? Here I use Martin Fowler's Registry Pattern [http://www.martinfowler.com/eaaCatalog/registry.html] and took a few cues from jCorporate's Request Registry [http://www.jcorporate.com/expresso/doc/edg/edg_request_registry.html]. Here's the key method in MethodInvocationContextCaptureFilter:
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException{
final MethodInvocationContext context = new DefaultMethodInvocationContext();
// put the "who" and the "where" in the MethodInvocationContext
// variable here.
WeakMethodInvocationContextRegistry.setMethodInvocationContext(context);
try
And here's the code for the WeakMethodInvocationContextRegistry:
package com.some.company.audit;
import java.lang.ref.WeakReference;
/**
* "Typically, with class variables, the same value is shared by all instances of
* the class. With ThreadLocal, that is not the case. Instead, each usage of the
* ThreadLocal class within the same thread shares the same static information.
* Usages of the ThreadLocal class within different threads result in different
* static information [From
* http://java.sun.com/developer/JDCTechTips/2003/tt1208.html#2\\\\]."
*
* The MethodInvocationContext is wrapped in a WeakReference. This ensures that
* a failure to remove the MethodInvocationContext from the ThreadLocal for some
* reason won't result in a memory leak.
*
*/
public class WeakMethodInvocationContextRegistry{
private static final ThreadLocal<WeakReference> contextThreadLocal = new ThreadLocal<WeakReference>();
/**
* Private constructor to prevent instantiation
*/
private WeakMethodInvocationContextRegistry()
public static MethodInvocationContext getMethodInvocationContext()
Now I have the MethodInvocationContext and I can get it via the WeakMethodInvocationContextRegistry. The Struts Action developer is blissfully still unaware. Now I need to grab it and propagate it without jeopardizing that blissful unawareness. Spring does the same kind of thing with its org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean. I'm already using that so that the Struts Action developer can code to the business service interface without knowing how it's getting to the implementation on the app server. I just need to swap the user's regular brand with Folger's Crystals, so to speak [I apologize for the confusion if you never saw those coffee commercials years ago]. I do this by secretly switching the Spring class with a class called MethodInvocationContextPropagationProxyFactoryBean that calls a class called DefaultMethodInvocationContextPropagationService that implements MethodInvocationContextPropagationService. [I know. My class names are revoltingly long]. Here's the MethodInvocationContextPropagationProxyFactoryBean:
package com.some.company.audit
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
public class MethodInvocationContextPropagationProxyFactoryBean implements FactoryBean, InitializingBean, InvocationHandler{
private MethodInvocationContextPropagationService methodInvocationContextPropagationService;
private Object proxy;
private Class businessInterface;
public MethodInvocationContextPropagationProxyFactoryBean()
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="auditService"
class="org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean">
<property name="jndiName">
<value>ejb/MethodInvocationContextPropagationService</value>
</property>
<property name="jndiEnvironment">
<props>
<prop key="java.naming.factory.initial">org.jnp.interfaces.NamingContextFactory</prop>
<prop key="java.naming.factory.url.pkgs">org.jboss.naming:org.jnp.interfaces</prop>
<prop key="java.naming.provider.url">jnp://127.0.0.1:1099</prop>
</props>
</property>
<property name="resourceRef">
<value>false</value>
</property>
<property name="businessInterface">
<value>com.some.company.audit.MethodInvocationContextPropagationService</value>
</property>
</bean>
<bean id="myExampleService"
class="com.some.company.service.audit.access.MethodInvocationContextPropagationProxyFactoryBean">
<property name="methodInvocationContextPropagationService">
<ref bean="auditService" />
</property>
<property name="businessInterface">
<value>com.some.company.service.example.MyExampleService</value>
</property>
</bean>
</beans>
The application context file in the .ear looks like this:
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="myExampleServiceLocalEJB" class="org.springframework.ejb.access.LocalStatelessSessionProxyFactoryBean">
<property name="jndiName" value="MyExampleServiceLocal" />
<property name="businessInterface" value="com.some.company.service.example.MyExampleService" />
</bean>
<bean id="auditingService" class="com.some.company.service.audit.DefaultMethodInvocationContextPropagationService">
<property name="servicesMap">
<map>
<entry key="com.some.company.service.example.MyExampleService ">
<ref local="myExampleServiceLocalEJB" />
</entry>
</map>
</property>
</bean>
</beans>
This MethodInvocationContextPropagationService is invoked on the appserver via Spring's EJB support. It grabs the MethodInvocationContext, puts it in the WeakMethodInvocationContextRegistry on the appserver (we're in a different JVM now), and then invokes the business service. I configure mine to call it as a local EJB, but your architecture may be different. Here's my DefaultMethodInvocationContextPropagationService:
package com.some.company.service.audit;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class DefaultMethodInvocationContextPropagationService implements
MethodInvocationContextPropagationService{
private Map<String, Object> servicesMap;
private static final Log log = LogFactory
.getLog(DefaultMethodInvocationContextPropagationService.class);
public DefaultMethodInvocationContextPropagationService()
catch (NoSuchMethodException e)
catch (IllegalAccessException e)
The key thing that makes it possible to propagate the MethodInvocationContext like this is that, once I get over to the appserver JVM, I stay on the same thread. If you spawn another thread, that thread is on its own. If you need to jump to another JVM, you would need to configure it similar to the way you did to get from the web server to the appserver.
Now your AOP class can grab the MethodInvocationContext from the WeakMethodInvocationContextRegistry. I hang AOP classes on my DAOs (without them knowing it, of course) because my auditing requirements necessitate capturing the before and after states of objects in addition to the "who" and "where."
So now where are you? If you're not in a distributed environment, you didn't need a lot of that stuff. A MethodInvocationContextRegistry could be useful for you but a lot depends on the MethodInvocationContext itself. Is that what it should even be called? I didn't show the code for mine because I think it still needs work. It needs to capture the "who." And the "where" could be useful (people sometimes want to know what ip addresses the requests are coming from). But what else should it have?
I have some ideas for how to do the actual auditing in the AOP and DAOs, but more on that later maybe.