This blog is mainly about Java...

Showing posts with label Seam Series. Show all posts
Showing posts with label Seam Series. Show all posts

Monday, April 5, 2010

Advanced Seam series part 3 of 3: Asynchronous mail sending

Advanced Seam series part 3 of 3: Asynchronous mail sending

In this last series I will be showing how easy you can set up your seam environment to handle asynchronous mail sending. You can even raise other events asynchronously.

Seam makes it very easy to perform work asynchronously from a web request.

Seam layers a simple asynchronous method and event facility over your choice of dispatchers:
  • java.util.concurrent.ScheduledThreadPoolExecutor (by default)
  • The EJB timer service (for EJB 3.0 environments)
  • Quartz
Asynchronous configuration
The default dispatcher, based upon aScheduledThreadPoolExecutor performs efficiently but does not guarantee that the task will ever actually be executed. This is because it does not have a persistence state, meaning it only stores the events in memory. If your application server goes down in between the calls, they will not run.
If you have a task that is not critical that it must be performed, ie a clean up task, or something trivial, then it is no reason not to use the default dispatcher.

However, if you want the guarantee that the task is called you might use the Timer Service.
If you're working in an environment that supports EJB 3.0, you can add the following line to components.xml:
<async:timer-service-dispatcher/>
Then your asynchronous tasks will be processed by the container's EJB timer service. The Timer Service implementation is persistence based, thus you will get some guarantee that the tasks will eventually run.

Finally, your third is to use an Open Source alternative called Quartz (recently acquired by Terracotta). To use Quartz, you will need to bundle the Quartz library JAR (found in the lib directory) in your EAR and declare it as a Java module in application.xml.The Quartz dispatcher may be configured by adding a Quartz property file to the classpath. It must be named seam.quartz.properties.In addition, you need to add the following line to components.xml to install the Quartz dispatcher.
<async:quartz-dispatcher/>
Note that Quartz uses RAMJobStore as default, thus it is not persistence based on default. You will need to configure it to use persistence base.
It is up to the reader to choose whichever asynchronous strategy they see fit.

Sending emails Asynchronously
It is really very easy sending a plain email with Seam. All you have to do is use Seam mail, annotate the method and interface (if you are using EJB's) with @Asynchronous and its done. However, the tricky part is using EL expressions and variables stored in the different Contexts, so that you can produce dynamic emails.

There are two ways you can call a task asynchronously. You can either send an asynchronous event, or as I previously explained, annotate a method with @Asynchronous, and call it normally, ie:
Events.instance().raiseAsynchronousEvent("sendOneTimepassword", user, theOnetimepass);
//Or the normal way
@In MailService mailService;
mailService.sendSupport(sender,supportEmail,supportMessage);
Lets say you have the following Stateless EJB that is responsible for sending emails:

import javax.ejb.Local;
import javax.ejb.Stateless;

import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.async.Asynchronous;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.faces.Renderer;

/**
 * This service is responsible for sending emails asynchronously
 * @author Shervin Asgari
 *
 */
@Stateless
@Local(MailService.class)
@Name("mailService")
@AutoCreate
public class MailServiceImpl implements MailService {

    @In(create = true)
    private Renderer renderer;
    
    @Asynchronous
    public void sendSupport(User sender, String supportEmail, String supportMessage) {
        Contexts.getEventContext().set("sender", sender);
        Contexts.getEventContext().set("supportEmail", supportEmail);
        Contexts.getEventContext().set("supportMessage", supportMessage);
        renderer.render("/generic/email-template/support-email-template.xhtml");
    }
    
    @Observer("sendOneTimepassword")
    //@Asynchronous, we dont need to annotate with @Asynchronous if we are raising the event asynchronously
    public void sendOneTimepassword(User emailUser, String oneTimePassword) {
        Contexts.getEventContext().set("emailUser", emailUser);
        Contexts.getEventContext().set("oneTimePassword", oneTimePassword);
        renderer.render("/generic/email-template/onetimepassword-email-template.xhtml");
    }    
}
One of the methods sendSupport() is responsible for sending support messages as email, whilst the other method sendOneTimepassword is used to send generated one time passwords to a user so that they can authenticate to the system. Have you look here for information on how you can set up your system to do exactly that.

The key point to note is the Contexts.getEventContext().set("emailUser", emailUser);. If I would have normally injected the user in the EmailService, it would not work. Since the call is made asynchronously we need to set the variables in the asynchronous context. Thus we can now get hold of them in the email template:


<m:message 
    xmlns="http://www.w3.org/1999/xhtml"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:m="http://jboss.com/products/seam/mail"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">

    <m:from name="Someone">no-reply@someplace.com</m:from>
    <m:to name="#{emailUser.name}" address="#{emailUser.fromEmail}" />
    <m:subject>#{messages['mail.onetime.heading']}</m:subject>
    <m:body>
        Your one time password is: <i>#{oneTimePassword}</i>
        <p>#{messages['mail.onetime.noreply']}</p>
    </m:body>
</m:message>
Thats really all there is to it! Now your emails will be sent asynchronously.

Friday, March 26, 2010

Advanced Seam series part 2 of 3: Seam Filters

Seam Filters.

In this blog post I will be showing two filters. One that hacks in UTF-8 as encoding, and the other, a download filter for downloading files efficiently.

Seam filters can potentially be called outside of the Seam context, thus they are not truly a Seam component, but they act as one. However, they are able to reuse the functionality for component scanning, installation and configuration for filters.

There are two ways to define filters.

1. Through web.xml
<filter>
    <filter-name>UTF8 Filter</filter-name>
    <filter-class>com.something.filter.UTF8Filter</filter-class>
</filter>

<filter-mapping>
      <filter-name>UTF8 Filter</filter-name>
      <url-pattern>*.seam</url-pattern>
</filter-mapping>

And the Filter it self:
This filter is a fix for a bug in Seam encoding filter. More information here.
Remember you must implement Filter.
/**
 * This Filter is a fix for bug in seam encoding filter, see here for more details: https://jira.jboss.org/jira/browse/JBSEAM-3006
 * It might be removed if the version will be upgraded to at least 2.2.1.CR1
 *    
 */
public class UTF8Filter implements Filter {

    public void destroy() {}

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)   throws IOException, ServletException {
        // set encoding to UTF-8
        req.setCharacterEncoding("UTF-8");
        chain.doFilter(req, res);
        return;
    }

    public void init(FilterConfig arg0) throws ServletException {}
}
However, the servlet specification doesn't provide a well defined order if you specify your filters in a web.xml, however Seam provides a way to do that by using the @Filter annotation.

