Friday Nov 16

Writing Variable Resolver using JSF 2

Sébastien Dante Ursini
PDF Print E-mail
Monday, 27 June 2011 07:21
Written by Sébastien Dante Ursini
AddThis Social Bookmark Button

You probably know that the API variable-resolver has been deprecated after JSF 1.1. JSF 2 use a more generic class called ELResolver

In the following tutorial we will illustrate the creation of a resolver (ex-varaibleresolver) to access the pom.properties located in you webapp. To have this file packaged in your webapp you need to package you application with Maven.

The pom.properties and pom.xml files are both packaged in the following folder :

|-- META-INF
|   |-- MANIFEST.MF
|   |-- application.properties
|   `-- maven
|       `-- com.mycompany.app
|           `-- my-app
|               |-- pom.properties
|               `-- pom.xml

The content of your pom.properties should look like :

#Generated by Mavon
#Mon Jun 27 17:36:23 CEST 2011
version=1.1-SNAPSHOT
groupId=com.mycompany.app
artifactId=my-app

The objective of this JSF resolver tutorial is to give a way to access those three properties using the unified expression language .

#{maven.pom['groupId']}
#{maven.pom['artifactId']}
#{maven.pom['version']}

To achieve this objective we will :

  1. Develop a MavenVariableResolver extending the JSF 2 ELResolver class
  2. Configureour webapp to use this variable resolver
  3. Write a page using the three expressions

Writing our Maven Variable Resolver

In the below source i'm creating an ELResolver called MavenVariableResolver. The POM_FILE field define the location of the Maven pom.properties file in the Web Application. MAVEN and POM define the variable name we support in the resolver.

RESOLVABLE_DESIGN_TIME is self explaining. The pomMap field will contain the Map containing the properties present in the pom.properties file.

The Maven inner class is the "fake" class returned if you call only the maven properties (eg: #{maven})

ELResolver implementation source

public class MavenVariableResolver extends ELResolver {
    private static final Logger logger = Logger.getLogger(MavenVariableResolver.class.getName());

    private final String     POM_FILE     = "/META-INF/maven/com.mycompany.app/my-app/pom.properties";
    private final String     MAVEN      = "maven";
    private final String     POM         = "pom";
    private final boolean     RESOLVABLE_DESIGN_TIME = Boolean.FALSE;
    
    private Map<String,String> pomMap = null; 
    
        .....
            
    public class Maven implements Serializable {}
}
}

The abstract class ELResolver requires the following methods to be implemented :

  • public abstract Iterator getFeatureDescriptors( ELContext context, Object base);
  • public abstract Class<?> getType(ELContext context, Object base, Object property);
  • public abstract Object getValue(ELContext context, Object base, Object property);
  • public abstract void setValue(ELContext context, Object base, Object property, Object value);
  • public abstract boolean isReadOnly(ELContext context, Object base, Object property);
  • public abstract Class<?> getCommonPropertyType(ELContext context, Object base);

In the following chapters i will explain and provide the implementation for each method.

EL Resolver Feature descriptor method implementation

The getFeatureDescriptors method returns information about the set of variables that can be resolved for the given base object.The Iterator returned must contain two instances of java.beans.FeatureDescriptor One for the maven keyword and the other for the pom keyword.

ELResolver implementation source

@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,Object base) {
    logger.fine("Registering features... (base : " + base + ")");
    if (base != null) return null;
    ArrayList<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>(14);
    list.add(Util.getFeatureDescriptor(
       "maven", 
       "maven",
       "maven",
       false,//EXPERT, 
       false,//HIDDEN,
       true,//PREFERRED, 
       Object.class, 
       RESOLVABLE_DESIGN_TIME)
    );
    list.add(Util.getFeatureDescriptor(
        "pom", 
        "pom",
        "pom",
        false,//EXPERT, 
        false,//HIDDEN, 
        true,//PREFERRED, 
        Map.class, 
        RESOLVABLE_DESIGN_TIME)
    );
    return list.iterator();
    }
}

Return the corresponding variables types

For a given base and property th getType method attempts to identify the most general type that is acceptable for an object to be passed as the value parameter in a future call to the setValue method. This implementation is mandatory but not very usefull in our case because our properties are read only. Anyway we will provide and implementation

EL Resolver getType implementation

@Override
    public Class<?> getType(ELContext context, Object base, Object property) {
        logger.fine("Get type for"+ "property" + property+"  base " + base);
        if (base != null) {
            if (base instanceof Maven && property == null){
                context.setPropertyResolved(true);
                logger.fine("\return Maven class type...");
                return Maven.class;
            }
        }
        if (property == null) {
            String message = MessageUtils.getExceptionMessageString
                (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "property");
            throw new PropertyNotFoundException(message);
        }
        if (MAVEN.equalsIgnoreCase(property.toString())) {
            context.setPropertyResolved(true);
            logger.fine("\treturn type :  Maven.class"  );
            return Maven.class;
        }
        if (POM.equalsIgnoreCase(property.toString())) {
            context.setPropertyResolved(true);
            logger.fine("\treturn type :  Map.class"  );
            return Map.class;
        }
        return null;
    }

IsReadOnly implementation

For a given base and property ,isReadOnly method attempts to determine whether a call to setValue will always fail.
If this resolver handles the given (base, property) pair, the propertyResolved property of the ELContext object must be set to true by the resolver, before returning. If this property is not true after this method is called, the caller should ignore the return value.

In our case both variables should be considered read only.

ELResolver isReadOnly implementation

@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
 logger.fine("\treturn type : Map.class" );
 if (MAVEN.equals(property) || POM.equals(property)) {
 return true;
 }
 return true;
}

Return the common property type

Returns the most general type that this resolver accepts for the property argument, given a base object. One use for this method is to assist tools in auto-completion.

ELResolver common property type

@Override
public Class<?> getCommonPropertyType(ELContext context, Object base) {
 if (base != null) return null;
 return Object.class;
}

Set value implementation

Attempts to set the value of the given property object on the given base object. Our implementation should throw a PropertyNotWritableException when someone try to set the maven of pom properties.

ELREsolver setValue method implementation

@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
    if (base != null) return;
    if (property == null) {
        String message = MessageUtils.getExceptionMessageString
             (MessageUtils.NULL_PARAMETERS_ERROR_MESSAGE_ID, "property");
        throw new PropertyNotFoundException(message);
    }
    if (MAVEN.equals(property) || POM.equals(property)) {
        throw new PropertyNotWritableException((String) property);
    }
}

Get value implementation

getValue() method attempts to resolve the given property object on the given base object.

If this resolver handles the given (base, property) pair, the propertyResolved property of the ELContext object must be set to true by the resolver, before returning. If this property is not true after this method is called, the caller should ignore the return value.

ELResolver getValue implementation

@Override
public Object getValue(ELContext context, Object base, Object property) {
    logger.fine("get Value property : "+ property);
    if (MAVEN.equalsIgnoreCase(property.toString())){
        logger.fine("\tReturn maven object");
        context.setPropertyResolved(true);
        return new Maven();
    }
    if ("pom".equalsIgnoreCase(property.toString())){
        logger.fine("return pom result ");
        context.setPropertyResolved(true);
        return getMavemPomProperties();
    }
    else{
        logger.severe("Property not supported : " + property);
    }
    return null;
}

Loading the properties used by the getValue method

To provide the values requrested by the getValue() method we need to read them from the properties. This is the responsability of the method below.

Read pom.properties file

private Map<String,String> getMavemPomProperties(){
        if (pomMap == null){
            logger.info("Read maven POM properties...");
            pomMap = new HashMap<String,String>();
            ServletContext servletContext = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
            InputStream pom_properties = servletContext.getResourceAsStream(POM_FILE);
            try {
                Properties properties = new Properties();
                properties.load(pom_properties);
                for (String key : properties.stringPropertyNames()) {
                    pomMap.put(key, properties.getProperty(key));
                }
            }
            catch(IOException ex) {
                logger.severe("Error while reading pom properties : " + ex.getMessage());
            }
        }
        return pomMap;
    }

Registering the ELResolver in JSF configuration

To make JSF aware of our ELREsolver we need now to configure the JSF faces-config.xml file.

Include the following snippet in the <build><plugins> section of your project's pom.xml-file:

Faces-config configuration EL Resolver

<?xml version="1.0" encoding="UTF-8"?>
<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
    version="2.0">
    ...

    <application>
        <el-resolver>
            com.ubiteck.jsf.util.MavenVariableResolver
        </el-resolver>
    </application>
    ...
</faces-config>

Create a JSF page and display the result

Faces-config configuration EL Resolver

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
    xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ui="http://java.sun.com/jsf/facelets">
    <h3>Maven Project configuration</h3>
    <table>
        <tr>
            <td><h:outputText value="maven.pom['groupId']" /></td>
            <td><h:outputText value="#{maven.pom['groupId']}" /></td>
        </tr>
        <tr>

            <td><h:outputText value="maven.pom['artifactId']" /></td>
            <td><h:outputText value="#{maven.pom['artifactId']}" /></td>
        </tr>
        <tr>
            <td><h:outputText value="maven.pom['version']" /></td>
            <td><h:outputText value="#{maven.pom['version']}" /></td>
        </tr>
    </table>
</ui:composition>

The result should look like :

Maven Project configuration

maven.pom['groupId'] com.mycompany.app
maven.pom['artifactId'] my-app
maven.pom['version'] 1.1-SNAPSHOT

If you need more details about the execution feel free to configure your logging system or use the following tutorial to configuration the JDK logging system in a Tomcat server : JSF 2 Logging in Tomcat

Conclusion

The aim of this article has been to help new users overcome the initial hurdles necessary for creating an ELResolver. If you found in this article usefull informations or want to have more details or discover and error feel free to add your comment below


Complete source available at : Maven Variable Resolver source

Tags: maven , method , class , property , public , object , resolver , context , base
Sébastien Dante Ursini

Sébastien Dante Ursini

Java/Finance Specialist
15 Years of experience in Java
20 Year in Banking/Finance
Based in Geneva/Switzerland

Add comment


Security code
Refresh

Java Tutorial on Facebook