5 Replies Latest reply on Apr 22, 2008 5:02 AM by matt.drees

    Configurable Interceptor

    wrzep

      Hi there,



      I'd like to pass a String parameter to my interceptor, but since interceptors are not Seam Components I can't configure it in a way described in Chapter 4.


      So far, I came to the following solution:




      @AroundInvoke
          public Object inject(InvocationContext invocation) throws Exception {
      
              String myParam = (String) Component.getInstance("myParam");
              System.out.println(myParam);
      
              return invocation.proceed();
          }



      In the components.xml file, I define myParam component:


      <factory name="myParam" value="Hello interceptor!" scope="APPLICATION"/>



      It works fine, but I don't like the idea of creating a Seam Component (called myParam), which is actually a configuration stuff, not a real component. Anyone knows any nicer way to pass a configuration parameter to Seam Interceptor? :)


      Cheers,
      -Pawel

        • 1. Re: Configurable Interceptor
          schlegel

          You could use Annotations.... I spent some time to develop a tracking interceptor - not relay tested yet!



          First you define a marker Annotation for class level:


          @Target({TYPE})
          @Retention(RUNTIME)
          @Interceptors(TrackingInterceptor.class)
          @Documented
          @Inherited
          public @interface Trackable {}



          Second for method level:



          @Target({METHOD})
          @Retention(RUNTIME)
          @Documented
          @Inherited
          public @interface Tracked {
          
              /**
               * @return special RequestTrack-Parameter or RequestTrack-Parameters to track
               */
              TrackedValue[] values() default {};
          
          }




          @Target({METHOD})
          @Retention(RUNTIME)
          @Documented
          @Inherited
          public @interface TrackedValue {
          
              String parameter() default "";
          
              String expr() default "";
          }




          Your interceptor should look something similar to:



          @Interceptor(stateless = true, around = AsynchronousInterceptor.class)
          public class TrackingInterceptor extends AbstractInterceptor implements Serializable {
          
              private static final long serialVersionUID = 4284998401280292997L;
          
              @AroundInvoke
              public Object aroundInvoke(InvocationContext invocationContext) throws Exception {
          
                  Method method = invocationContext.getMethod();
          
                  if (method.isAnnotationPresent(Tracked.class)) {
                      EntityManager em = (EntityManager) Component.getInstance("entityManager");  // TODO: eigener trackingEM
                      FacesContext fc = FacesContext.getCurrentInstance();
                      ExternalContext ec = fc.getExternalContext();
          
                      RequestTrack rt = new RequestTrack();
                      rt.setSessionId(((Cookie)FacesContext.getCurrentInstance().getExternalContext().getRequestCookieMap().get("JSESSIONID")).getValue());
                      rt.setRequestTime(new DateTime().toDate());
                      rt.setUserId(((SysUser)Contexts.getSessionContext().get("loggedInUser")).getId());
                      rt.setViewId(fc.getViewRoot().getViewId());
                      rt.setComponentName(getComponent().getName());
                      rt.setMethodName(method.getName());
          
                      Tracked trackedAnnotaion = method.getAnnotation(Tracked.class);
                      String result = "";
                      for (TrackedValue trv : trackedAnnotaion.values()) {
                          Object eval = Expressions.instance().createValueExpression(trv.expr()).getValue();
                          if (eval != null) {
                              String param = trv.parameter();
                              String val = eval.toString();
          
                              if (!"".equals(param) && !"".equals(val)) {
                                  rt.addTrackedParameter(trv.parameter(), eval.toString());
                              }
                          }
                      }
          
                      em.persist(rt);
                      em.flush();
          
                  }
          
                  return invocationContext.proceed();
              }
          }



          Now you can use it like this:



          @Stateful
          @Trackable
          @Name("municipalitySearch")
          @Scope(ScopeType.CONVERSATION)
          public class MunicipalitySearchAction implements MunicipalitySearach, Serializable {
          
          ....
          
              @Tracked(values = {
                  @TrackedValue(parameter = "name", expr = "#{municipalitySearch.municipalitySearchForm.name}"),
                  @TrackedValue(parameter = "id", expr = "#{municipalitySearch.municipalitySearchForm.statisticNumber}")
                      })
              public void edit(Municipality municipality) {
          
          



          I used this approach with the following Tracking Entity-Bean:



          @Entity(name = "HTTPRequest")
          public class RequestTrack {
          
              @Id
              @GeneratedValue
              @Column(name = "Id")
              private Long id;
          
              @Column(name = "SessionId")
              @org.hibernate.validator.Length(max = 150)
              @org.hibernate.validator.NotNull
              private String sessionId;
          
              @Column(name = "UserId")
              private Long userId;
          
              @Column(name = "RequestTime")
              @Temporal(TemporalType.TIMESTAMP)
              private Date requestTime;
          
              @Column(name = "ComponentName")
              @org.hibernate.validator.Length(max = 150)
              private String componentName;
          
              @Column(name = "MethodName")
              @org.hibernate.validator.Length(max = 150)
              private String methodName;
          
              @Column(name = "ViewId")
              @org.hibernate.validator.Length(max = 150)
              private String viewId;
          
              @org.hibernate.annotations.CollectionOfElements
              @JoinTable(name = "HTTPRequest_Parameter",
                      joinColumns = @JoinColumn(name = "HTTPRequest_Id")
              )
              @org.hibernate.annotations.MapKey(
                      columns = @Column(name = "Parameter", nullable = false, length = 150)
              )
              @Column(name = "Value", nullable = false, length = 250)
              private Map<String, String> trackedParameters = new HashMap<String, String>();



          • 2. Re: Configurable Interceptor
            wrzep

            Hi Hans,


            Thanks for your reply. Yeah, using annotation parameters is an option. I don't know why I didn't think about it ;)


            This of course only allows configuration in the java code, but I hope that sooner or later interceptors will be configurable in the components.xml.


            -Pawel

            • 3. Re: Configurable Interceptor
              cpopetz

              I created a delegating interceptor which just takes the name of a component you wish to intercept with, which must contain a method annotated @AroundInvoke like a normal interceptor.  Code follows.  You'd use it like this:


              @InterceptWith(value="myInterceptingComponent")
              public class ClassIWantToIntercept() { 
              ...
              }
              



              and


              @Name("myInterceptingComponent")
              public class MyClass { 
              
                   @AroundInvoke
                   public Object intercept(InvocationContext invocation, Component component) throws Throwable { 
                        ...
              
              }
              
              




              Note that all methods are intercepted by default, but that you can also apply @InterceptWith(allMethods=false) and then also apply @InterceptWith to each method you want to intercept.



              In InterceptWith.java:


              import static java.lang.annotation.RetentionPolicy.RUNTIME;
              
              import java.lang.annotation.ElementType;
              import java.lang.annotation.Retention;
              import java.lang.annotation.Target;
              
              import org.jboss.seam.annotations.intercept.Interceptors;
              
              
              /**
               * Annotating a class with @InterceptWith will cause the component specified with the annotation
               * to be intercept the invocation of any method in the target class
               * The component specified must have a method annotated @AroundInvoke.  
               * If @InterceptWith(allMethods=false) is specified, then only individual methods annotated
               * with @InterceptWith will be logged.  Conversely, if allMethods=true, you can turn off 
               * logging for an individual method with @InterceptWith(ignore=true).  
               */
              
              @Target({ElementType.METHOD,ElementType.TYPE})
              @Retention(RUNTIME)
              @Interceptors(DelegatingInterceptor.class)
              public @interface InterceptWith{ 
                   
                   /**
                    * The name of a component that contains a method annotetd with @AroundInvoke
                    */
                   String value() default "";
                   
                   /**
                    * If "cacheable" is true, the implementation will assume that the component can be cached once
                    * instantiated, i.e. it is an Application scoped component.  Otherwise the component will be looked up
                    * upon each invocation, which can be costly, so be careful.
                    */
                   boolean cacheable() default true;
                   
                   
                   /**
                    * If allMethods is turned off, only methods that are also annotated with @InterceptWith will be logged
                    */
                   boolean allMethods() default true;
                   
                   /**
                    * If ignore is turned on, logging for this method is ellided, even if allMethods is true for the class.
                    */
                   
                   boolean ignore() default false;
              }
              
              



              
              import java.io.Serializable;
              import java.lang.reflect.Method;
              import java.util.HashSet;
              import java.util.Set;
              
              import javax.annotation.PostConstruct;
              
              import org.jboss.seam.Component;
              import org.jboss.seam.annotations.intercept.AroundInvoke;
              import org.jboss.seam.annotations.intercept.Interceptor;
              import org.jboss.seam.intercept.InvocationContext;
              
              
              /**
               * Any component annotated with @InterceptWith InterceptWith will have this interceptor installed for it.
               * We just delegate to the @AroundInvoke method on the component which is identified by the InterceptWith value annotation parameter
               * @author cpopetz
               *
               */
              @Interceptor
              public class DelegatingInterceptor implements Serializable {
                   
                   private transient InterceptWith classAnnotation;
                   private transient Component component;
                   private transient Object cachedDelegate;
                   private transient Set<Method> filteredMethods = new HashSet<Method>();
                   private transient Method delegateInterceptMethod;
                   
                   public void setAnnotation(InterceptWith annotation) { 
                        this.classAnnotation = annotation; 
                   }
                   
                   public void setComponent(Component component) { 
                        this.component = component; 
                   }
                   
                   
                   /** 
                    * Hook into postConstruct to set up the list of methods we should actually intercept,
                    * saving them in the filteredMethods set
                    */
                   @PostConstruct
                   public void postConstruct(InvocationContext context) {
                        boolean all = classAnnotation.allMethods();
                        for (Class clazz = component.getBeanClass(); clazz!=Object.class; clazz = clazz.getSuperclass() ) {
                             for (Method m : clazz.getDeclaredMethods()) {
                                  InterceptWith methodLevel = m.getAnnotation(InterceptWith.class);
                                  if ((all && (methodLevel == null || !methodLevel.ignore())) ||
                                            (!all && (methodLevel != null && !methodLevel.ignore())))
                                       filteredMethods.add(m);
                             }
                        }
                   }
              
                   @AroundInvoke 
                   public Object intercept(InvocationContext invocation) throws Exception {
                        
                        if (!filteredMethods.contains(invocation.getMethod()))
                             return invocation.proceed();
                        
                        Object delegate = cachedDelegate;
                        if (delegate == null) {
                             delegate = Component.getInstance(classAnnotation.value());
                             if (classAnnotation.cacheable())
                                  cachedDelegate = delegate;
                             
                             /* 
                              * Scan the delegate and find its @AroundInvoke method
                              */
                             if (delegateInterceptMethod == null) {
                                  for (Method m : Component.forName(classAnnotation.value()).getBeanClass().getDeclaredMethods()) {
                                       if (m.isAnnotationPresent(AroundInvoke.class)) 
                                            if (delegateInterceptMethod != null)
                                                 throw new RuntimeException("Can't annotate two methods in " + delegate.getClass().getName() 
                                                           + " with @AroundInvoke");
                                            else
                                                 delegateInterceptMethod = m;
                                  }
                                  if (delegateInterceptMethod == null)
                                       throw new RuntimeException("Must annotate a method in " + delegate.getClass().getName() 
                                                 + " with @AroundInvoke");
                             }
                        }
                        
                        if (delegate != null) 
                             return delegateInterceptMethod.invoke(delegate, invocation, component);
                        else {
                             System.err.println("No delegate component for InterceptWith(\"" + classAnnotation.value() + "\") on invocation of " 
                                       + invocation.getMethod() + " on " + invocation.getTarget());
                             return invocation.proceed();
                        }
                   }
              }
              
              

              • 4. Re: Configurable Interceptor
                cpopetz

                I failed to mention that this of course means you can define your interceptors in components.xml, and annotate the methods you wish to intercept with @InterceptWith. 

                • 5. Re: Configurable Interceptor
                  matt.drees

                  Nice!