• About

N1nja Hacks

~ Random assortment of solutions, sneaky hacks and technical HOWTOs

N1nja Hacks

Category Archives: JSF

How to use JSF libraries without packaging them as JARs during development

25 Friday Sep 2015

Posted by valblant in JSF

≈ Leave a comment

Tags

JSF

Introduction

JSF spec allows us to place JSF configuration documents, such as faces-config.xml and *taglib.xml either inside WEB-INF/ of our WAR, or in META-INF/ of JARs included in WEB-INF/lib of our WAR. For JSF annotated classes, they can either be in WEB-INF/classes, or in the included JARs.

But what if we want all these things to work properly without having to package all our JSF dependency projects as jars? Naturally, we never want to deploy like that, but during development it would be really nice, b/c then we could actually make changes to any code inside our JSF dependencies with full hot-swap support, without having to package anything, or to restart the application server! Unfortunately, this is not possible with JSF out-of-the-box…

This article describes a technique I used to work around these limitations of JSF, thus gaining the ability to make direct modifications to my JSF libraries without restarting or repackaging, and achieving the state of coding zen :).

This solution was tested with Mojarra JavaServer Faces 2.1.7, and it is intended to work with Eclipse workspaces. There would probably be small differences in the implementation for other configurations, but the general approach should work everywhere.

Solution

We have 3 problems to solve:

1) Picking up JSF Annotated Classes from other JSF projects in the workspace

This turned out to be the hardest problem to solve.

Normally JSF annotated classes (such as @FacesComponent, @FacesConverter, @FacesRenderer, etc) must be inside a JAR, or in /WEB-INF/classes/. What we need is to pick up annotated classes from other Eclipse projects we depend on, which means that they need to be loaded from our Web Project’s classpath.

There is no way to extend JSF to do this, b/c everything inside AnnotationScanTask and ProvideMetadataToAnnotationScanTask is hard coded. In order to make the necessary changes, we’ll need some AspectJ magic.

The idea is to use Load Time Weaving to advise the call to JavaClassScanningAnnotationScanner.getAnnotatedClasses() and merge results from our own annotation scan with the results coming from the stock JSF implementation.

This can be achieved with a simple aspect, and some code to scan for annotated classes, which is the first part of our solution. I am using Google Reflections here to do the annotation scan inside the packages where I know my JSF libraries will be. Modify this for your own needs.

JsfConfigurationShimForEclipseProjectsAspect.aj:

/**
 * This is an AspectJ shim used to find more JSF annotated classes during the setup process. 
 * Normally, JSF configuration and JSF annotations are only processed on paths inside our own WAR, and from other jars.
 * However, in development mode we are interested in linking to DryDock dependencies as local Eclipse projects, rather than jars.
 * This shim provides a missing extension point, which scans the DryDock project classpath for JSF annotations.
 *

 * The other part of this solution is found in <code>EclipseProjectJsfResourceProvider</code>
 *

 * Since we are weaving JSF, Load Time Weaving is required, which means that this aspect must be declared in <code>META-INF/aop.xml</code>.
 * Also, Tomcat must be started with:
 *



<pre>
 *  -javaagent:/fullpath/aspectjweaver-version.jar -classpath /fullpath/aspectjrt-version.jar
 * </pre>

 *
 * @see EclipseProjectJsfResourceProvider
 *
 * @author Val Blant
 */
public aspect JsfConfigurationShimForEclipseProjectsAspect {

	pointcut sortedFacesDocumentsPointcut() : execution(* ConfigManager.sortDocuments(..));
	after() returning (DocumentInfo[] sortedFacesDocuments): sortedFacesDocumentsPointcut() {
		System.out.println("\n ====== Augmented list of JSF config files detected with JsfConfigurationShimForEclipseProjectsAspect ====== ");
		for ( DocumentInfo doc : sortedFacesDocuments ) {
			System.out.println(doc.getSourceURI().toString());
		}
		System.out.println("\n");
	}

	pointcut getAnnotatedClassesPointcut(Set<URI> urls) : execution(* JavaClassScanningAnnotationScanner.getAnnotatedClasses(Set<URI>)) && args(urls);
	Map<Class<? extends Annotation>, Set<Class<?>>> around(Set<URI> urls): getAnnotatedClassesPointcut(urls)  {

		Map<Class<? extends Annotation>, Set<Class<?>>> oldMap = proceed(urls);
		Map<Class<? extends Annotation>, Set<Class<?>>> newMap = EclipseJsfDryDockProjectAnnotationScanner.getAnnotatedClasses();
		Map<Class<? extends Annotation>, Set<Class<?>>> mergedMap = new AnnotatedJsfClassMerger().merge(oldMap, newMap);

		return mergedMap;

	}
}