In this next example I will show an efficient and pretty safe download filter which you can use to serve files to the user. First I want to present the way you shouldn't do it.

Say you have a File entity class.
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.GeneralSecurityException;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Transient;

import org.hibernate.validator.NotNull;

@Entity
public class File {

    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    @Column(nullable = false, length = 256)
    private String name;

    @Column
    private String hash;

    // BLOB = L + 2 bytes (max size is 2^16 - 1 or 65,535 bytes, 65KB)
    // MEDIUMBLOB = L + 3 bytes (max size is 2^24 - 1 or 16,777,215 bytes, 16MB)
    // LONGBLOB = L + 4 bytes (max size is 2^32 - 1 or 4,294,967,295 bytes, 4GB)
    @Basic(fetch = FetchType.LAZY)
    // @Basic is used in conjunction with @Lob
    @Lob             
    // Set to MAX 100MB LONGBLOB in MySQL
    @Column(length = 104857600, nullable = false)
    private byte[] data;

    @PrePersist
    @PreUpdate
    public void setHash() {
        try {
            hash = PasswordSupport.generateFileHash(data);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
            hash = null;
        }
    }

    @Transient
    public InputStream getInputStream() {
        return new ByteArrayInputStream(data);
    }
    //getters and setters
}
For the PasswordSupport class (which basically hashes the input), have a look at my previous post. We always want to generate a new hash if a new file appears, thus the triggers @PreUpdate and @PrePersist

But to save you some time, here is the method:
    /**
     * Hash file
     * @throws GeneralSecurityException 
     */
    public static final String generateFileHash(byte[] data) throws GeneralSecurityException {
        byte[] salt = new byte[1024];
        String theHash;
        try {
            AnnotatedBeanProperty<UserPassword> userPasswordProperty = new AnnotatedBeanProperty<UserPassword>(ProcessUser.class, UserPassword.class);
            // Will get the hash value from annotation UserPassword in User.class
            PasswordHash.instance().setHashAlgorithm(userPasswordProperty.getAnnotation().hash().toUpperCase());
            theHash = PasswordHash.instance().createPasswordKey(String.valueOf(Arrays.hashCode(data)).toCharArray(), salt, userPasswordProperty.getAnnotation().iterations());
            return theHash;
        } finally {
            salt = null;
            theHash = null;
        }
    }
Typically, you would do the following to serve the user a file:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.FileNameMap;
import java.net.URLConnection;

import javax.annotation.PostConstruct;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletResponse;

