Skip to content

Instantly share code, notes, and snippets.

@moose56
Last active June 23, 2024 15:21
Show Gist options
  • Save moose56/96bafb4c8b9f0c9c596d67e25ff324d3 to your computer and use it in GitHub Desktop.
Save moose56/96bafb4c8b9f0c9c596d67e25ff324d3 to your computer and use it in GitHub Desktop.
JSF 1.2 on WebLogic to JSF 2.3 on Tomcat notes

JSF 1.2 on WebLogic to JSF 2.3 on Tomcat

Some notes on the process of making this change to an application.

In addition to this other changes that have been included are:

  • Java 6/7 to Java 12
  • Usage of HTML5
  • Removal of the Trinidad component library
  • Use of Maven 3.*
  • Move from JDeveloper to Eclipse or IntelliJ IDEA

Datasource

A fundamental change is required in order to get a connection to a database. In order to get it to work with Tomcat some changes needed to be made to the good old bham.utils.backing.DBUtils.

First configure Tomcat by adding the Oracle JDBC driver to the Tomcat libs folder. Then add the following Resource and ResourceLink elements:

<!-- server.xml -->
<GlobalNamingResources>
   
    ...

    <Resource 
      accessToUnderlyingConnectionAllowed="true" 
      auth="Container" 
      driverClassName="oracle.jdbc.OracleDriver" 
      initialSize="25" 
      maxIdle="-1" 
      maxTotal="400" 
      maxWaitMillis="30" 
      name="jdbc/myapp" 
      password="..." 
      testOnBorrow="true" 
      type="javax.sql.DataSource" 
      url="..." 
      username="..." 
      validationQuery="select 1 from dual"/>
</GlobalNamingResources>
  
<!-- context.xml -->
<Context>

    ...
    
    <ResourceLink
      name="jdbc/myapp"
      global="jdbc/myapp"
      type="javax.sql.DataSource" />
</Context>

This will make the JNDI datasource available to any application running on the Tomcat instance.

We need to create a new class to load the JNDI datasource from Tomcat. At the same time it would also be useful to be able to create a datasource without a container like Tomcat when running tests with jUnit.

First things are basic interfaces to provide a datasource and connection objects:

import javax.sql.DataSource;

public interface DatabaseDataSource
{
    DataSource getDataSource();
}
import java.sql.Connection;
import java.sql.SQLException;

public interface ConnectionFactory
{
    Connection getConnection() throws SQLException;
}

We can then have multiple implementations of the datasource one for running in a container or running in jUnit:

// JndiDataSource is used when running in Tomcat
import lombok.extern.slf4j.Slf4j;

import javax.enterprise.inject.Default;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

@Slf4j
@Default
public class JndiDataSource implements DatabaseDataSource
{
    private final String CONTEXT = "java:/comp/env";
    private final String DATA_SOURCE = "jdbc/myapp";
    private DataSource dataSource;

    @Override
    public DataSource getDataSource()
    {
        if (this.dataSource == null)
        {
            try
            {
                logger.debug("Get context");
                Context ctx = new InitialContext();
                Context envContext = (Context) ctx.lookup(this.CONTEXT);
                this.dataSource = (DataSource) envContext.lookup(this.DATA_SOURCE);
            }
            catch (NamingException e)
            {
                e.printStackTrace();
            }
        }

        return this.dataSource;
    }
}
// JdbcDataSource is used when running in jUnit

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Properties;

@ApplicationScoped
@Alternative
public class JdbcDataSource implements DatabaseDataSource
{
    private OracleDataSource dataSource;

    public DataSource getDataSource()
    {
        try
        {
            ClassLoader classLoader = ClassLoader.getSystemClassLoader();

            var databaseProperties = new File(
                    classLoader.getResource("conf/database.properties").getFile()
            );

            Properties p = new Properties();
            p.load(new FileReader(databaseProperties));

            this.dataSource = new OracleDataSource();
            dataSource.setURL(p.getProperty("url"));
            dataSource.setUser(p.getProperty("username"));
            dataSource.setPassword(p.getProperty("password"));
        }
        catch (SQLException | IOException e)
        {
            e.printStackTrace();
        }

        return dataSource;
    }
}

In order for the application to create connections to the datasource to execute queries we have a DatabaseConnectionFactory object. This is a singleton that can be injected into any class that requires a database connection.

import lombok.NoArgsConstructor;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

@NoArgsConstructor // applicationScope bean needs this to allow for proxy object creation
@ApplicationScoped
public class DatabaseConnectionFactory implements ConnectionFactory
{
    private DataSource dataSource;

    @Inject
    public DatabaseConnectionFactory(DatabaseDataSource databaseDataSource)
    {
        this.dataSource = databaseDataSource.getDataSource();
    }