EclipseJsfDryDockProjectAnnotationScanner.java:

/**
 * Scans DryDock project classpath to find any JSF annotated classes. This scanner is activated by 
 * the <code>JsfConfigurationShimForEclipseProjectsAspect</code>, which requires Load Time Weaving.
 *

 * This class should only be used in development! It is part of a solution that allows us to run the app
 * against locally imported DryDocked projects.
 *
 * @see JsfConfigurationShimForEclipseProjectsAspect
 * @see EclipseProjectJsfResourceProvider
 *
 * @author Val Blant
 */
public class EclipseJsfDryDockProjectAnnotationScanner extends AnnotationScanner {
	
	private static final Log log = LogFactory.getLog(EclipseJsfDryDockProjectAnnotationScanner.class);
	
	
	
	private static Reflections reflections = new Reflections( 
			new ConfigurationBuilder()
				.addUrls(ClasspathHelper.forPackage("ca.gc.agr.common.web.jsf"))
				.addUrls(ClasspathHelper.forPackage("ca.ibm.web"))
	);


	public EclipseJsfDryDockProjectAnnotationScanner(ServletContext sc) {
		super(sc);
	}
	
	
	public static Map<Class<? extends Annotation>, Set<Class<?>>> getAnnotatedClasses() {
		Map<Class<? extends Annotation>, Set<Class<?>>> annotatedClassMap = new HashMap<>();
		
		for ( Class<? extends Annotation> annotation : FACES_ANNOTATION_TYPE ) {
			Set<Class<?>> annotatedClasses = reflections.getTypesAnnotatedWith(annotation);
			
			if ( !annotatedClasses.isEmpty() ) {
				Set<Class<?>> classes = annotatedClassMap.get(annotation);
				if ( classes == null ) {
					classes = new HashSet<Class<?>>();
					annotatedClassMap.put(annotation, classes);
				}
				
				classes.addAll(annotatedClasses);
			}
		}
		
		log.info(" ====== Found additional JSF annotated classes from Eclipse classpath ====== \n" + annotatedClassMap);
		
		return annotatedClassMap;
	}

	@Override
	public Map<Class<? extends Annotation>, Set<Class<?>>> getAnnotatedClasses(Set<URI> urls) {
		return getAnnotatedClasses();
	}

}

AnnotatedJsfClassMerger.java:

/**
 * Merges 2 maps of JSF annotated classes into one map.
 * 
 * This class should only be used in development! It is part of a solution that allows us to run the app
 * against locally imported DryDocked projects.
 * 
 * @see JsfConfigurationShimForEclipseProjectsAspect
 * @see EclipseProjectJsfResourceProvider
 *
 * @author Val Blant
 */
public class AnnotatedJsfClassMerger {
	
	public Map<Class<? extends Annotation>, Set<Class<?>>> merge(
				Map<Class<? extends Annotation>, Set<Class<?>>> oldMap,
				Map<Class<? extends Annotation>, Set<Class<?>>> newMap) {
		
		
		Set<Class<? extends Annotation>> annotations = new HashSet<>();
		annotations.addAll(oldMap.keySet());
		annotations.addAll(newMap.keySet());
		
		Map<Class<? extends Annotation>, Set<Class<?>>> mergedMap = new HashMap<>();
		for ( Class<? extends Annotation> annotation : annotations ) {
			Set<Class<?>> classes = new HashSet<>();
			
			Set<Class<?>> oldClasses = oldMap.get(annotation);
			Set<Class<?>> newClasses = newMap.get(annotation);
			
			if ( oldClasses != null ) classes.addAll(oldClasses);
			if ( newClasses != null ) classes.addAll(newClasses);
			
			mergedMap.put(annotation, classes);
		}
		
		return mergedMap;
	}

}

Next, we need to properly set up the Load Time Weaver.

