Status

Blog::Calendar

« September 2010
SunMonTueWedThuFriSat
   
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  
       
Today
XML

Blog::Navigation

Blog::Editing

Bookmarks::Blogroll

Blog::Referers

Today''s Page Hits: 435

Other sites

Google Analytics

Powered by Roller Weblogger.
All | JBoss&Seam | JSF | ZK | Music | General | Java
20100506 Thursday May 06, 2010
A solution that fixes accessing Seam components bug in multiple WAR in a EAR
Seam is a great web-bean framework indeed.
However, It has a limitation when running EAR with multiple WAR.
look into the code of Lifecycle and ServletLifecycle, you can see it store applicationContext in 'static' way.
which means , if you have multiple WAR in a EAR, the last initialized WAR become the application context of entire EAR.
It doesn't matter if you only access Seam component in JSF page/event, because FacesLifecycle provide another way to retrieve application context.
but, if you want to access Seam component in a custom servlet or in a thread (for example:Timer, Scheduler), you will always get the last registered application(WAR module) and it might be wrong one if servlet and thread are running for another WAR module.
Following is the example to access Seam component in servlet and thread.
[Access Seam component in Servlet]
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        ServletLifecycle.beginRequest(request);
        try{
            Requests.instance().setRequest(request);
            // do some thing.
        }finally{
            ServletLifecycle.endRequest(request);
        }
    }
[Access Seam component in Thread]
public void run() {
    Lifecycle.beginCall();
    try{
        //do something
    }finally{
        Lifecycle.endCall();
    }
}
Since Lifecycle and ServletLifecycle handle application context in static, so you might get wrong application when use it.
To solve this , we have to know which application context we needed when we want to initial Seam Thanks Open Source, after deep into the code, I has a chance to fix it.
I wrote two Lifecycle Util and one seam component to workaround it.
[LifecycleEx] : helps you to retrieve application by name.
package org.jboss.seam.contexts;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.jboss.seam.ScopeType;
import org.jboss.seam.core.Manager;
import org.jboss.seam.log.Log;
import org.jboss.seam.log.Logging;
/**
 * A enhanced lifecycle controller for running seam in multiple war
 * 
 * @author dennis
 */
public class LifecycleEx {
    
    private static String NULL_APP_NAME = "LifecycleEx.NULL.APPNAME";
    private static Map<String,Object> defaultApplication = null;
    private static Map<String,Map<String,Object>> applications = new HashMap<String,Map<String,Object>>();
    private static List<Map<String,Object>> applicationList = new LinkedList<Map<String,Object>>();
    private static final Log log = Logging.getLog(LifecycleEx.class);
    
    static public void registerApplication(String name,Map<String,Object> application){
        
        name = name == null?NULL_APP_NAME:name;
        
        if(applications.containsKey(name)){
            log.warn("trying to register application '#0' again",name);
        }
        
        applications.put(name,application);
        applicationList.add(application);
        
        defaultApplication = application;
    }
    
    static public void unregisterApplication(String name){
        name = name == null?NULL_APP_NAME:name;
        
        Map<String,Object> app = applications.remove(name);
        
        if(app!=null){
            applicationList.remove(app);
            if(app==defaultApplication){
                if(applicationList.size()>0){
                    //last one
                    defaultApplication = applicationList.get(applicationList.size()-1);
                }else{
                    defaultApplication = null;
                }
            }
        }
    }
    
    static Map<String,Object> getApplication(String name){
        return getApplication(name,false);
    }
    
    /**
     * get Application context by name, if no application context found, then try to get default application context (the last registered)
     * @param name name of application
     * @param as_possible true: if get application not found, then return default application 
     * @return RuntimeException if no application found.
     */
    static Map<String,Object> getApplication(String name,boolean as_possible){
        name = name == null?NULL_APP_NAME:name;
        
        Map<String,Object> app = applications.get(name);
        if(app==null&&as_possible){
            log.debug("name #0 not found, use default application",name);
            app = defaultApplication;
        }
        if(app==null){
            throw new RuntimeException("application "+name+" not found ");
        }
        return app;
    }
    
