Search

Migration War Stories

Jack Wang's migration experiences

In this entry, Jack shares his notes for upgrading applications. Before we dive in, we asked Jack a few questions.


In a couple of sentences, what does the application do for its end-users?
We are an electronic component distributor. This application allows traders to bulk load data, matches requirements against offers, and allows traders to analyze and process data further into ERP system. This application is one of the several in-house built applications to be migrated.

*Can you give us some background on your application. How many JPA entities do you have? How many business components? How many views/pages? How long did it take to build the application, and with how many people? *
In the application we are currently migrating, there are 150+ entity beans, 200+ business logic components, and 100+ pages. We are also planning to build a CRM in-house. The CRM will be based on EE6 under JBoss platform. Hence the migration process is necessary and also a warm-up on the EE6 technology pack.

Is the app in production? What operating system and database do you use?
The pre-migrated app has been in production for many releases. It is on Linux + Oracle in testing and production environments and Windows + Oracle in development environment.

Did you already have automated tests? Or was validation of the migration manual checking and end-user testing?
We have continue-build setup, but no automated tests. The QA testing for the migration will mostly focus on black-box test. End-user test is the last step more for verification and approval purpose.

As a developer, how did it feel to go through this experience? are you and your organization better or worse off?
The migration process has two phases.

Phase I is trying to sort out the packaging structure changes, library dependencies, deployment configuration changes, etc.

In this phase we had the most struggled time because the gap between the current environment and targeting environment is so huge that there are too many variances when compilation fails, or deployment fails.

Eventually, we decided to do it step by step base on following roadmap:

  1. Migrate to Seam 2.1.2 with JBoss AS5
  2. Migrate to Seam 2.2.2 with JBoss AS5
  3. Migrate to Seam 2.2.2 with Jboss AS7
  4. Migrate to Seam 2.3.0_Beta1 + Richfaces 4.2.x + Hibernate 4.x with Jboss AS7.
  5. Migrate to CDI

The outcome of phase I is the project layout with libraries, and the migration guideline as I posted.

Phase II is relatively simple because all key issues had been sorted out. The real code change can now be distributed to the developers. The most time consuming work in this phase is the page migration.

Which element of the "stack" proved to be problematic (e.g. moving from Ant to Maven) and which one proved to be a biggest win (e.g. Seam 2 to CDI)?
Yes, the build change from Ant to Maven is probably the most problematic, and we are still not convinced yet. To me, using ANT is like you are driving the car - everything is under control; and using MAVEN is like taking the bus - the route is pre-defined, but you surrender the control, and you have to wait for the bus’s arrival (such as waiting for a well-configured bom, a well-functioned plugin, etc.), even more, the result may not be optimized for you.

The biggest win, I can say is moving to JSF2.x, which allows us to use Primefaces – Primefaces provides some UI components that are easy to use in the new CRM.

Another big win is moving to EAP6 (JBoss AS7), which provides better management, and gives us the potential to use lots of new technologies.


Summary

Our current application platform is based on following combination:

  • JDK 5
  • JBoss AS4.2.x
  • Seam 2.0.1GA
  • JSF 1.2
  • Richfaces 3.3.1
  • Hibernate 3.2.x
  • Build with ANT

The interim application platform during migration is based on following combination:

  • JDK 7
  • JBoss AS7.1
  • Seam 2.3.0
  • JSF 2.1
  • Richfaces 4.2.1
  • Hibernate 4.x
  • Build with ANT

The target application platform is based on following combination:

  • JDK 7
  • JBoss AS7.1
  • Seam 3.x (CDI)
  • JSF 2.1
  • Richfaces 4.2.1 + PrimeFaces 3.x
  • Hibernate 4.x
  • Build with Maven and private repository managed by Nexus 2.0.6

Application Server Configuration

  1. In AS7, all runtime server settings are consolidated into a single file in standalone server: standalone.xml, or domain.xml in a managed domain environment. That includes datasource definitions, transaction attributes, etc.

  2. Our applications normally maintain some configurable values in external property files or json files. Those external files will be put in $server.config.dir. And the property reader in the utility class needs to be changed accordingly.

Application Configuration

WEB-INF/components.xml

  • JNDI naming convention is changed from (Datasource as example) java:/partmatchDatasource to java:jboss/datasources/partmatchDatasource. Some of our the applications, have hard-coded JNDI references. This situation should either be re-implemented to use EntityManager, or move the JNDI names to external property file.

  • Security Rules definition changes:

    • old:
    • new:

WEB-INF/faces-config.xml

  • Upgrade the document version:

    • old:
    • new:
  • Remove "view-handler" setting

WEB-INF/jboss-web.xml

  • remove the "class-loading" setting

WEB-INF/web.xml

Replace the Richfaces 3 context params with Richfaces 4.2 context params:

<!--  RF 4.2 -->
<context-param> 
    <param-name>javax.faces.STATE_SAVING_METHOD</param-name> 
    <param-value>server</param-value> 
</context-param> 
<context-param> 
    <param-name>javax.faces.PROJECT_STAGE</param-name> 
    <param-value>Development</param-value> 
</context-param> 
<context-param> 
    <param-name>javax.faces.FACELETS_REFRESH_PERIOD</param-name> 
    <param-value>1</param-value> 
</context-param> 
<context-param> 
    <param-name>javax.faces.FACELETS_SKIP_COMMENTS</param-name> 
    <param-value>true</param-value> 
</context-param> 
<context-param> 
    <param-name>javax.faces.SEPARATOR_CHAR</param-name> 
    <param-value>-</param-value> 
</context-param> 
<context-param> 
    <param-name>org.richfaces.skin</param-name> 
    <param-value>deepMarine</param-value> 
</context-param> 
<context-param> 
    <param-name>facelets.BUILD_BEFORE_RESTORE</param-name> 
    <param-value>true</param-value> 
</context-param> 
<context-param> 
    <param-name>facelets.RECREATE_VALUE_EXPRESSION_ON_BUILD_BEFORE_RESTORE</param-name> 
    <param-value>true</param-value> 
</context-param>

<!-- Primefaces 3.2 -->
<context-param>
    <param-name>primefaces.THEME</param-name>
    <param-value>bluesky</param-value> <!-- sunny | glass-x | bluesky -->
</context-param>

META-INF/application.xml

  • old:

    jboss-seam.jar
    jboss-el.jar
    lib/commons-discovery-0.2.jar

  • new:

    jboss-seam.jar

META-INF/persistence.xml:

  • update the JNDI namings
  • comment out the listed classes (include and exclude)

Add jboss-deployment-structure.xml to META-INF

<jboss-deployment-structure xmlns="urn:jboss:deployment-structure:1.0">
    <ear-subdeployments-isolated>true</ear-subdeployments-isolated>
    <deployment>
        <exclusions>
            <!-- Exclude container version of hibernate. By default its version 4, we want 
                 bundled version 3 loaded -->
            <module name="org.hibernate" slot="main"/>
            <module name="org.hibernate.validator" slot="main"/>          
        </exclusions>
        <dependencies>
            <module name="org.apache.log4j" export="true"/>
            <module name="org.dom4j" export="true"/>
            <module name="org.apache.commons.logging" export="true"/>
            <module name="org.apache.commons.collections" export="true"/>
            <module name="javax.faces.api" slot="main" export="true"/>
            <module name="com.sun.jsf-impl" slot="main" export="true"/>
        </dependencies>
    </deployment>
    <sub-deployment name="partmatch.war"> 
        <exclusions>
            <module name="javax.faces.api" slot="1.2"/>
            <module name="com.sun.jsf-impl" slot="1.2"/>
        </exclusions>
        <dependencies>
            <module name="javax.faces.api" slot="main"/>
            <module name="com.sun.jsf-impl" slot="main"/>
        </dependencies>
    </sub-deployment> 
 </jboss-deployment-structure>  

resources/security.drl:

  • old:

    import org.jboss.seam.security.PermissionCheck; import org.jboss.seam.security.Role;

  • new:

    import org.jboss.seam.security.permission.PermissionCheck; import org.jboss.seam.security.Role;

standalone.xml

Add system properties to standalone.xml (or domain.xml) after the section extensions:

<system-properties>
    <property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
    <property name="config.path" value="\\configuration\\"/>
</system-properties>

Java

  • For all classes that extends from EntityQuery:

    • Remove or Comment out this method: @Override public List getRestrictions() { return Arrays.asList(RESTRICTIONS); }
    • Add contructor: public UploadFileList() {
      super(); setRestrictionExpressionStrings(Arrays.asList(RESTRICTIONS)); }
  • Hibernate 4.x has removed the following two validators: @Length and @NotNull. This has impact on all Entities that were generated by hbm2java.

    • Remove the import statements: import org.hibernate.validator.Length; import org.hibernate.validator.NotNull;
    • Remove all @NotNull and @Length annoations
  • UserTransaction exception during launch for asynchronized processes. Need to override with following class:

    package com.acme.util;

    import static org.jboss.seam.annotations.Install.*;

    import javax.naming.InitialContext; import javax.naming.NamingException; import javax.transaction.UserTransaction;

    import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Install; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.intercept.BypassInterceptors; import org.jboss.seam.transaction.Transaction; import org.jboss.seam.util.Naming;

    @Name("org.jboss.seam.transaction.transaction") @Scope(ScopeType.EVENT) @Install(precedence = APPLICATION) // overrides Seam default component of BUILT_IN @BypassInterceptors public class TransactionAS7 extends Transaction {

      @Override
      protected UserTransaction getUserTransaction() throws NamingException {
          final InitialContext context = Naming.getInitialContext();
    
          try {
            return (UserTransaction) context.lookup("java:comp/UserTransaction");
          } catch (final NamingException ne) {
              try {
                  // JBoss AS7 (with patch from https://issues.jboss.org/browse/AS7-1358)
                  return (UserTransaction) context.lookup("java:jboss/UserTransaction");
              } catch (final Exception cause) {
                  // ignore this so we let the code carry on to try the final JNDI name
              }
    
              try {
                  // Embedded JBoss has no java:comp/UserTransaction
                  final UserTransaction ut = (UserTransaction) context.lookup("UserTransaction");
                  ut.getStatus(); // for glassfish, which can return an unusable UT
                  return ut;
              } catch (final Exception e) {
                  throw ne;
              }
          }
      }
    

    }

  1. Accessing external file, such as acme.properties, is changed to use system property config.path value defined in standalone.xml:

    /**
     * For AS7
     */
    public static String getProperty(String key) {
            if (props == null) {
                    log.debug("---first time reading property file #0", propFileName);
                    props = new Properties();
                    try {
                            // do not use "jboss.server.config.dir" since the path seperators for Windows and Unix are diff
                            // Windows, config.path: "\\configuration\\"
                            // Windows: C:\jboss-EAP6\jboss-eap-6.0\standalone\configuration\partmatch.properties
                            // Unix, config.path: "/configuration/"
                            // Unix: //$JB7/domain/configuration/partmatch.properties
                            String path = System.getProperty("jboss.server.base.dir") + System.getProperty("config.path") + propFileName;
                            props.load(new  FileInputStream(path));
                    } catch (MalformedURLException e) {
                            log.error("*** property #0 is not defined due to #1, return null value", key, e.getMessage());
                            return null;
                    } catch (IOException ie) {
                            log.error("*** property #0 is not defined due to #1, return null value", key, ie.getMessage());
                            return null;
                    }
            }
    
            return props.getProperty(key);
    }
    

Richfaces

Please see the references in Appendix B for more details.

The following list indicates the substitutions you should make:

  • rich:simpleTogglePanel => rich:togglePanel
  • rich:toolTip => rich:tooltip
  • rich:tooltip: remove top-right director attribute
  • head => h:head
  • a4j:form => h:form
  • rich:spacer => p:spacer
  • rich:modalPanel => rich:popupPanel, add modal="true"
  • rich:suggestionbox => rich:autocomplete
  • <a4j:support> change to <a4j:ajax> and change these attributes
    • action => listener
    • reRender => render
    • event="on***" => `event="***",
    • remove ajaxSingle="true".
  • <a4j:include> => <ui:include>
  • <rich:tabPanel> - switchType="server" => switchType="client"
  • <rich:tooltip> does not work in h:outputText so we change <h:outputText> to <h:outputLabel>
  • we use bluesky css
    • web.xml: <param*value>bluesky</param-value>
    • build.xml: <include name="lib/primefaces*bluesky-1.0.4.jar" />
  • <rich:datascroller> => <rich:dataScroller>
  • <rich:modalPanel> => <p:dialog>
  • <body> => <h:body>
  • <rich:autocomplete> => <p:autoComplete>
  • PrimeFaces:'update' attribute does not work if component in the other form uses <a4j:jsFunction> instead of 'update' attribute
  • <rich:simpleTogglePanel> => <rich:collapsiblePanel>
  • <rich:tabPanel> => <p:tabView>
    • remove switchType="client"
    • remove height
  • <rich:tab> change to <p:tab>
    • label => title
    • styleClass => titleStyleClass
  • <a4j:commandButton> => <p:commandButton>
    • render => update
    • add id="***" (id is the form id)
    • add `partialSubmit="true"
    • add process="***"
    • add onclick="**Dialog.hide()"
  • <s:button> => <p:button>
    • view => href
  • <rich:dataTable> => <p:dataTable>
    • <h:column> => <p:column>
  • <rich:panel> => <p:panel>
  • <input type="hidden"> => <h:inputHidden>
  • <a4j:commandLink> => <p:commandLink>
    • action => actionListener
    • reRender => update

From our developers' experience, Richfaces 4.2.X has lots of minor glitches that make the component behave wierd. Some make them have no choice but switch to PrimeFaces components.

Build

The build process is changed for following reasons:

  • Library changed
  • JBoss AS7 is modularized

Database

There are no Database change for the migration.

Testing

Applications need to be tested thoroughly due to the migration has impacts on every corner. Due to the large QA effort, however, we can focus on following scenarios:

  • UI: pages need to be loaded successfully
  • Search: search conditions and result need to be correct
  • Transaction: data should be saved/updated/deleted correctly

One easy way to help testing is to setup two sets of environments, one for pre-migrated version, one for migrated version. Then QA can compare the result easily.

References

  1. RichFaces 3 to 4
  2. RichFaces 3 to 4
  3. JBoss AS 5 to 7
  4. Seam 2 to 3