First we create src/main/resources/META-INF/aop.xml in our Web Project.

META-INF/aop.xml:

<!-- This file is read by AspectJ weaver java agent. Make sure you specify the following on server startup command line: -javaagent:/fullpath/AgriShare/aspectjweaver-version.jar -classpath /fullpath/AgriShare/aspectjrt-version.jar Also, make sure that you actually compile the aspects specified below. Eclipse can't do it! You'll have to use Gradle for that. -->

<aspectj>
 <aspects>
   <aspect name="ca.gc.pinss.web.jsf.drydock.eclipse.JsfConfigurationShimForEclipseProjectsAspect"/>
 </aspects>
 <weaver options="-verbose -showWeaveInfo -XnoInline">
 	<include within="com.sun.faces.config.*"/>
 </weaver>
</aspectj>

Now we need to make sure that we start our application with the AspectJ weaver.

  • Append the following to your Application Server’s startup JVM parameters:
-javaagent:/home/val/.gradle/caches/modules-2/files-2.1/org.aspectj/aspectjweaver/1.7.4/d9d511e417710492f78bb0fb291a629d56bf4216/aspectjweaver-1.7.4.jar

Note: Use the correct path for your machine!

  • Make sure that this jar is first on your Application Server’s classpath:
/home/val/.gradle/caches/modules-2/files-2.1/org.aspectj/aspectjrt/1.7.4/e49a5c0acee8fd66225dc1d031692d132323417f/aspectjrt-1.7.4.jar

Note: Use the correct path for your machine!

And that’s it – now your annotated JSF classes will be picked up directly from project classpath!

To make sure that it is working, look for messages from EclipseJsfDryDockProjectAnnotationScanner in the log. It will have the following heading:

 ====== Found additional JSF annotated classes from Eclipse classpath ======

You should also see some messages from the AspectJ weaver:

[WebappClassLoader@6426a58b] weaveinfo Join point 'method-execution(
com.sun.faces.config.DocumentInfo[] com.sun.faces.config.ConfigManager.sortDocuments(com.sun.faces.config.DocumentInfo[], com.sun.faces.config.FacesConfigInfo))'
in Type 'com.sun.faces.config.ConfigManager' (ConfigManager.java:503) 
advised by afterReturning advice from 'ca.gc.pinss.web.jsf.drydock.eclipse.JsfConfigurationShimForEclipseProjectsAspect' (JsfConfigurationShimForEclipseProjectsAspect.aj:36)
[WebappClassLoader@6426a58b] weaveinfo Join point 'method-execution(
java.util.Map com.sun.faces.config.JavaClassScanningAnnotationScanner.getAnnotatedClasses(java.util.Set))' 
 in Type 'com.sun.faces.config.JavaClassScanningAnnotationScanner' (JavaClassScanningAnnotationScanner.java:121) 
 advised by around advice from 
'ca.gc.pinss.web.jsf.drydock.eclipse.JsfConfigurationShimForEclipseProjectsAspect' (JsfConfigurationShimForEclipseProjectsAspect.aj:45)

2) Picking up Taglibs from other JSF Projects in the Workspace

This one is easy in comparison.

All we need to do here is to specify an additional custom FacesConfigResourceProvider.

EclipseProjectJsfResourceProvider.java:

/**
 * This custom resource provider is used for finding JSF Resources located in other Eclipse Projects, rather 
 * than jars. JSF spec does not support this, but it is very useful for running DryDocked projects inside the local Eclipse workspace.
 *

 * In order to enable this resource provider, this class's name must be specified in 
 * <code>META-INF/services/com.sun.faces.spi.FacesConfigResourceProvider</code>
 *

 * <b>NOTE:</b> The Gradle build will not include the com.sun.faces.spi.FacesConfigResourceProvider file, b/c we never want this 
 * customization to be deployed - it's for development only.
 * 
 * @see JsfConfigurationShimForEclipseProjectsAspect
 *
 * @author Val Blant
 */
public class EclipseProjectJsfResourceProvider implements FacesConfigResourceProvider {
	
	private static final Log log = LogFactory.getLog(EclipseProjectJsfResourceProvider.class);
	
	
	