    public static void setupApplication(String name)
    {
       Contexts.applicationContext.set( new ApplicationContext(getApplication(name)) );
    }

    public static void cleanupApplication()
    {
       Contexts.applicationContext.set(null);
    }
    
    static void clearThreadlocals() 
    {
       Contexts.eventContext.set(null);
       Contexts.pageContext.set(null);
       Contexts.sessionContext.set(null);
       Contexts.conversationContext.set(null);
       Contexts.businessProcessContext.set(null);
       Contexts.applicationContext.set(null);
    }
    
    public static void beginCall(String appname){
        beginCall(appname,false);
    }
    
    public static void beginCall(String appname,boolean as_possible)
    {
       Contexts.applicationContext.set( new ApplicationContext(getApplication(appname,as_possible)) );
       Contexts.eventContext.set( new BasicContext(ScopeType.EVENT) );
       //TODO should we initial session?? 
       Contexts.sessionContext.set( new BasicContext(ScopeType.SESSION) );
       Contexts.conversationContext.set( new BasicContext(ScopeType.CONVERSATION) );
       Contexts.businessProcessContext.set( new BusinessProcessContext() );
    }

    public static void endCall()
    {
       try
       {
          Contexts.destroy( Contexts.getSessionContext() );
          Contexts.flushAndDestroyContexts();
          if ( Manager.instance().isLongRunningConversation() )
          {
             throw new IllegalStateException("Do not start long-running conversations in direct calls to EJBs");
          }
       }
       finally
       {
          clearThreadlocals();
       }
    }
}
[ServletLifecycleEx] : helps you to retrieve application by name.
package org.jboss.seam.contexts;

import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

import org.jboss.seam.log.Log;
import org.jboss.seam.log.Logging;
import org.jboss.seam.servlet.ServletApplicationMap;
import org.jboss.seam.servlet.ServletRequestMap;
import org.jboss.seam.servlet.ServletRequestSessionMap;
import org.jboss.seam.web.Session;
/**
 * A enhanced servlet lifecycle controller for running seam in multiple war
 * @author dennis
 */
public class ServletLifecycleEx {

    private static final Log log = Logging.getLog(ServletLifecycleEx.class);
    
    static public void registerApplication(String name,ServletContext context){
        log.info("Register application context name #0",name);
        Map<String,Object> application = new ServletApplicationMap(context);
        LifecycleEx.registerApplication(name,application);
        
    }
    
    static public void unregisterApplication(String name){
        log.info("Unregister application context name #0",name);
        LifecycleEx.unregisterApplication(name);
        
    }
    
    public static void beginRequest(String path,HttpServletRequest request){
        log.debug( "begin web request #",path );
        Contexts.eventContext.set( new EventContext( new ServletRequestMap(request) ) );
        Contexts.sessionContext.set( new SessionContext( new ServletRequestSessionMap(request) ) );
        Contexts.applicationContext.set(new ApplicationContext( LifecycleEx.getApplication(path) ) );
        Contexts.conversationContext.set(null); //in case endRequest() was never called
    }
    
    public static void endRequest(HttpServletRequest request) {
        log.debug("After request, destroying contexts");
        try
        {
           Session session = Session.getInstance();
           boolean sessionInvalid = session!=null && session.isInvalid();
           
           Contexts.flushAndDestroyContexts();

           if (sessionInvalid)
           {
              LifecycleEx.clearThreadlocals();
              request.getSession().invalidate();
              //actual session context will be destroyed from the listener
           }
        }
        finally
        {
            LifecycleEx.clearThreadlocals();
        }
    }
}
[SeamLifecycleEx] : this component is for registering application context into LifecycleEx and ServletLifecycleEx.
package foo.bar;

import java.util.Map;
import javax.servlet.ServletContext;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Startup;
import org.jboss.seam.contexts.Lifecycle;
import org.jboss.seam.contexts.LifecycleEx;
import org.jboss.seam.contexts.ServletLifecycle;
import org.jboss.seam.contexts.ServletLifecycleEx;
import org.jboss.seam.log.Log;
import org.jboss.seam.log.Logging;

@Name("foo.bar.SeamLifecycleEx")
@Scope(ScopeType.APPLICATION)
@Startup @AutoCreate
public class SeamLifecycleEx {

    private static final Log log = Logging.getLog(SeamLifecycleEx.class);
    
    private String _name;
    
    @Create
    public void create(){
        log.debug("Initial SeamLifecycleEx");
        ServletContext context = ServletLifecycle.getServletContext();
        if(context==null){
            log.warn("servlet context not found, did you initial seam out of WAR moudle?");
            Map<String,Object> currentApplication= Lifecycle.getApplication();
            LifecycleEx.registerApplication(null,currentApplication);
            return;
        }
        _name = context.getContextPath();
        //in mock/test , _name is null
        if(_name!=null && _name.startsWith("/")){
            _name = _name.substring(1);
        }
        log.debug("register servlet application context with name #0 ",_name);
        ServletLifecycleEx.registerApplication(_name,context);
    }
    
    @Destroy
    public void destroy(){
        ServletLifecycleEx.unregisterApplication(_name);
    }
}
Ok, Now you have 3 java class, you have to put first 2 java into seam package (the package level protected issue) and put the seam component to where you want.
I don't want to explain how the code work, since you have my code, you can read it by yourself.
now, following is the example when you want to access Seam component in your servlet and thread.
[Access Seam component in Servlet]
    protected String name;

    public void init() throws ServletException {
        super.init();
        if(name.startsWith("/")){
            name = name.substring(1);
        }
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
            IOException {
        ServletLifecycleEx.beginRequest(name,request);
        try{
            Requests.instance().setRequest(request);
            // do some thing.
        }finally{
            ServletLifecycleEx.endRequest(request);
        }
    }

[Access Seam component in Thread]
 // in thread, name is a configured string usually.
public void run() {
    LifecycleEx.beginCall(name,true);
    try{
        //do something
    }finally{
        LifecycleEx.endCall();
    }
}
20091130 Monday November 30, 2009
When will seam create compoent instance.

When will seam cerate component instance?

20090728 Tuesday July 28, 2009
How to install JBPM4 into JBoss AS 4.2.3

Reference :
 http://www.mastertheboss.com/en/jbpm/209-jbpm-4-tutorial-installation.html
 http://www.li-zone.cn/index.php/2009/06/jbpm%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

  1. Install JBPM Schema
    1. create a schema named "jbpm4" on your local MySQL database. Then add an user named "jboss" to the database.
      CREATE DATABASE jbpm4;

      GRANT ALL PRIVILEGES ON *.* TO jboss@localhost
      -> IDENTIFIED BY 'jboss' WITH GRANT OPTION;
    2. Then edit the file JBPM4_HOME\db\jdbc\mysql.properties so that it contains our database properties:
      jdbc.driver=com.mysql.jdbc.Driver
      jdbc.url=jdbc:mysql://localhost:3306/jbpm4
      jdbc.username=jboss
      jdbc.password=jboss
    3. Now move the the "db" folder where you will find a build.xml. Launch the following command
      ant -Ddatabase=mysql create.jbpm.schema
  2. Install JBPM4 on JBoss 4.2.3
    1. Move into the "jboss" folder and open the ant build.xml file, Configure the following properties, at the top of the file:
      <property name="jboss.version" value="4.2.3.GA" />
      <property name="jboss.home" value="/the_path_of_jboss_ap" /> <!-- ex :/user/home/dennis/jboss-4.2.3.GA -->
    2. Launch the command, it will create a folder (JBoss AS server/default/deploy/jbpm), and copy file into it.
      ant -Ddatabase=mysql install.jbpm.into.jboss
  3. Manually configuration, JBPM4 installation command is only for JBossAS 5, so we need to do some thing manually
    1. Create folder DEPLOY/jbpm/jbpm-service.sar/jbpm.deployer/jbpm.beans/META-INF
    2. Create folder DEPLOY/jbpm/jbpm-service.sar/jbpm.deployer/META-INF
    3. Copy file JBPM4_HOME/jboss/config.jboss4/deploy/jbpm/jbpm-service.sar/jboss-beans.xml to DEPLOY/jbpm/jbpm-service.sar/jbpm.deployer/jbpm.beans/META-INF
    4. Copy file JBPM4_HOME/jboss/config.jboss4/deploy/jbpm/jbpm-service.sar/META-INF/jboss-service.xml to DEPLOY/jbpm/jbpm-service.sar/jbpm.deployer/META-INF
    5. Copy JBPM4_HOME/lib, bpm-spi.jar and jbpm-jboss4.jar to DEPLOY/jbpm/jbpm-service.sar/jbpm.deployer
  4. Start JBoss AS and link to  http://localhost:8080/jbpm-console/ , login with alex(password) .

Make it more automatically

you can modify JBPM4_HOME/jboss/build.xml to make it automatically configure on JBoss 4.2.3.

  1. Move into the "jboss" folder and open the ant build.xml file, configure the following properties, at the top of the file:
    <property name="jboss.version" value="4.2.3.GA" />
    <property name="jboss.home" value="/the_path_of_jboss_ap" /> <!-- ex :/user/home/dennis/jboss-4.2.3.GA -->
  2. Search <antcall target="internal.install.jbpm.into.jboss.500specifics" /> , append this line below it.
    <antcall target="internal.install.jbpm.into.jboss.400specifics" />
    1, Search <condition property="jboss.version.5">, append a condition declaration below the condition
    <condition property="jboss.version.4">
    <or>
    <equals arg1="${jboss.version}" arg2="4.2.3.GA" />
    </or>
    </condition>
  3. Search <target name="internal.install.jbpm.into.jboss.500specifics" if="jboss.version.5">, append a target below the target.
      <!-- ### THE JBOSS 4 SPECIFIC PART ############################### -->
    <target name="internal.install.jbpm.into.jboss.400specifics" if="jboss.version.4">
    <copy todir="${jboss.server.config.dir}/deploy/jbpm/jbpm-service.sar/jbpm.deployer/jbpm.beans/META-INF/" overwrite="true">
    <fileset dir="${jbpm.home}/jboss/config.jboss4/deploy/jbpm/jbpm-service.sar/" >
    <include name="jboss-beans.xml"/>
    </fileset>
    </copy>
    <copy todir="${jboss.server.config.dir}/deploy/jbpm/jbpm-service.sar/jbpm.deployer/META-INF/" overwrite="true">
    <fileset dir="${jbpm.home}/jboss/config.jboss4/deploy/jbpm/jbpm-service.sar/META-INF/" >
    <include name="jboss-service.xml"/>
    </fileset>
    </copy>
    <copy todir="${jboss.server.config.dir}/deploy/jbpm/jbpm-service.sar/jbpm.deployer/" overwrite="true">
    <fileset dir="${jbpm.home}/lib">
    <include name="jbpm-spi.jar"/>
    <include name="jbpm-jboss4.jar"/>
    </fileset>
    </copy>
  4. Launch the command
    ant -Ddatabase=mysql install.jbpm.into.jboss
20090718 Saturday July 18, 2009
Run seam register example in WAR

In past of two days, I was studying JBoss Seam, reading the document and trying to run the tutorial example.
I did a stupid thing that spent me half a day, the example must run in EAR deployment, but I ran it in WAR mode.

I am curious how to make it run in WAR mode.
Cause of the example is made for a EJB, so you need to declare @Stateless/@Stateful for the controller.
In document of Seam, it can control any bean to be a controller, no need to EJB , so we should able to remove EJB annotation.
However cause we still need an entity manager,that annotated by @PersistenceContext, to work, so we don't remove @Stateless/@Stateful directly.
Thanks Seam, it is easy to fix, use @In annotation, you can get a entity manager with out @PersistenceContext

following is the steps to run example in WAR mode.

  1. Remove @Stateless/@Stateful in RegisterAction
  2. Change @PersistenceContext to @In(value="entityManager") , or @In if the EntityManager value name is already "entityManager"
  3. Remove @Local in Register, keep Register interface, I think this interface is still good for a OO Design.
that's all, you only need to care about is the the name of "entityManager" must same as the declartion in components.xml

Copyright (C) 2003, 閣樓貓的五四三 (About Cat)