    @Override
    public Connection getConnection() throws SQLException
    {
        DataSource dataSource = this.dataSource;
        Connection conn = dataSource.getConnection();
        conn.setAutoCommit(false);

        return conn;
    }
}

WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="4.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd">
    <display-name>Admissions PTB</display-name>

    <servlet>
        <servlet-name>faces</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>faces</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>

    <context-param>
        <param-name>facelets.SKIP_COMMENTS</param-name>
        <param-value>true</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>org.jboss.weld.development</param-name>
        <param-value>true</param-value>
    </context-param>
</web-app>

WEB-INF/faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                                  http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_3.xsd"
              version="2.3">
</faces-config>

WEB-INF/beans.xml

This file is needed to trigger scanning of the project for beans.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2.xsd"
       bean-discovery-mode="all">
</beans>

CDI

Rather than specificying beans in the faces-config.xml or beans.xml files we specify them via attributes.

For example:

@Named("backing_search")
@SessionScoped
public class Search extends BasePage
{
	...
}	

Will create a session scoped bean called backing_search.

This is a massive change. Basically any class used in the application will need to be updated to work this way.

Resources

CSS and JavaScript files need to be in the 'resources' folder of the webapp directory. JSF will only look in here to resolve any page links.

jQuery UI

To get jQuery UI images to load you need to edit the CSS file:

/* before */
.ui-icon{background-image:url("/images/ui-icons_444444_256x240.png")} 