	@Override
	public Collection<URI> getResources(ServletContext context) {
		
		List<URI> unsortedResourceList = new ArrayList<URI>();

        try {
            for (URI uri : loadURLs(context)) {
            	if ( !uri.toString().contains(".jar!/") ) {
                   unsortedResourceList.add(0, uri);
            	}
            }
        } catch (IOException e) {
            throw new FacesException(e);
        }

        List<URI> result = new ArrayList<>();
        
        // Then load the unsorted resources
        result.addAll(unsortedResourceList);
        
		log.info(" ====== Found additional JSF configuration resources on Eclipse classpath ====== \n" + result);

        return result;
	}
	
	
    private Collection<URI> loadURLs(ServletContext context) throws IOException {

        Set<URI> urls = new HashSet<URI>();
        try {

// Turns out these are already grabbed by MetaInfFacesConfigResourceProvider, so we don't need to do it again	
//            for (Enumeration<URL> e = Util.getCurrentLoader(this).getResources("META-INF/faces-config.xml"); e.hasMoreElements();) {
//                    urls.add(new URI(e.nextElement().toExternalForm()));
//            }
            URL[] urlArray = Classpath.search("META-INF/", ".taglib.xml");
            for (URL cur : urlArray) {
                urls.add(new URI(cur.toExternalForm()));
            }
        } catch (URISyntaxException ex) {
            throw new IOException(ex);
        }
        return urls;
        
    }
	

}

To register this provider, we add the following into our Web Project:

src/main/resources/META-INF/services/com.sun.faces.spi.FacesConfigResourceProvider:

ca.gc.agr.common.web.jsf.drydock.eclipse.EclipseProjectJsfResourceProvider

Note: Use the correct package name for your project!

3) Picking up Facelet includes and resources from OTHER JSF PROJECTS IN THE WORKSPACE

This one is also easy.

We create a custom Facelets ResourceResolver.

ClasspathResourceResolver.java:

/**
 * This is a special Facelets ResourceResolver, which allows us to ui:include resources from
 * the classpath, rather than from jars. This is necessary in for the Incubator to see stuff
 * in other projects under META-INF/resources/ 
 * 
 * @author Val Blant
 */
public class ClasspathResourceResolver extends DefaultResourceResolver {
	/**
	 * First check the context root, then the classpath
	 */
    public URL resolveUrl(String path) {
        URL url = super.resolveUrl(path);
        if (url == null) {
            
            /* classpath resources don't start with /, so this must be a jar include. Convert it to classpath include. */
            if (path.startsWith("/")) {
                path = "META-INF/resources" + path;
            }
            url = Thread.currentThread().getContextClassLoader().getResource(path);
        }
        return url;
    }
}

Now we register it in our web.xml:

	<!-- This allows us to "ui:include" resources from the classpath, rather than from jars, which is important for working with DryDocked projects directly from our Eclipse workspace -->
	<context-param>
		<param-name>facelets.RESOURCE_RESOLVER</param-name>
		<param-value>ca.gc.agr.common.web.jsf.ClasspathResourceResolver</param-value>
	</context-param>	

And that’s it! We now have everything we need to load all JSF resources from Eclipse projects instead of JARs.

Eclipse Project Setup

All that remains is to reconfigure the Eclipse workspace to start using our new capabilities.

  1. Import your JSF library projects and all their dependencies into your Eclipse workspace together with the Web Application you are working on.
  2. Go to all projects that have dependencies on common component jars, delete the jar dependencies, and replace them with project dependencies that are now in your workspace.
  3. Get rid of any test related project exports from the library projects that might interfere with the running of the app. This may not be necessary depending on your configuration.
  4. Configure your Application Server classpath to use the Eclipse Projects instead of JARs.
  5. Configure your build scripts to turn off these modifications, so they don’t get deployed anywhere past your development machine. This is as simple as not including META-INF/services/com.sun.faces.spi.FacesConfigResourceProvider and META-INF/aop.xml in your WAR.

And that’s it.

jQuery Conflicts or Re-routing JSF resource requests with RichFaces Resource Mapping

01 Wednesday May 2013

Posted by valblant in JSF, RichFaces

≈ 1 Comment

If you are using RichFaces and you ever need to resolve a resource conflict (like a conflicting version of jQuery, for example), you need to read this article:
http://rik-ansikter.blogspot.ca/2012/02/re-routing-jsf-resource-requests-with.html