import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;

@Name("fileService")
@AutoCreate
@Stateless
@Local(FileService.class)
public class FileServiceImpl implements FileService {

    @In
    private EntityManager entityManager;

    private FileNameMap fileNameMap;
    
    @PostConstruct
    public void contruct() {
        fileNameMap = URLConnection.getFileNameMap();
    }
    
    @Deprecated
    public void download(File file) throws IOException {
        byte[] data = file.getData();
    
        HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
    
        //Take mime type from filename, I don't know if the null check is necessary, the API doesn't say. 
        String mime = fileNameMap.getContentTypeFor(file.getName());
        if (mime == null || "".equals(mime.trim())) {
            mime = "application/octet-stream";
        }
        response.setContentType(mime);
        response.addHeader("Content-Disposition", "attachment;filename=" + file.getName());
    
        response.setContentLength(data.length);
    
        OutputStream writer = response.getOutputStream();
    
        writer.write(data);
        writer.flush();
        writer.close();
    
        //Skip the rest of JSF phases
        FacesContext.getCurrentInstance().responseComplete();
    }
    
        //rest of ejb not shown
}
This method of doing it is bad because it will load the file in to memory and serve it to the user. Not to mention all the Seam interceptors along the way.

A much better way is to use a filter.
All you need to do is create a link that has the downloadFileId as parameter and the filter will activate, like this http://somedomain.com/myApp/somePage.seam?downloadFileId=1-BAC272728189887A672

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;

import javax.faces.context.FacesContext;
import javax.persistence.EntityManager;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.mydomain.model.File;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.annotations.web.Filter;
import org.jboss.seam.contexts.Lifecycle;
import org.jboss.seam.web.AbstractFilter;

/**
 * Implemented from suggestion on 
 * {@link http://seamframework.org/Community/LargeFileDownload}
 * @author Shervin Asgari
 * 
 */
@Name("downloadFilter")
@Filter(around = { "org.jboss.seam.web.ajax4jsfFilter" })
@Scope(ScopeType.APPLICATION)
@Startup
@BypassInterceptors
public class DownloadFilter extends AbstractFilter {

    public void doFilter(ServletRequest request, ServletResponse resp, FilterChain arg2) throws IOException, ServletException {
        if (HttpServletRequest.class.isAssignableFrom(request.getClass())) {
            HttpServletRequest req = (HttpServletRequest) request;
            HttpServletResponse response = (HttpServletResponse) resp;
            String downloadFileId = req.getParameter("downloadFileId");
            
            //The param is seperated with the id of the file and the hash eg http://localhost:8080/myapp/somePage.xhtml/?downloadFileId=1-8B94B45466C738FE90EF9CA8D47281EAE821F4E0 
            
            //The two ifs could be one, but for debugging and breakpoints, its easier this way
            if (downloadFileId != null && downloadFileId.matches("^\\d+-.*$")) {
                boolean started = false;
                try {
                    String[] ids = downloadFileId.split("-",0);
                    if(ids.length >= 1) {
                        String fileId = ids[0];
                        String hash = ids.length >= 2 ? ids[1] : null;
                        
                        started = true;
                        Lifecycle.beginCall();
                        File file = null;
                        
                        EntityManager entityManager = (EntityManager) Component.getInstance("entityManager");

                        if(hash != null) {
                            file = (File) entityManager.createQuery("SELECT f FROM " + File.class.getName() + " f " +
                                    "WHERE f.id=:id and f.hash=:hash").setMaxResults(1).setParameter("id", Long.valueOf(fileId)).setParameter("hash", hash).getSingleResult();
                        } else {
                            //This is here for backward compatibility
                            file = (File) entityManager.createQuery("SELECT f FROM " + File.class.getName() + " f " +
                            "WHERE f.id=:id and f IS NULL").setMaxResults(1).setParameter("id", Long.valueOf(fileId)).getSingleResult();
                        }
                        
                        if(file != null) {
                            FacesContext facesContext = FacesContext.getCurrentInstance();
                            InputStream stream = file.getInputStream();
                            response.setContentType("application/octet-stream");
                            response.setContentLength(stream.available());
                            response.setCharacterEncoding("UTF-8");
                            response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(file.getName(), "UTF-8"));
                            OutputStream out = response.getOutputStream();
                            try {
                                byte[] buf = new byte[response.getBufferSize()];
                                int len;
                                while ((len = stream.read(buf)) > 0) {
                                    out.write(buf, 0, len);
                                }
                            } finally {
                                out.close();
                            }
                                
                            if(facesContext != null)
                                facesContext.responseComplete();
                        }
                    }
                    
                } catch (Exception e) {
                    e.printStackTrace();
                    resp.getOutputStream().write(String.valueOf("Error downloading file" + e.getMessage()).getBytes());
                }

                finally {
                    if(started)
                        Lifecycle.endCall();
                    else 
                        arg2.doFilter(req, response);
                }
            } else {
                arg2.doFilter(req, response);
            }
        }
    }
}
This way you will not load the file into memory, and avoid all the interceptors of Seam, thus it will perform much faster and better. Note that we have included a backward compatibility in case the file entity does not have a hash yet.

Saturday, February 13, 2010

Advanced Seam series part 1 of 3: Seam Interceptors

Seam Interceptors

EJB 3.0 introduced a standard interceptor model for session bean components. Interceptors are a great way of performing extra functionality. I will not go into detail of what Interceptors are. If you want to know more, google is your friend. 

Prerequisites you should know:
  • Seam has a few built in Interceptors, these include but are not limited to:
    • BijectionInterceptor
    • MethodContextInterceptor 
    • ConversationInterceptor
    • SynchronizationInterceptor
    • ConversationalInterceptor
    • RemoveInterceptor
    • SeamInterceptor
    • SecurityInterceptor
    • TransactionInterceptor
    • EventInterceptor
    • HibernateSessionProxyInterceptor   
  • @BypassInterceptors will skip Interceptors 
In this example I will show you two examples on how you can use your own interceptor as a way to enhance your security domain and measure how long each method takes to execute.

First example
Imagine this simple model. A user is connected to one organization, and only the user connected to that Organization is allowed to view information about that organization.



Let's say you print out the users organization, and you have a page with detailed information about that organization like this:

<s:link view="myOrganization.xhtml" value="#{organizationAction.choose}">
    <f:param name="id" value="#{organization.id}"/>
</s:link>

In the request, something like this will appear:
http://mydomain.com/myOrganization.xhtml?actionMethod=myOrganization.xhtml%3organizationAction%choose()&organizationId=3

And this is how your simple OrganizationAdmin.java component looks like:

@Name("organizationAdmin")
@Scope(ScopeType.CONVERSATION)
@Restrict("#{s:hasRole('user')}&;quot;)
public class OrganizationAction {

    @RequestParameter
    Long id;

    @In
    EntityManager entityManager;

    Organization myOrganization;
   
    @Begin
    public void choose() {
        if(id != null) {
            this.myOrganization = (Organization) entityManager.find(Organization.class, id);
        }
    }

    public Organization getMyOrganization() {
          return this.myOrganization;
    }
}

Now, imagine changing the organizationId in the request to show information about an organization you should initially not be allowed to see.

This is obviously very bad. We have restricted the component to role user, but another user can easily change the request to another id, and get hold of another organization.

To fix this, you can add this extra security check:

@Name("organizationAdmin")
@Scope(ScopeType.CONVERSATION)
@Restrict("#{s:hasRole('user')}")
public class OrganizationAction {

    @RequestParameter
    Long id;

    @In
    EntityManager entityManager;

    Organization myOrganization;
   
    @In(create = true)
    User currentUser; //The current user logged in
   
    @Begin
    public void choose() {
        if(id != null) {
            this.myOrganization = (Organization) entityManager.find(Organization.class, id);
            if(!currentUser.getOrganization().getId().equals(myOrganization.getId()))
                throw new SecurityException("You are not allowed to view this page");
        }
    }

    public Organization getMyOrganization() {
          return this.myOrganization;
    }
}

Voila! Now if an authorized user tries to "hack" the system, you will throw a SecurityException. This is fine and works just fine. However, it is cumbersome to do and you have to remember to do this everywhere where these exploits exists. 
Wouldn't it be nice to just do this:

@Name("organizationAdmin")
@Scope(ScopeType.CONVERSATION)
@Restrict("#{s:hasRole('user')}")
@MySecurityAuthorized
public class OrganizationAction {

    @RequestParameter
    Long id;

    @In
    EntityManager entityManager;

    Organization myOrganization;
 
    @Begin
    public void choose() {
        if(id != null) {
            this.myOrganization = (Organization) entityManager.find(Organization.class, id);
        }
    }
    @AuthorizeOrganization
    public Organization getMyOrganization() {
          return this.myOrganization;
    }
}

I for one think this is much easier to do. You can even reuse the @AuthorizeOrganization annotation to apply the same security other places. You only need to write the logic once.

Our first Interceptor
We can do this by using @Interceptor or @Interceptors. The latter is pre Java EE 5 compatible. I will cover both, but will show the latter in this example.

We start by creating our two annotations.
@AuthorizeOrganization
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthorizeOrganization {}
@MySecurityAuthorized
/**
 * This annotation can be added to any Seam component to hook in interception of our custom annotations.
 * Important: Unless a custom specific @AuthorizeX annotation (like @AuthorizeOrganization) is also present, this annotation won't do anything 
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@org.jboss.seam.annotations.intercept.Interceptors(MyInterceptor.class)
public @interface MySecurityAuthorized {}

Note the @Interceptors(MyInterceptor.class). It is here we register this annotation to the Interceptor MyInterceptor.
AnnotationHandler.java
Note that this is just an interface that all our custom annotations will inherit from


public interface AnnotationHandler {
    public void preProceed();
    public void postProceed(Object retObj);
    public boolean isCurrentlyUsed();
}

BasicHandler.java
An abstract class all handler will extend from. Read javadoc and comments in code to get real understanding of what's going on. Much of this code is really not necessary for this simple example. But this is a good portion of the one we are using, and it is quite flexible.

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.interceptor.InvocationContext;

import org.jboss.seam.Component;
import org.jboss.seam.core.Events;
import org.jboss.seam.log.Logging;

/**
 * Class that can be extended when creating new custom managed annotations.
 * Add extending classes to MyInterceptor.
 *
 * @param <T> The Annotation that should be looked for
 */
public abstract class BasicHandler<T extends Annotation, U> {

    private User user;
    private Boolean isAdmin;
    
    private boolean isMethodAnnotated;
    private List<U> allInstanceParams = new ArrayList<U>();
    private List<U> annotatedInstanceParams = new ArrayList<U>();
    private List<Long> annotatedIdParams = new ArrayList<Long>();
    
    private Class<U> authorizedClass;
    
    /**
     * This constructor parses the method signature for annotations,
     * validated annotated types and
     * if needed verifies the current User
     * @param ic, the current InvocationContext
     * @param annotationClazz, the class of the annotation to be scanned for
     * @param authorizedClass, the class of the object to authorize
     */
    @SuppressWarnings("unchecked")
    public BasicHandler(InvocationContext ic, Class<T> annotationClazz, Class<U> authorizedClass) {

        this.authorizedClass = authorizedClass;
        
        //method annotation
        Annotation annotation = ic.getMethod().getAnnotation(annotationClazz);
        if (annotation != null) {
            isMethodAnnotated = true;
        }
        
        //parameter annotations - why why why is there no Parameter class with methods for annotations, values, types?
        Annotation[][] aaa = ic.getMethod().getParameterAnnotations();
        for (int i = 0; i < aaa.length; i++) {
            Object obj = ic.getParameters()[i];    
            if (isMethodAnnotated) {
                allInstanceParams.add((U) obj);
            }
            Annotation[] aa = aaa[i];
            for (int j = 0; j < aa.length; j++) {
                Annotation a = aa[j];
                if (a.annotationType().equals(annotationClazz) && ic.getParameters()[i] != null) {
                    //So we've found an annotated not null param
                    if (authorizedClass.isInstance(obj)) {
                        //instance of class to be authorized
                        annotatedInstanceParams.add((U) obj);
                    } else if (obj instanceof Long) {
                        //id of instance of class to be authorized
                        annotatedIdParams.add((Long) obj);
                    } else {
                        //Annotation put on erronous type
                        illegalObjectAnnotated((U) obj, annotationClazz);
                    }
                    //params.add(ic.getParameters()[i]);
                }
            }    
        }

        //Verify user
        if (isCurrentlyUsed()) {
            getUser();
        }        
    }
    
//public methods
    
    public boolean isCurrentlyUsed() {
        return (isMethodAnnotated || !annotatedInstanceParams.isEmpty() || !annotatedIdParams.isEmpty() ? true : false);
    }
    
//protected methods
    
    @SuppressWarnings("unchecked")    
    protected List<U> getAllReturnInstances(Object retObj) {
        List<U> l = new ArrayList<U>();
        
        if (isMethodAnnotated()) {            
            if (authorizedClass.isInstance(retObj)) {
                //Simple object
                l.add((U) retObj);
            } else if (retObj instanceof Collection<?>) {
                //Collections
                for (Object obj : (Collection<?>) retObj) {
                    if (authorizedClass.isInstance(obj)) {
                        l.add((U) obj);
                    }                     
                }
            }
        }            
        return l;
    }

    /**
     * Authorizing Organization
     * Role "admin" may access everything
     * Other (like "user") must be within the organization to have privilege
     * @param organization
     */    
    protected boolean authorize(Organization organization) {
        if (organization != null) {
            return authorizeUser(organization.getId().toString());
        } else {
            //null object need no authorization                
            return true;            
        }        
    }
    
    /**
     * Getting current logged in user
     * @return Getting current logged in user
     */
    protected User getUser() {
        if (user == null) {
            user = (User) Component.getInstance("currentUser", true);
            if (user.getId() == null || user.getId() == 0) {
                //annotation is present but we don't have an user
                logAndThrow("User is null, must exsist to enable authentication");
            } 
        }
        return user;
    }
    
    /**
     * Is user admin?
     * @return
     */
    protected boolean isAdmin() {
        if (isAdmin == null) {
            return isRoleInRoles(user.getRoles(), "admin");
        }
        return isAdmin;
    }

    /**
     * Checks to see if the role is in the Collection<Role>
     * If one of the roles appears in the Collection it returns true
     * 
     * @param allroles - All the roles
     * @param roles - varargs containing the roles to check for
     * 
     * @return true if any of the role is found
     */
    private boolean isRoleInRoles(Collection<Role> allroles, String ...roles) {
        if (allroles == null || roles == null || roles.length == 0)
            return false;

        for (Role r : allroles)
            for(String role : roles)
                if (r.getName().equals(role))
                    return true;

        return false;
    }
    
    protected boolean logAndThrow(String s) {
        Logging.getLogProvider(this.getClass()).warn(s);
        throw new SecurityException(s);
    }

//private methods

    private boolean authorizeUser(String orgId) {
        User user = getUser();

        if (isAdmin()) {
            //Admin can do whatever            
            return true;
        } else if (user.getOrganization().getId().equals(orgId)) {
            //Correct organization, can also check role here if you want
            return true;                                
        } else {
            //wrong organization, fail authorization
            return logAndThrow("User '" + user.getUsername() + "' is not authorized to view this page");
        }                
    }
    
    private boolean isMethodAnnotated() {
        return isMethodAnnotated;
    }
    
    private void illegalObjectAnnotated(U obj, Class<? extends Annotation> annotationClass) {
        logAndThrow("Program error, annotation " + annotationClass.getSimpleName() + " cannot handle object of type " + obj.getClass().getSimpleName());
    }    
    
//Getters    

    public List<U> getAllInstanceParams() {
        return allInstanceParams;
    }    
    
    public List<U> getAnnotatedInstanceParams() {
        return annotatedInstanceParams;
    }    

    public List<Long> getAnnotatedIdParams() {
        return annotatedIdParams;
    }        
}

@AuthorizeOrganizationHandler.java

/**
 * This AnnotationHandler handles the @AuthorizeOrganization annotation.
 * It authorizes a User for an Organization
 *
 */
public class AuthorizeOrganizationHandler extends BasicHandler<AuthorizeOrganization, Organization> implements AnnotationHandler {
    
    /**
     * This constructor looks for @AuthorizeOrganization markers on method
     * If any exists, User is validated directly.
     * @param ic
     */
    public AuthorizeOrganizationHandler(InvocationContext ic) {
        super(ic, AuthorizeOrganization.class, Organization.class);
    }

//Public methods
    
    public void preProceed() {
        //Method level - input parameters
        for (Organization obj : getAllInstanceParams()) {
            authorize(obj);
        }
    }

    public void postProceed(Object retObj) {
        //return value(s)
        for (Organization obj : getAllReturnInstances(retObj)) {
            authorize(obj);
        }        
    }
        
}


Last but not least, our interceptor.
MyInterceptor.java

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

/**
 * This interceptor handles method/param level annotations
 * (Seam only provides Class (ElementType.TYPE) level annotation support)
 * Thus this interceptor must be added to those beans that wish to use these annotations
 * If we implement new annotations, their corresponding AnnotationHandler must be registered below
 *
 */
public class MyInterceptor {
    @AroundInvoke
    public Object aroundInvoke(InvocationContext ic) throws Exception {
        
        //gather custom annotation handlers
        List<AnnotationHandler> ahs = new ArrayList<AnnotationHandler>();
        addHandlerIfNeeded(new AuthorizeOrganizationHandler(ic), ahs);
        //add new annotation handlers here - TODO: Use more clever generics instead
        
        //handle pre proceed
        for (AnnotationHandler ah : ahs) {
            ah.preProceed();
        }

        //proceed
        Object obj = ic.proceed();
        
        //handle post proceed
        Collections.reverse(ahs); //Reversing list in order to get same behavior as interceptors/filters
        for (AnnotationHandler ah : ahs) {
            ah.postProceed(obj);
        }
                
        return obj;
    }

    private void addHandlerIfNeeded(AnnotationHandler handler, List<AnnotationHandler> ahs) {
        if (handler.isCurrentlyUsed()) {
            ahs.add(handler);
        }
    }
}

Thats it! Now you can easily add new annotation and handlers and just add login in BasicHandler. If you look closely you will see that we automatically give all privileges to admin, but only check user's organization if they are role user. You can modify and do whatever fits your need.

Second Example

Finally I want to also mention the other Seam interceptor.
This is taken directly from the Seam forum (Link here). Which will print something like this:
284.94 ms   1   FooBean.getRandomDroplets()
284.56 ms   1   GahBean.getRandomDroplets()
201.60 ms   2   SohBean.searchRatedDoodlesWithinHead()
185.94 ms   1   FroBean.doSearchPopular()
157.63 ms   1   FroBean.doSearchRecent()
 42.34 ms   1   FooBean.fetchMostRecentYodel()
 41.94 ms   1   GahBean.getMostRecentYodel()
 15.89 ms   1   FooBean.getNoOfYodels()
 15.00 ms   1   GahBean.getNoOfYodels()
  9.14 ms   1   SohBean.mainYodels()
  1.11 ms   2   SohBean.trackHoorayEvent()
  0.32 ms   1   FroBean.reset()
  0.22 ms  43   NohBean.thumbPicture()
  0.03 ms  18   FooBean.getMostRecentYodels()
  0.01 ms   1   NohBean.profilePicture()
  0.01 ms   1   FroBean.setToDefault()
  0.01 ms   1   FroBean.getRecentMarker() 



The first column contains the accumulated time spent in this method. The second column contains the number of times the method has been called, and the final column shows which method was called.

This Interceptor will measure how long each method will take to execute in milliseconds.
TimingInterceptor.java

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.jboss.seam.annotations.intercept.AroundInvoke;
import org.jboss.seam.annotations.intercept.Interceptor;
import org.jboss.seam.core.BijectionInterceptor;
import org.jboss.seam.core.ConversationInterceptor;
import org.jboss.seam.core.ConversationalInterceptor;
import org.jboss.seam.core.EventInterceptor;
import org.jboss.seam.core.MethodContextInterceptor;
import org.jboss.seam.core.SynchronizationInterceptor;
import org.jboss.seam.ejb.RemoveInterceptor;
import org.jboss.seam.ejb.SeamInterceptor;
import org.jboss.seam.intercept.InvocationContext;
import org.jboss.seam.persistence.HibernateSessionProxyInterceptor;
import org.jboss.seam.security.SecurityInterceptor;
import org.jboss.seam.transaction.TransactionInterceptor;

/**
 * This interceptor will calculate how long it takes to execute each method
 * 
 * Copied and modified from @link http://www.seamframework.org/Community/SeamPerformanceProblemRewardingWorkaround
 *
 */
@Interceptor(
    around = {
        BijectionInterceptor.class,
        MethodContextInterceptor.class,
        ConversationInterceptor.class,
        SynchronizationInterceptor.class,
        ConversationalInterceptor.class,
        RemoveInterceptor.class,
        SeamInterceptor.class,
        SecurityInterceptor.class,
        TransactionInterceptor.class,
        EventInterceptor.class,
        HibernateSessionProxyInterceptor.class
})
public class TimingInterceptor {

    public final static CallChain callChain = new CallChain();

    @AroundInvoke
    public Object timeCall(InvocationContext invocation) throws Exception {
        long t0 = System.nanoTime();
        try {
            return invocation.proceed();
        } finally {
            long dt = System.nanoTime() - t0;
            callChain.addInvocation(invocation, dt);
        }
    }

    // -----------------------------------------------------------------------------

    /**
     * A call chain is the set of invocations on methods (annotated 
     * with MeasureCalls) that a request issued on its way through 
     * the application stack.
     */
    public static class CallChain extends ThreadLocal<Map<Method, TimedInvocation>> {

        @Override
        protected Map<Method, TimedInvocation> initialValue() {
            return new HashMap<Method, TimedInvocation>();
        }

        public void addInvocation(InvocationContext invocation, long dt) {
            Map<Method, TimedInvocation> invocations = get();
            Method method = invocation.getMethod();
            if (!invocations.containsKey(method)) {
                invocations.put(method, new TimedInvocation(invocation.getMethod(), dt));
            } else {
                TimedInvocation timedInvocation = invocations.get(method);
                timedInvocation.anotherCall(dt);
            }
        }

        public int totalNumberOfInvocations() {
            Map<Method, TimedInvocation> invocations = get();
            Collection<TimedInvocation> timedInvocationCollection = invocations.values();
            int totCalls = 0;
            for (TimedInvocation invocation : timedInvocationCollection)
                totCalls += invocation.getCalls();
            return totCalls;
        }
    }
}

TimedInvocation.java

import java.lang.reflect.Method;

import org.apache.commons.lang.StringUtils;

/**
 * TimedInvocation is an invocation (i.e. a method call) which is being
 * counted and timed. 
 */
public class TimedInvocation implements Comparable<TimedInvocation> {

    private long dt;
    private int calls = 1;
    private Method method;

    public TimedInvocation(Method method, long dt) {
        this.method = method;
        this.dt = dt;
    }

    public long getDt() {
        return dt;
    }

    public Method getMethod() {
        return method;
    }

    /**
     * The first column contains the accumulated time spent in this method. 
     * The second column contains the number of times the method has been called.
     * The third column contains the method which was called
     */
    public String toString() {
        String className = method.getDeclaringClass().getName();
        String shortendName = className.substring(method.getDeclaringClass().getPackage().getName().length() + 1);
        String duration = StringUtils.leftPad(Double.valueOf(dt / 1e6) + " ms", 11);
        String nCallStr = StringUtils.leftPad(String.valueOf(calls), 4);
        return duration + nCallStr + "   " + shortendName + "." + method.getName() + "()";
    }

    public void anotherCall(long dt) {
        this.dt += dt;
        calls++;
    }

    public int compareTo(TimedInvocation o) {
        return -Long.valueOf(dt).compareTo(o.dt);
    }

    public int getCalls() {
        return calls;
    }
}

Lastly, we will create a simple filter that will print out the result. The filter is only installed in debug mode so that it doesn't run on production. Be aware that the interceptor will still run if you don't forget to uncomment the interceptor.
TimingFilter.java

/**
 * This filter is used in conjunction with the TimingInterceptor. * 
 * It is defaulted to only work in debug mode (debug = true)
 * To use this filter you need to set value to true
 * 
 */
@Startup
@Scope(ScopeType.APPLICATION)
@Name("timingFilter")
@BypassInterceptors
@Filter
@Install(debug = true)
public class TimingFilter extends AbstractFilter {
    
    private static final LogProvider log = Logging.getLogProvider(TimingFilter.class);
    
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        if (!(req instanceof HttpServletRequest)) {
            chain.doFilter(req, res);
            return;
        }
        
        TimingInterceptor.callChain.remove();
        chain.doFilter(req, res);
        
        Map<Method, TimedInvocation> invocations = TimingInterceptor.callChain.get();
        for(TimedInvocation timedInvocation : invocations.values()) {
            log.debug(timedInvocation.toString());
        }
    }
}
Finally create an annotation where you register the interceptor.