/* after */
.ui-icon{background-image:url(#{resource["css/images/ui-icons_444444_256x240.png"]})} 

File names

Web pages now have .xhtml extensions instead of .jspx

Page structure

<!DOCTYPE html>
<html lang="en"
      xmlns="http://www.w3.org/1999/xhtml"
      xmlns:f="http://xmlns.jcp.org/jsf/core"
      xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
    <f:view encoding="UTF-8" contentType="text/html">
        <h:head id="head">
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

            <h:outputStylesheet name="css/..."/>

            <title></title>

            <f:attribute name="pageId" value="..."/>
            <f:attribute name="pageName" value="..."/>
        </h:head>
        <h:body>
            <p>Page content</p>
            <!-- include standard js -->
            <h:outputScript name="js/..."/>
        </h:body>
   </f:view>
</html>

Enable JSF 2.3

Details for how to configure this are here.

Basically, add a class to the application like this:

import javax.enterprise.context.ApplicationScoped;
import javax.faces.annotation.FacesConfig;

@FacesConfig
@ApplicationScoped
public class Jsf23Activator {}

Expression language

In JSF 1.2 you could have JSP and JSF expressions:

<!-- JSF 1.2 -->
<title>#{backing_search.shortMnemonic}</title>
<title>${backing_search.shortMnemonic}</title>

<!-- JSF 2.3 -->
<title>#{backing_search.shortMnemonic}</title>

In JSF 2.3 you can only have JSF epressions #{...}

Page to page navigation

// JSF 1.2
public String search_action() {
    ...
    
    return "search";
}
<!-- JSF 1.2 faces-config.xml -->
<navigation-rule>
	<from-view-id>/details.jspx</from-view-id>
    <navigation-case>
      	<from-outcome>search</from-outcome>
      	<to-view-id>/search.jspx</to-view-id>
    	<redirect/>
    </navigation-case>
</navigation-rule>

In JSF 2.3 the <navigation-rule> is no longer needed. It will simply look for a page with the name that is returned:

// JSF 2.3
public String search_action() {
    ...
    
    return "search"; // will load search.xhtml
}

By default this will not update the browser URL. It would display the same page path as the original page. To navigate to a new page and have the browser URL update like a normal GET request do the following:

<!-- JSF 2.3 -->
public String search_action() {
    ...
    
    return "search?faces-redirect=true"; // will load search.xhtml and update the 
                                         // browser address bar to search.xhmtl without 
                                         // the `faces-redirect=true` bit
}

JSF tags

Tag libraries

<!-- JSF 1.2 with Trinidad -->
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:c="http://java.sun.com/jsp/jstl/core"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:trh="http://myfaces.apache.org/trinidad/html"
xmlns:tr="http://myfaces.apache.org/trinidad"

<!-- JSF 2.3 -->
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:ui="http://xmlns.jcp.org/jsf/facelets"

Link and Script

CSS Links:

<!-- JSF 1.2 -->
<link type="text/css" rel="stylesheet" href="css/..."/>

<!-- JSF 2.3 -->
<h:outputStylesheet name="css/..."/>

Script Links:

<!-- JSF 1.2 -->
<script type="text/javascript" language="JavaScript" src="js/...s"></script>

<!-- JSF 2.3 -->
<h:outputScript name="js/..."/>

These elements can now also be included in ui:composition files and will be included in the calling page. You can specify a target parameter to say where in the calling page they appear:

<!-- JSF 2.3 -->
<h:outputStylesheet name="css/..." target="head"/>

<h:outputScript name="js/..." target="body" />

This will insert the CSS link in the calling page head element and the script tag at the end of the calling page body element.

The links are added in the order the ui:include elements appear in the calling page.

tr:spacer

<!-- JSF 1.2 with Trinidad -->
<tr:spacer width="10" height="1"/>

<!-- JSF 2.3 -->
<h:outputText value=" "/>

tr:outputText

<!-- JSF 1.2 with Trinidad -->
<tr:outputText value="Some value" styleClass="CSS classes"/>

<!-- JSF 2.3 -->
<h:outputText value="Some value" styleClass="CSS classes"/>

tr:messages

There is no direct replacement for the Trinidad messages tag so we need to make something simillar:

<!-- JSF 1.2 -->
<tr:panelBorderLayout styleClass="error">
	<tr:messages globalOnly="true" text="Errors/Information:"/>
</tr:panelBorderLayout>

<!-- JSF 2.3 -->
<c:if test="#{not empty facesContext.getMessageList()}">
	<div class="header-messages">
		<h2>Errors/Information:</h2>
		<h:messages layout="list" globalOnly="true" />
	</div>
</c:if>

As we are making our own version of the Trinidad one we also need some CSS:

.header-messages {
    border: solid 1px #999999;
    color: #669966;
    background: #f3f3f3;
    margin-top: 5px;
}

.header-messages h2 {
    border-bottom: solid 1px #cecece;
    padding: 0 0 0 5px;
    margin: 0;
    font-size: 13pt;
}

.header-messages ul {
    margin: 0;
    padding: 5px 30px;
}

.header-messages li {
    list-style-type: none;
}

c:if

<!-- JSF 1.2 -->
<c:if test="${...}">
</c:if>

<!-- JSF 2.3 -->
<c:if test="#{...}">
    ...
</c:if>

f:subview & jsp:include

<!-- JSF 1.2 -->
<f:subview id="headerm">
	<jsp:include page="header.jspx" flush="true"/>
</f:subview>

<!-- JSF 2.3 -->
<ui:include src="header.xhtml"/>

The filea you are including are defined as ui:composition documents:

<ui:composition
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"
        xmlns:h="http://xmlns.jcp.org/jsf/html"
        xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
	
	...
</ui:composition>

tr:inputHidden

<!-- JSF 1.2 with Trinidad -->
<tr:inputHidden value="#{...}" id="..."/>

<!-- JSF 2.3 -->
<h:inputHidden value="#{...}" id="..."/>

tr:commandButton

There are a number of changes to how this element behaves.

<!-- JSF 1.2 with Trinidad -->
<tr:commandButton 
    text="View Selected"
    styleClass="ui-button ui-corner-all ui-widget"
    action="#{...}"
    id="..." />

<!-- output -->
<button id="..." type="button" class="ui-button ui-corner-all ui-widget">View Selected</button>

<!-- JSF 2.3 -->
<h:commandButton
    value="View Selected"
    styleClass="ui-button ui-corner-all ui-widget"
    action="#{...}"
    id="..."/>

<!-- output -->
<input id="..." type="submit" value="View Selected" class="ui-button ui-corner-all ui-widget"/>

Because this is now rendered as an input element CSS styles for button elements may need to be revisited.

tr:forEach

<!-- JSF 1.2 with Trinidad -->
<tr:forEach var="item" items="#{list}">
</tr:forEach>

<!-- JSF 2.3 -->
<c:forEach var="item" items="#{list}">
</c:forEach>

c:out

<!-- JSF 1.2 with Trinidad -->
<td>
    <c:out value="${activity.year}"/>
</td>

<!-- JSF 2.3 -->
<td>
    #{activity.year}
</td>

partialPageRefresh

JSF 1.2 Page markup:

<!-- JSF 1.2 with Trinidad -->
<tr:panelGroupLayout id="ptbReviewLayout" partialTriggers="save">
    ...
</tr:panelGroupLayout>

<tr:commandButton text="Save"
                  action="#{backing.save_action}"
                  partialSubmit="true" 
                  id="save" />

JSF 1.2 JavaScript events:

// JSF 1.2 with Trinidad
TrPage.getInstance().getRequestQueue().addStateChangeListener(function(state) {
    ...
});

TrPage.getInstance().addDomReplaceListener(function(oldDom, newDom) {
    ...
});

JSF 2.3 page markup:

<!-- JSF 2.3 -->
<h:panelGroup id="save">
    ...
</h:panelGroup>

<h:commandButton 
                 value="Save" 
                 action="#{backing.save_action}">
    <f:ajax execute="@form" render="save"/>
</h:commandButton>

JSF 2.3 JavaScript event:

// JSF 2.3
jsf.ajax.addOnEvent(function(event){
    ...
});

Passing parameters to actions

<!-- JSF 1.2 with Trinidad -->
<tr:commandButton 
              text="View Document" 
              action="#{backing.view_document_action}">
              <tr:setActionListener from="#{item.guid}" to="#{backing.selectedDocumentID}"/>
</tr:commandButton>

<!-- JSF 2.3 -->
<h:commandButton
        value="View Document"
        action="#{backing.view_document_action(item.guid)}">
</h:commandButton>

Multi selects

Now we are not using Trinidad the following lines can be removed when creating a client side multi select:

selectList.select2({
    multiple: true,
    placeholder: placeholder,
    allowClear: true,
    closeOnSelect: false
}).on('change', {
    selectList: selectList,
    valholder: valholder
}, this.getSelected);
// NO LONGER NEEDED! if left in will remove the first item in the list!
// .find("option") 
// .eq(0)
// .remove();

Extras

Configuration

A configuration object that is responsible for loading settings from various sources and then can be injected into other objects when needed is added, with a sub object for development specific config:

import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
@Data
@Slf4j
public class AppConfig
{
    private final Config conf;
    private final String environment;
    private final DevelopmentConfig development;

    ...
}


import lombok.Value;

@Value
public class DevelopmentConfig
{
    private String userId;
}

Authentication filters

In Servlets 3 Servlet Filters can be loaded dynamically at run time. This allows for different authentication process based on enviroment. In development we want to load a user ID based on an enviroment varialble. In test and production we want the normal token based authentication.

To set the filter at runtime we use a @WebListner:

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.annotation.WebListener;
import java.util.EnumSet;

@WebListener
@Slf4j
public class FilterStartupListener implements ServletContextListener
{
    @Inject
    private AppConfig config;
    
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent)
    {
        ServletContext ctx = servletContextEvent.getServletContext();

        if (config.getEnv().equals("development"))
        {
            FilterRegistration fr = ctx.addFilter("DevelopmentAuthenticationFilter", new DevelopmentAuthenticationFilter(config));
            fr.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), true, "/*");
        } else
        {
            FilterRegistration fr = ctx.addFilter("AuthenticationFilter", AuthenticationFilter.class);
            fr.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD), true, "/*");
        }
    }
}