There is even an example implementation provided:
https://github.com/camra/Richfaces-Primefaces-Problem

Ajax, JSF 2 and ClientBehaviors

30 Tuesday Apr 2013

Posted by valblant in JSF

≈ Leave a comment

I consider myself to have a fairly expert understanding of JSF-related technologies, so it is becoming rare that I come across articles that teach me something new about the subject. However, I have come across just such a pair of articles, so I thought I’d share. These provide an excellent overview of the subjects and it is thorough enough that I learned some new things that I never came across before.

“Ajax and JSF, Joined At Last” by Jay Balunas

“Introducing JSF 2 Client Behaviors” by Dan Allen

Using CGLIB proxies to create efficient wrappers

29 Monday Apr 2013

Posted by valblant in CGLIB, JSF, RichFaces

≈ Leave a comment

Tags

cglib, JSF

I was recently faced with a problem that encouraged me to look into CGLIB proxies. What I needed to do was to slightly modify the way a RichFaces component rendered a partial response (an AJAX response). The change was very small, yet the only way to do it with traditional methods would be to override a RichFaces class, copy-and-paste a large method and make the necessary changes there.

I did not want to go in that direction, b/c that would make my code very sensitive to changes in future versions of RichFaces. Instead I decided to explore the possibility of somehow intercepting a call to PartialResponseWriter.startUpdate() method, b/c that was the behaviour I needed to change.

The obvious strategy here would be to create a wrapper around the PartialResponseWriter and make the necessary changes in the method I need. The problem with this approach is that my wrapper would have to implement about 20 methods, all of which except for one would be useless pass-throughs. That’s a lot of boilerplate there. A Lazy Programmer such as myself would never actually spend the time writing all that useless code. So, I decided to see if I could achieve my goal by utilizing CGLIB proxies.

CGLIB

CGLIB is a powerful and performant bytecode generation library, that relies on the low-level ASM framework (http://asm.ow2.org/). If you ever used Spring AOP or Hibernate, than you are probably used to seeing CGLIB proxied objects all the time.

CGLIB codebase is small, but not well documented, so it took me some time to figure out how to create some simple proxies.

Creating a Simple Proxy

Unlike the JDK dynamic proxy, we cannot proxy an existing object. The target object must be created by the CGLIB library, so all we get to do is specify the superclass for our proxy, as well as which constructor to use, if any.

The core of the CGLIB proxying API is the net.sf.cglib.proxy.Enhancer class. It has a lot of helper method for different situations. You can easily explore these options by looking at the source code for that class. In my example here I’ll show how to create a proxy for PartialResponseWriter, which has no default constructor:

private PartialResponseWriter proxyResponseWriter(PartialResponseWriter origWriter) {
	Enhancer enhancer = new Enhancer();
	enhancer.setSuperclass(PartialResponseWriter.class);
	enhancer.setCallback(new ResponseWriterInterceptor(origWriter));
		
	PartialResponseWriter proxy = (PartialResponseWriter) enhancer.create(
			new Class[] {ResponseWriter.class}, 
			new Object[] {origWriter.getWrapped()});
	
	return proxy;
}

private class ResponseWriterInterceptor implements MethodInterceptor {
	private PartialResponseWriter originalWriter;
	public ResponseWriterInterceptor(PartialResponseWriter originalWriter) {
		this.originalWriter = originalWriter;
	}

	public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if ( "startUpdate".equals(method.getName()) ) {
			String clientId = args[0].toString();
			
			if ( FixedUICollapsibleSubTable.this.getClientId().equals(clientId) ) { // Make sure the update is for our table
				clientId += ":c";
				return method.invoke(originalWriter, new Object[] {clientId});
			}
		}

		return method.invoke(originalWriter, args);
	}
}


This code creates a new instance of PartialResponseWriter and provides us with a way to examine and/or modify all calls to it via the provided MethodInterceptor. In this case I made the interceptor act as a pass-through for all methods, except startUpdate, which does additional processing before calling the proxied method.

The Final Hack

The final implementation of this hack was a bit more complex than proxying just the PartialResponseWriter, since RichFaces did not make it easy to “install” our proxied version of the ResponseWriter into the PartialViewContext, nevertheless this strategy worked out very well for me.

The two additional things I needed to do was CGLIB proxy ExtendedPartialViewContext as well, in order to get at the right spot to proxy PartialResponseWriter.

I also had to use reflection to set the resulting PartialResponseWriter proxy on the current FacesContext.

Here is the full code for the hack:

/**
* The ajax response modification allows us to re-render only the subtable we want on ajax requests, rather than
* the whole master table.
*
* @author Val Blant
*/
public class FixedUICollapsibleSubTable extends UICollapsibleSubTable {

/**
 * This method installs hooks into the <code>PartialResponseWriter</code> used to put together an ajax 
 * response that involves our Collapsible Sub Table. We use CGLIB proxies to intercept the startUpdate() 
 * call and manipulate the clientId used in writing the response.
 */
@Override
public boolean visitTree(VisitContext visitContext, VisitCallback callback) {
	FacesContext facesContext = FacesContext.getCurrentInstance();
	PartialViewContext viewContext = facesContext.getPartialViewContext();
	
	boolean magicEnabled = false;
	if ( visitContext instanceof RenderExtendedVisitContext ) { // Only do this for render phase
		if ( viewContext instanceof ExtendedPartialViewContext ) { // Should always be true unless RF4 changes something
			magicEnabled = true;

			// Proxy the View Context and set it into the FacesContext
			//
			ExtendedPartialViewContext proxy = proxyViewContext(viewContext);
			replaceViewContext(facesContext, proxy);
		}
		
	}
	
	// Visit the subtable with proxied PartialResponseWriter
	//
	boolean result = super.visitTree(visitContext, callback);
	
	// Restore the original objects
	//
	if ( magicEnabled ) {
		replaceViewContext(facesContext, viewContext);
	}
	
	return result;
}

/**
 * @param viewContext
 * @return CGLIB proxy of the passed in <code>PartialViewContext</code>
 * @see PartialViewContextInterceptor
 */
private ExtendedPartialViewContext proxyViewContext(PartialViewContext viewContext) {
	Enhancer enhancer = new Enhancer();
	enhancer.setSuperclass(ExtendedPartialViewContext.class);
	enhancer.setCallback(new PartialViewContextInterceptor((ExtendedPartialViewContext)viewContext));
	
	ExtendedPartialViewContext proxy = (ExtendedPartialViewContext) enhancer.create(
			new Class[] {FacesContext.class}, 
			new Object[] {FacesContext.getCurrentInstance()});
	
	return proxy;
}

/**
 * Replaces the <code>PartialViewContext</code> inside the given facesContext
 * 
 * @param facesContext
 * @param viewContext
 */
private void replaceViewContext(FacesContext facesContext, PartialViewContext viewContext) {
	try {
		Field f = facesContext.getClass().getDeclaredField("partialViewContext");
		f.setAccessible(true);
		f.set(facesContext, viewContext);
	} catch (Exception e) {
		throw new IllegalStateException(e);
	}
}


/**
 * Intercepts calls to getPartialResponseWriter() in order to return a proxied <code>PartialResponseWriter</code>
 * with our customizations.
 */
private class PartialViewContextInterceptor implements MethodInterceptor {
	private ExtendedPartialViewContext originalContext;
	public PartialViewContextInterceptor(ExtendedPartialViewContext originalContext) {
		this.originalContext = originalContext;
	}

	public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if ( "getPartialResponseWriter".equals(method.getName()) ) {
			return proxyResponseWriter();
		}
		else {
			return method.invoke(originalContext, args);
		}
	}
	
	/**
	 * @return CGLIB proxy of the <code>PartialResponseWriter</code>
	 * @see ResponseWriterInterceptor
	 */
	private PartialResponseWriter proxyResponseWriter() {
		PartialResponseWriter origWriter = originalContext.getPartialResponseWriter();
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(PartialResponseWriter.class);
		enhancer.setCallback(new ResponseWriterInterceptor(origWriter));
		
		PartialResponseWriter proxy = (PartialResponseWriter) enhancer.create(
				new Class[] {ResponseWriter.class}, 
				new Object[] {origWriter.getWrapped()});
		
		return proxy;
	}
	
}

/**
 * Intercepts calls to startUpdate() method in order to modify the clientId.
 * 
 * This is necessary, b/c we are no longer using the stock version of the Collapsible Sub Table from RF4.
 * We made a lot of modifications which change the HTML structure of the table. One change involves not rendering
 * the 'tbody' tag with id that matches the clientId of our subtable component. We are only rendering the content 
 * 'tbody', which has the id with form: "{clientId}:c".
 *  
 * The purpose of this interceptor is to make sure that the ajax update response targets the content 'tbody', rather than
 * now missing 'tbody'.
 */
private class ResponseWriterInterceptor implements MethodInterceptor {
	private PartialResponseWriter originalWriter;
	public ResponseWriterInterceptor(PartialResponseWriter originalWriter) {
		this.originalWriter = originalWriter;
	}

	public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		if ( "startUpdate".equals(method.getName()) ) {
			String clientId = args[0].toString();
			
			if ( FixedUICollapsibleSubTable.this.getClientId().equals(clientId) ) { // Make sure the update is for our table
				clientId += ":c";
				return method.invoke(originalWriter, new Object[] {clientId});
			}
		}

		return method.invoke(originalWriter, args);
	}
}


}

Navigate <rich:tabPanel /> with keyboard

08 Friday Feb 2013

Posted by valblant in JSF, RichFaces

≈ Leave a comment

<rich:tabPanel /> does not support any keyboard interactions out of the box. One way to add it is to include the following javascript code:

 

/**
 * WCAG requires keyboard navigation for tabs. This code turns all inactive tabs into links,
 * so the browser starts tabbing over them. 
 * 
 * Author: Val Blant
 */
jQuery(document).ready(function() {
	jQuery("td.rf-tab-hdr-inact").children('span').each(function() {
		var tabName = jQuery(this).html();
		jQuery(this).empty();
		jQuery(this).append("<a href='#' style='text-decoration:none; color: black;' onclick='return false;'>" + tabName + "</a>");
	});
});

Dirty <rich:tabPanel />

15 Tuesday Jan 2013

Posted by valblant in JSF

≈ Leave a comment

Tags

Javascript, RichFaces

I have recently had to implement a new requirement for all pages in our app that have a RichFaces Tab Panel. This post is about RichFaces 4.2.Final.

Requirement

Change tabs with Unsaved Changes:
1. Issue the following warning:

There are pending changes to the current tab that have not been saved. Do you wish to proceed with the tab change, or cancel and return to the tab?

2. The User may elect to either:
2.1. Proceed; The changes to the current tab are discarded.
2.2. Cancel; The use case continues at the step at which the user elected to change tabs.

Analysis

rich:tabPanel provides us with the following tools:

  • We can call a server method when the tab is changed (if we use ajax), but w/o ability to stop the tab change or show any warnings
  • We can call arbitrary javascript with ability to warn the user and abort tab switch, but the javascript is rendered at page render time, not at tab switch time, so no info from the server can be communicated

I did not want to get into server side tracking of all changes, since that would really complicate all pages, so the solution must be client side only.

Solution

I have created a very simple framework that I call Dirty Field Handler. It works as follows:

  • dirtyFieldHandler.js attaches a “delegated” event handler to the document, which will handle all “change” events from any input field on the page
  • Developer can use the DirtyFieldHandler framework as follows:
<!-- Include the framework on the page that needs it -->
 	<h:outputScript library="javascript" name="dirtyFieldHandler.js" target="head"/>

 	.....

 	<rich:tabPanel
 		switchType="ajax" <!-- Can't call the server w/o using ajax tab switches! -->
 		onbeforeitemchange='return DirtyFieldHandler.warnUser("#{msgs["common.warn.unsavedTab"]}");' <!-- Smart Javascript popup -->
 		itemChangeListener="#{initiativeBackingBean.tabSwitchAction}"> <!-- Notify the server that tab was switched -->

 		......

	</rich:tabPanel>

Code

 
dirtyFieldHandler.js:

/**
 * Provides the ability to detect if any input fields on the page were dirtied.
 * 
 * Exposes DirtyFieldHandler.warnUser(message, [true/false]) function that can be called
 * by the page to warn the user about dirty fields.
 * 
 * @author Val Blant
 */

if (!window.DirtyFieldHandler) {
	window.DirtyFieldHandler = {};
}


(function($, dfh) {
	
	dfh.setDirty = function() {
		dfh.dirty = true;
	}

	dfh.resetDirty = function() {
		dfh.dirty = false;
	}

	/**
	 * Warns the user if any fields are dirty. If the user chooses
	 * to navigate away, clears the dirty flag, unless the 2nd parameter is used
	 * to override that behavior
	 */
	dfh.warnUser = function(event, message, clearDirty) {
		var leavePage = true;
		clearDirty = typeof clearDirty !== 'undefined' ? clearDirty : true;
		
		if (dfh.dirty) {
			leavePage = confirm(message);
			if ( leavePage && clearDirty) {
				dfh.resetDirty();
			}
		}

		return leavePage;
	}
	
	
	
	// Init dirty flag
	//
	dfh.resetDirty();
	
	// Attach a "delegated" event handler to the document. All events that bubble
	// from input elements under this document will execute the handler.
	// We use a delegated handler b/c this approach will detect input field
	// changed or added by ajax requests
	//
	$(document).on("change", ":input", dfh.setDirty)

}(jQuery, window.DirtyFieldHandler));

Primefaces and Broken Head Resource Ordering

14 Monday Jan 2013

Posted by valblant in JSF

≈ Leave a comment

Tags

JSF, PrimeFaces, RichFaces

I have recently tried using PrimeFaces with a RichFaces application. I am very impressed with PrimeFaces. It is a well designed, simple, yet powerful JSF framework with a lot of cool components. Unfortunately, it does have bugs that I found as soon as I tried to use it.

Introduction

PrimeFaces is an open source JSF component suite with various extensions.

  • Rich set of components (HtmlEditor, Dialog, AutoComplete, Charts and many more).
  • Built-in Ajax based on standard JSF 2.0 Ajax APIs.
  • Lightweight, one jar, zero-configuration and no required dependencies.
  • Ajax Push support via websockets.
  • Mobile UI kit to create mobile web applications for handheld devices.
  • Skinning Framework with 35+ built-in themes and support for visual theme designer tool.
  • Extensive, clear documentation.
  • Large, vibrant and active user community.

Interaction with JSF stack

PrimeFaces uses the following classes to insert itself into the JSF framework. i.e – these are the classes which will be doing stuff to our requests, even if we are not using any PrimeFaces components!

  • org.primefaces.context.PrimePartialResponseWriter (affects AJAX requests)
  • org.primefaces.context.PrimePartialViewContext (affects AJAX requests)
  • org.primefaces.application.PrimeResourceHandler (affects resource handling)

Head Resource Ordering Bug

PrimeFaces comes with some very cool features that allow you to control where your resources are going to be rendered in the <HEAD/>. You can use facets to place your resources into “first”, “middle” and “last” positions. This is a nice feature, since with RichFaces I had to place our style and script tags at the end of the <BODY/>, to make sure that they are rendered after the RF resources. This order is important, since it allows us to override RF styles and scripts.

However, there is a very obvious bug in org.primefaces.renderkit.HeadRenderer which makes this feature quite destructive to our app. Simply including the PrimeFaces jar causes all of our script and style overrides to stop working, b/c the app resources are placed before all others.

There are other issues with this feature as well: http://code.google.com/p/primefaces/issues/detail?id=4807

Workaround

The problem is explained here: http://code.google.com/p/primefaces/issues/detail?id=4807#c1. Even though I know how to fix the problem, I do not wish to start maintaining our own PrimeFaces patches at this point of low adoption. So, the workaround is to disable Head Resource Ordering entirely for now:

WEB-INF/faces-config.xml:

<renderer>
   <component-family>javax.faces.Output</component-family>
   <renderer-type>javax.faces.Head</renderer-type>
   <renderer-class>com.sun.faces.renderkit.html_basic.HeadRenderer</renderer-class>
</renderer>

This means that we now have to manually hardcode the theme include for PrimeFaces components:

WEB-INF/layout/mainTemplate.xhtml:

<h:outputStylesheet library="primefaces-bluesky" name="theme.css" target="head" />

Blog at WordPress.com.

  • Follow Following
    • N1nja Hacks
    • Already have a WordPress.com account? Log in now.
    • N1nja Hacks
    • Customize
    • Follow Following
    • Sign up
    • Log in
    • Report this content
    • View site in Reader
    • Manage subscriptions
    • Collapse this bar