@MeasureCalls

/**
 * This is an annotation that is used with the Interceptor TimingInterceptor,
 * that will measure time it takes to execute each method in a seam component.
 * 
 * The filter that uses this interceptor only works in debug mode and you will need to set value to true 
 * on the TimingFilter. Otherwize you will not see anything when running this in production or test. 
 * However, the interceptor will still run, so be "careful" where you put this annotation.
 * 
 * Usage: 
 * @AutoCreate
 * @Name("theBeanILikeToInvestigate")
 * @MeasureCalls
 * public class TheBeanILikeToInvestigare
 * 
 *
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Interceptors(TimingInterceptor.class)
public @interface MeasureCalls {}

Just put @MeasureCalls on your component and all methods on that component will be invoked on.

Tuesday, February 9, 2010

Advanced Seam series

In the upcoming weeks I will be posting some nice articles about some of the advanced features of Seam I know people have trouble understanding and incorporating. 

The articles will be a "three" (may be more) part series where I will cover the topics Interceptors, Filters and Asynchronous events.

I will show working real life examples of all three. You will learned:
  • What Interceptors are, and how you can use them and incorporate security techniques using interceptors.
  • Filters - Why they are useful to use. 
    • I will show how you can implement a filter that enables downloading of files without consumption of unnecessary memory.
  • Asynchronous - I will show how you can send a mail asynchronously

If there are any advanced areas you wish to know more about, let me know by writing in the comment section and I will consider adding them to the series.

Labels