The standard authentication filter is AuthenticationFilter:

import lombok.extern.slf4j.Slf4j;
import uk.ac.bham.portal.utils.Encrypter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.GregorianCalendar;

@Slf4j
public class AuthenticationFilter implements Filter
{
    private static final String[] ignoredURLs = { "/error" };
    
    private static int TIMEDELAY = 1;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
    	...
    }
}

And the development authentication filter is DevelopmentAuthenticationFilter:

import com.typesafe.config.Config;
import com.typesafe.config.ConfigException;
import com.typesafe.config.ConfigFactory;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@Slf4j
public class DevelopmentAuthenticationFilter implements Filter
{
    private static final String[] ignoredURLs = { "/error" };
    private AppConfig config;

    public DevelopmentAuthenticationFilter(AppConfig config)
    {
        this.config = config;
    }
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
        ...
        
        String userId = this.config.getUserId();
        
        ...
    }
}

Cache busting

Adding a hash or version number to the path of CSS or JavaScript files is something that web frameworks support. JSF works a bit differently as this is ability is not covered by the spec specifically and the expectation is you will version the entire resources folder. This is not practical so as a work around we append the application start time to the resource’s files. This means users should (it will not always work) get the latest copy of the application resources whenever it restarts.

First create an App class:

import lombok.Value;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import java.util.Date;

@Named("app")
@ApplicationScoped
@Value
public class App
{
    private Date startup;

    public App()
    {
        this.startup = new Date();
    }
}

This can then be referenced in pages:

<h:outputScript name="js/jquery.js?v=#{app.startup.time}"/>

Lombok

Lombok is a useful Java library that saves you from writing a lot of boiler plate code. Some examples of frequently used annotations are:

  • @Data - Classes annotated with @Data automatically get all of their getters and setters created automatically as well as toString, equals, hash and a required args constructor.
  • @Slf4j - Classes annotated with @Slf4j automatically get static Log class property.

lombok.config

This file lives in the root of the project and allows you to set options for how lombok works. E.g:

lombok.log.fieldName=logger

will set the name of the log class property to logger instead of log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment