How to insert response size and time into the page itself, at least partially? - jsf

I realize it's a chicken and egg problem and that it's not possible to accurately resolve the time it took to render a page (or the size of response) and insert that number into the page itself without affecting either measure. Nevertheless, I'm looking for a way to insert either number partially in a page of a JSF/Facelets/Seam application.
E.g., at the bottom of a .jsf page somewhere:
<!-- page size: 10.3Kb -->
<!-- render time: 0.2s -->
I've come across JSFUnit's JSFTimer, which is really handy. However, the phase listener approach doesn't allow the results of RENDER_RESPONSE phase to be inserted into the page. Not sure how to access the size of the response encoded so far either.
Is there a quick and dirty way to hook up to some sort of post-processing event at or after the end of RENDER_RESPONSE and to inject both numbers into the page about to be rendered? One way of approaching this is perhaps through servlet filters, but I'm looking for something simpler; perhaps a trick with Seam or Facelets...
Thanks,
-A

This is a perfect use case for the Apache Commons IO CountingOutputStream. You need to create a Filter which uses HttpServletResponseWrapper to replace the OutputStream of the response with this one and replaces the Writer as well which should wrap the wrapped OutputStream. Then get hold of the HttpServletResponseWrapper instance in the request scope so that you can get the getByteCount() from the CountingOutputStream.
Here's a kickoff example of the CountingFilter:
public class CountingFilter implements Filter {
#Override
public void init(FilterConfig arg0) throws ServletException {
// NOOP.
}
#Override
public void doFilter(ServletRequest request, final ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpres = (HttpServletResponse) response;
CountingServletResponse counter = new CountingServletResponse(httpres);
HttpServletRequest httpreq = (HttpServletRequest) request;
httpreq.setAttribute("counter", counter);
chain.doFilter(request, counter);
counter.flushBuffer(); // Push the last bits containing HTML comment.
}
#Override
public void destroy() {
// NOOP.
}
}
The CountingServletResponse:
public class CountingServletResponse extends HttpServletResponseWrapper {
private final long startTime;
private final CountingServletOutputStream output;
private final PrintWriter writer;
public CountingServletResponse(HttpServletResponse response) throws IOException {
super(response);
startTime = System.nanoTime();
output = new CountingServletOutputStream(response.getOutputStream());
writer = new PrintWriter(output, true);
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
return output;
}
#Override
public PrintWriter getWriter() throws IOException {
return writer;
}
#Override
public void flushBuffer() throws IOException {
writer.flush();
}
public long getElapsedTime() {
return System.nanoTime() - startTime;
}
public long getByteCount() throws IOException {
flushBuffer(); // Ensure that all bytes are written at this point.
return output.getByteCount();
}
}
The CountingServletOutputStream:
public class CountingServletOutputStream extends ServletOutputStream {
private final CountingOutputStream output;
public CountingServletOutputStream(ServletOutputStream output) {
this.output = new CountingOutputStream(output);
}
#Override
public void write(int b) throws IOException {
output.write(b);
}
#Override
public void flush() throws IOException {
output.flush();
}
public long getByteCount() {
return output.getByteCount();
}
}
You can use it in any (even non-JSF) page as follows:
<!DOCTYPE html>
<html
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>Counting demo</title>
</h:head>
<h:body>
<h1>Hello World</h1>
</h:body>
</html>
<!-- page size: #{counter.byteCount / 1000}KB -->
<!-- render time: #{counter.elapsedTime / 1000000}ms -->

I wrote a blog post explaining how you could create an Interceptor that would measure each method call your seam compontents where using.
You can find the blog post here.
You need to scroll down to the second part.
Basically, all you need to do is annotate the method you want to measure with
#MeasureCalls and it will automatically be picked up by the interceptor
#Name("fooBean")
#MeasureCalls
public class FooBean
An output would be something like this, showing the time it took in milliseconds and how many times each method was called:
284.94 ms 1 FooBean.getRandomDroplets()
284.56 ms 1 GahBean.getRandomDroplets()
201.60 ms 2 SohBean.searchRatedDoodlesWithinHead()
185.94 ms 1 FroBean.doSearchPopular()
157.63 ms 1 FroBean.doSearchRecent()
42.34 ms 1 FooBean.fetchMostRecentYodel()
41.94 ms 1 GahBean.getMostRecentYodel()
15.89 ms 1 FooBean.getNoOfYodels()
15.00 ms 1 GahBean.getNoOfYodels()
9.14 ms 1 SohBean.mainYodels()
1.11 ms 2 SohBean.trackHoorayEvent()
0.32 ms 1 FroBean.reset()
0.22 ms 43 NohBean.thumbPicture()
0.03 ms 18 FooBean.getMostRecentYodels()
0.01 ms 1 NohBean.profilePicture()
0.01 ms 1 FroBean.setToDefault()
0.01 ms 1 FroBean.getRecentMarker()

Or have an asynch Javascript call that gets the response time and size from the server after its ready? Treat it like a callback to be performed after the page is done loading and the values are ready to insert.

Related

Why Primefaces change encoding of the response?

I have a webapp that uses Primefaces 4 (I cannot update it, it does not deppend on me) with a datatable with a column that sometimes has alphas (the greek letter: α).
To load this datatable, I use a button of a form with some filters. When I click on that button, the backend sends me the response using ISO-8859-15 encoding. That is a problem because with this encoding alphas are represented as a question mark (?) but, when I use the pagination buttons to go to the second page, I receive the response using UTF-8 encoding and alphas are correctly represented, even I can go back to the first page, and I receive the response using UTF-8.
I was able to solve this issue on my local testing Tomcat server changing encoding in the first line of the xhtml file:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
But this is not working in the production server that uses Weblogic 12c. The data of the datatable came from a lazydatamodel class and following it with the debugger it does exactly the same with both buttons, except for the page obviously. But I leave here the code of the load method of this lazydatamodel class:
private List<ListElement> data;
#Autowired
private transient CamFacade camFacade;
public List<ListElement> load(int first, int pageSize, String sortField,
SortOrder sortOrder, Map<String, String> filters) {
Map<String, Object> params = new HashMap<String, Object>();
params.putAll(filters);
params.put("first", first);
params.put("pageSize", pageSize);
if(sortField == null){
params.put("orderBy", params.get("orderBy"));
params.put("order", params.get("order"));
}else{
params.put("orderBy", sortField);
params.put("order", sortOrder.name());
}
int count = camFacade.countByParams(params);
if (count > 0 ) {
data = camFacade.findByParams(params);
} else {
data = new ArrayList<ListElement>();
}
return data;
}
EDIT:
I also tried this solution: Unicode input retrieved via PrimeFaces input components become corrupted
But it does not work for me. If I am not misunderstanding it, with this code I change the request encoding, but actually my request is ok, it is UTF-8. I need to change the RESPONSE encoding.
Any idea how to solve this problem?
You can add a Filter where you set the character encoding on both the request and the response to UTF-8:
#WebFilter("/*")
public class CharacterEncodingFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
chain.doFilter(request, response);
}
// ...
}

Dynamic URLs for a Liferay Portlet

I am looking for a solution for developing a dynamic content Liferay Portlet with different URLs. I am not going create separate pages in Liferay.All the information are stored in separate database and all pages are generated by using a Liferay Portlet.My current Liferay version is 6.2 CE.
Sample URLs are ,
https://localhost/travel/hotel/Lanzarote/Costa Teguise/Hotel Beatriz Costa & Spa
https://localhost/travel/hotel/Lanzarote/Costa Teguise/Club Siroco Apartments
https://localhost/travel/hotel/Lanzarote/Costa Teguise/El Guarapo Apartments
How do I implement different URLs with out creating separate pages in Liferay? If I need to use Liferay API for generate dynamic URLs , what are the API components do I need to use?
You can get very similar urls with Liferay friendly url mapping:
https://localhost:8080/{page}/-/hotel/Lanzarote/Costa Teguise/Hotel Beatriz Costa
https://localhost:8080/{page}/-/hotel/Lanzarote/Costa Teguise/Club Siroco Apartments
https://localhost:8080/{page}/-/hotel/Lanzarote/Costa Teguise/El Guarapo Apartments
To make it work, you need to configure the mapping in liferay-portlet.xml:
<portlet>
<portlet-name>my-hotel-portlet</portlet-name>
<friendly-url-mapper-class>com.liferay.portal.kernel.portlet.DefaultFriendlyURLMapper</friendly-url-mapper-class>
<friendly-url-mapping>hotel</friendly-url-mapping>
<friendly-url-routes>friendly-url-routes.xml</friendly-url-routes>
...
</portlet>
The hotel part of the url that comes right after /-/ is defined by <friendly-url-mapping>hotel</friendly-url-mapping> value.
The configuration refers to the routes defined in friendly-url-routes.xml. Just one route definition is necessary:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE routes PUBLIC "-//Liferay//DTD Friendly URL Routes 6.2.0//EN" "http://www.liferay.com/dtd/liferay-friendly-url-routes_6_2_0.dtd">
<routes>
<route>
<pattern>/{region}/{town}/{hotel}</pattern>
</route>
</routes>
Sample Liferay MVCPortlet method reading the parameters:
#Override
public void doView(RenderRequest renderRequest, RenderResponse renderResponse) {
String region = ParamUtil.getString(renderRequest, "region");
String town = ParamUtil.getString(renderRequest, "town");
String hotel = ParamUtil.getString(renderRequest, "hotel");
...
}
Sample Spring controller method reading the parameters:
#RenderMapping
public String renderHotel(#RequestParam String region, #RequestParam String town, #RequestParam String hotel) {
...
return "hotel/view";
}
See FriendlyURLMapper for detailed coverage.
Another solution is writing a separate servlet filter hook plugin project.The idea is to transfer the current URL into Liferay specific URL.
As an example:
https://localhost/travel/hotel/Lanzarote/Costa Teguise/Hotel Beatriz Costa & Spa
Converted in to,
https://localhost/web/guest/travel/hotel?p_p_id=hotelsearch_WAR_hotelportlet&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view&_hotelsearch_WAR_hotelportlet_param1=travel&_hotelsearch_WAR_hotelportlet_param2=hotel&_hotelsearch_WAR_hotelportlet_param3=Lanzarote&_hotelsearch_WAR_hotelportlet_param4=Costa%20Teguise&_hotelsearch_WAR_hotelportlet_param5=Hotel%20Beatriz%20Costa%20&%20Spa
First configure the mapping in sample hook project liferay-hook.xml :
<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.2.0//EN"
"http://www.liferay.com/dtd/liferay-hook_6_2_0.dtd">
<hook>
<servlet-filter>
<servlet-filter-name>CustomURLPatternFilter</servlet-filter-name>
<servlet-filter-impl>com.hotel.travel.portlet.customefilter.CustomURLPatternFilter</servlet-filter-impl>
<init-param>
<param-name>hello</param-name>
<param-value>world</param-value>
</init-param>
</servlet-filter>
<servlet-filter-mapping>
<servlet-filter-name>CustomURLPatternFilter</servlet-filter-name>
<url-pattern>/travel/hotel/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</servlet-filter-mapping>
Sample servlet filter class :
public class CustomURLPatternFilter implements Filter {
#Override
public void destroy() {
LOG.info("CustomURLPatternFilter.destroy()");
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws ServletException, IOException {
HttpServletRequest request = (HttpServletRequest) req;
String requestURI = request.getRequestURI();
try {
String[] urlPaths = StringUtil.split(requestURI, StringPool.FORWARD_SLASH);
System.out.println(urlPaths[0]);
System.out.println(urlPaths[1]);
System.out.println(urlPaths[2]);
System.out.println(urlPaths[3]);
System.out.println(urlPaths[4]);
System.out.println(urlPaths[5]);
if (urlPaths.length == 6) {
String forwardPath = "/web/guest/travel/hotel?p_p_id=hotelsearch_WAR_hotelportlet&p_p_lifecycle=1&p_p_state=normal&p_p_mode=view"
+ "&_hotelsearch_WAR_hotelportlet_param1=" + urlPaths[1]
+ "&_hotelsearch_WAR_hotelportlet_param2=" + urlPaths[2]
+ "&_hotelsearch_WAR_hotelportlet_param3=" + urlPaths[3]
+ "&_hotelsearch_WAR_hotelportlet_param4=" + urlPaths[4]
+ "&_hotelsearch_WAR_hotelportlet_param5=" + urlPaths[5];
req.getRequestDispatcher(forwardPath).forward(req, res);
}
else {
chain.doFilter(req, res);
}
} catch (Exception e) {
chain.doFilter(req, res);
e.printStackTrace();
}
}
#Override
public void init(FilterConfig filterConfig) {
System.out.println("Called SampleFilter.init(" + filterConfig + ")");
}
private static final Log LOG =
LogFactoryUtil.getLog(CustomURLPatternFilter.class);
}
At the end in your original hotel portlet ,
HttpServletRequest httpReq = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(req));
System.out.println( httpReq.getParameter("_hotelsearch_WAR_hotelportlet_param1") );
System.out.println( httpReq.getParameter("_hotelsearch_WAR_hotelportlet_param2") );
System.out.println( httpReq.getParameter("_hotelsearch_WAR_hotelportlet_param3") );
System.out.println( httpReq.getParameter("_hotelsearch_WAR_hotelportlet_param4") );
System.out.println( httpReq.getParameter("_hotelsearch_WAR_hotelportlet_param5") );

Post render event in JavaFX

I'm trying to add a click event listener to the label of all column-headers of a TableView, as follows:
for (final Node header : tblView.lookupAll(".column-header > .label")) {
if ((header != null) && (header instanceof Label)) {
final Label headerLabel = (Label) header;
// ...
}
}
Now, the problem is that if I do this in the initialize()-function of the Controller, the Scenegraph is not yet rendered and the above code won't work. Hence my question: Is there some kind of a post-render event?
many thanks in advance.
There is a WINDOW_SHOWN event in javafx.stage.WindowEvents. That is not (imo) "Post render event" but you can utilize it in similar manner, by adding an event handler to the Stage (which extends from Window).
In the initialize method of controller class, get the primary stage and then:
stage.addEventHandler(WindowEvent.WINDOW_SHOWN, new EventHandler<WindowEvent>() {
#Override
public void handle(WindowEvent window) {
Platform.runLater(new Runnable() {
#Override
public void run() {
addListenerToColumnHeaders();
}
});
}
});
Hope this helps, since didn't try myself.

How to save all FacesMessages over different requests

I was just wondering to implement a kind of log display to user where in all messages in the application are displayed to user all the time.
Since I use JSF 1.2 and RF 3.3.3, wanted to know if it is possible to save all messages be it added by different requests and display them to user, so that user will know the history of actions he has done. It is also useful for support team to analyse the cause of the problem and he can send the messages to developer if it needs to simulated or to debug purpose also.
I also know facesmessages get cleared over different requests, thats where my question lies, to save messages over different requests :)
Could be handled in different ways by saving them is a session variable or so...I would appreciate all possible answers
You could collect all messages during the render response in a PhaseListener. E.g.
public class MessagesListener implements PhaseListener {
#Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
#Override
public void beforePhase(PhaseEvent event) {
FacesContext context = event.getFacesContext();
Iterator<String> clientIds = context.getClientIdsWithMessages();
while (clientIds.hasNext()) {
String clientId = clientIds.next();
Iterator<FacesMessage> messages = context.getMessages(clientId);
while (messages.hasNext()) {
FacesMessage message = messages.next();
save(clientId, message); // Do your job here.
}
}
}
#Override
public void afterPhase(PhaseEvent event) {
// NOOP.
}
}
To get it to run, register it as follows in faces-config.xml:
<lifecycle>
<phase-listener>com.example.MessagesListener</phase-listener>
</lifecycle>

JSF2 Static Resource Management -- Combined, Compressed

Is anyone aware of a method to dynamically combine/minify all the h:outputStylesheet resources and then combine/minify all h:outputScript resources in the render phase? The comined/minified resource would probably need to be cached with a key based on the combined resource String or something to avoid excessive processing.
If this feature doesn't exist I'd like to work on it. Does anyone have ideas on the best way to implement something like this. A Servlet filter would work I suppose but the filter would have to do more work than necessary -- basically examining the whole rendered output and replacing matches. Implementing something in the render phase seems like it would work better as all of the static resources are available without having to parse the entire output.
Thanks for any suggestions!
Edit: To show that I'm not lazy and will really work on this with some guidance, here is a stub that captures Script Resources name/library and then removes them from the view. As you can see I have some questions about what to do next ... should I make http requests and get the resources to combine, then combine them and save them to the resource cache?
package com.davemaple.jsf.listener;
import java.util.ArrayList;
import java.util.List;
import javax.faces.component.UIComponent;
import javax.faces.component.UIOutput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.event.PreRenderViewEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import org.apache.log4j.Logger;
/**
* A Listener that combines CSS/Javascript Resources
*
* #author David Maple<d#davemaple.com>
*
*/
public class ResourceComboListener implements PhaseListener, SystemEventListener {
private static final long serialVersionUID = -8430945481069344353L;
private static final Logger LOGGER = Logger.getLogger(ResourceComboListener.class);
#Override
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
/*
* (non-Javadoc)
* #see javax.faces.event.PhaseListener#beforePhase(javax.faces.event.PhaseEvent)
*/
public void afterPhase(PhaseEvent event) {
FacesContext.getCurrentInstance().getViewRoot().subscribeToViewEvent(PreRenderViewEvent.class, this);
}
/*
* (non-Javadoc)
* #see javax.faces.event.PhaseListener#afterPhase(javax.faces.event.PhaseEvent)
*/
public void beforePhase(PhaseEvent event) {
//nothing here
}
/*
* (non-Javadoc)
* #see javax.faces.event.SystemEventListener#isListenerForSource(java.lang.Object)
*/
public boolean isListenerForSource(Object source) {
return (source instanceof UIViewRoot);
}
/*
* (non-Javadoc)
* #see javax.faces.event.SystemEventListener#processEvent(javax.faces.event.SystemEvent)
*/
public void processEvent(SystemEvent event) throws AbortProcessingException {
FacesContext context = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = context.getViewRoot();
List<UIComponent> scriptsToRemove = new ArrayList<UIComponent>();
if (!context.isPostback()) {
for (UIComponent component : viewRoot.getComponentResources(context, "head")) {
if (component.getClass().equals(UIOutput.class)) {
UIOutput uiOutput = (UIOutput) component;
if (uiOutput.getRendererType().equals("javax.faces.resource.Script")) {
String library = uiOutput.getAttributes().get("library").toString();
String name = uiOutput.getAttributes().get("name").toString();
// make https requests to get the resources?
// combine then and save to resource cache?
// insert new UIOutput script?
scriptsToRemove.add(component);
}
}
}
for (UIComponent component : scriptsToRemove) {
viewRoot.getComponentResources(context, "head").remove(component);
}
}
}
}
This answer doesn't cover minifying and compression. Minifying of individual CSS/JS resources is better to be delegated to build scripts like YUI Compressor Ant task. Manually doing it on every request is too expensive. Compression (I assume you mean GZIP?) is better to be delegated to the servlet container you're using. Manually doing it is overcomplicated. On Tomcat for example it's a matter of adding a compression="on" attribute to the <Connector> element in /conf/server.xml.
The SystemEventListener is already a good first step (apart from some PhaseListener unnecessity). Next, you'd need to implement a custom ResourceHandler and Resource. That part is not exactly trivial. You'd need to reinvent pretty a lot if you want to be JSF implementation independent.
First, in your SystemEventListener, you'd like to create new UIOutput component representing the combined resource so that you can add it using UIViewRoot#addComponentResource(). You need to set its library attribute to something unique which is understood by your custom resource handler. You need to store the combined resources in an application wide variable along an unique name based on the combination of the resources (a MD5 hash maybe?) and then set this key as name attribute of the component. Storing as an application wide variable has a caching advantage for both the server and the client.
Something like this:
String combinedResourceName = CombinedResourceInfo.createAndPutInCacheIfAbsent(resourceNames);
UIOutput component = new UIOutput();
component.setRendererType(rendererType);
component.getAttributes().put(ATTRIBUTE_RESOURCE_LIBRARY, CombinedResourceHandler.RESOURCE_LIBRARY);
component.getAttributes().put(ATTRIBUTE_RESOURCE_NAME, combinedResourceName + extension);
context.getViewRoot().addComponentResource(context, component, TARGET_HEAD);
Then, in your custom ResourceHandler implementation, you'd need to implement the createResource() method accordingly to create a custom Resource implementation whenever the library matches the desired value:
#Override
public Resource createResource(String resourceName, String libraryName) {
if (RESOURCE_LIBRARY.equals(libraryName)) {
return new CombinedResource(resourceName);
} else {
return super.createResource(resourceName, libraryName);
}
}
The constructor of the custom Resource implementation should grab the combined resource info based on the name:
public CombinedResource(String name) {
setResourceName(name);
setLibraryName(CombinedResourceHandler.RESOURCE_LIBRARY);
setContentType(FacesContext.getCurrentInstance().getExternalContext().getMimeType(name));
this.info = CombinedResourceInfo.getFromCache(name.split("\\.", 2)[0]);
}
This custom Resource implementation must provide a proper getRequestPath() method returning an URI which will then be included in the rendered <script> or <link> element:
#Override
public String getRequestPath() {
FacesContext context = FacesContext.getCurrentInstance();
String path = ResourceHandler.RESOURCE_IDENTIFIER + "/" + getResourceName();
String mapping = getFacesMapping();
path = isPrefixMapping(mapping) ? (mapping + path) : (path + mapping);
return context.getExternalContext().getRequestContextPath()
+ path + "?ln=" + CombinedResourceHandler.RESOURCE_LIBRARY;
}
Now, the HTML rendering part should be fine. It'll look something like this:
<link type="text/css" rel="stylesheet" href="/playground/javax.faces.resource/dd08b105bf94e3a2b6dbbdd3ac7fc3f5.css.xhtml?ln=combined.resource" />
<script type="text/javascript" src="/playground/javax.faces.resource/2886165007ccd8fb65771b75d865f720.js.xhtml?ln=combined.resource"></script>
Next, you have to intercept on combined resource requests made by the browser. That's the hardest part. First, in your custom ResourceHandler implementation, you need to implement the handleResourceRequest() method accordingly:
#Override
public void handleResourceRequest(FacesContext context) throws IOException {
if (RESOURCE_LIBRARY.equals(context.getExternalContext().getRequestParameterMap().get("ln"))) {
streamResource(context, new CombinedResource(getCombinedResourceName(context)));
} else {
super.handleResourceRequest(context);
}
}
Then you have to do the whole lot of work of implementing the other methods of the custom Resource implementation accordingly such as getResponseHeaders() which should return proper caching headers, getInputStream() which should return the InputStreams of the combined resources in a single InputStream and userAgentNeedsUpdate() which should respond properly on caching related requests.
#Override
public Map<String, String> getResponseHeaders() {
Map<String, String> responseHeaders = new HashMap<String, String>(3);
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US);
sdf.setTimeZone(TIMEZONE_GMT);
responseHeaders.put(HEADER_LAST_MODIFIED, sdf.format(new Date(info.getLastModified())));
responseHeaders.put(HEADER_EXPIRES, sdf.format(new Date(System.currentTimeMillis() + info.getMaxAge())));
responseHeaders.put(HEADER_ETAG, String.format(FORMAT_ETAG, info.getContentLength(), info.getLastModified()));
return responseHeaders;
}
#Override
public InputStream getInputStream() throws IOException {
return new CombinedResourceInputStream(info.getResources());
}
#Override
public boolean userAgentNeedsUpdate(FacesContext context) {
String ifModifiedSince = context.getExternalContext().getRequestHeaderMap().get(HEADER_IF_MODIFIED_SINCE);
if (ifModifiedSince != null) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN_RFC1123_DATE, Locale.US);
try {
info.reload();
return info.getLastModified() > sdf.parse(ifModifiedSince).getTime();
} catch (ParseException ignore) {
return true;
}
}
return true;
}
I've here a complete working proof of concept, but it's too much of code to post as a SO answer. The above was just a partial to help you in the right direction. I assume that the missing method/variable/constant declarations are self-explaining enough to write your own, otherwise let me know.
Update: as per the comments, here's how you can collect resources in CombinedResourceInfo:
private synchronized void loadResources(boolean forceReload) {
if (!forceReload && resources != null) {
return;
}
FacesContext context = FacesContext.getCurrentInstance();
ResourceHandler handler = context.getApplication().getResourceHandler();
resources = new LinkedHashSet<Resource>();
contentLength = 0;
lastModified = 0;
for (Entry<String, Set<String>> entry : resourceNames.entrySet()) {
String libraryName = entry.getKey();
for (String resourceName : entry.getValue()) {
Resource resource = handler.createResource(resourceName, libraryName);
resources.add(resource);
try {
URLConnection connection = resource.getURL().openConnection();
contentLength += connection.getContentLength();
long lastModified = connection.getLastModified();
if (lastModified > this.lastModified) {
this.lastModified = lastModified;
}
} catch (IOException ignore) {
// Can't and shouldn't handle it here anyway.
}
}
}
}
(the above method is called by reload() method and by getters depending on one of the properties which are to be set)
And here's how the CombinedResourceInputStream look like:
final class CombinedResourceInputStream extends InputStream {
private List<InputStream> streams;
private Iterator<InputStream> streamIterator;
private InputStream currentStream;
public CombinedResourceInputStream(Set<Resource> resources) throws IOException {
streams = new ArrayList<InputStream>();
for (Resource resource : resources) {
streams.add(resource.getInputStream());
}
streamIterator = streams.iterator();
streamIterator.hasNext(); // We assume it to be always true; CombinedResourceInfo won't be created anyway if it's empty.
currentStream = streamIterator.next();
}
#Override
public int read() throws IOException {
int read = -1;
while ((read = currentStream.read()) == -1) {
if (streamIterator.hasNext()) {
currentStream = streamIterator.next();
} else {
break;
}
}
return read;
}
#Override
public void close() throws IOException {
IOException caught = null;
for (InputStream stream : streams) {
try {
stream.close();
} catch (IOException e) {
if (caught == null) {
caught = e; // Don't throw it yet. We have to continue closing all other streams.
}
}
}
if (caught != null) {
throw caught;
}
}
}
Update 2: a concrete and reuseable solution is available in OmniFaces. See also CombinedResourceHandler showcase page and API documentation for more detail.
You may want to evaluate JAWR before implementing your own solution. I've used it in couple of projects and it was a big success. It used in JSF 1.2 projects but I think it will be easy to extend it to work with JSF 2.0. Just give it a try.
Omnifaces provided CombinedResourceHandler is an excellent utility, but I also love to share about this excellent maven plugin:- resources-optimizer-maven-plugin that can be used to minify/compress js/css files &/or aggregate them into fewer resources during the build time & not dynamically during runtime which makes it a more performant solution, I believe.
Also have a look at this excellent library as well:- webutilities
I have an other solution for JSF 2. Might also rok with JSF 1, but i do not know JSF 1 so i can not say. The Idea works mainly with components from h:head and works also for stylesheets. The result
is always one JavaScript (or Stylesheet) file for a page! It is hard for me to describe but i try.
I overload the standard JSF ScriptRenderer (or StylesheetRenderer) and configure the renderer
for the h:outputScript component in the faces-config.xml.
The new Renderer will now not write anymore the script-Tag but it will collect all resources
in a list. So first resource to be rendered will be first item in the list, the next follows
and so on. After last h:outputScript component ist rendered, you have to render 1 script-Tag
for the JavaScript file on this page. I make this by overloading the h:head renderer.
Now comes the idea:
I register an filter! The filter will look for this 1 script-Tag request. When this request comes,
i will get the list of resources for this page. Now i can fill the response from the list of
resources. The order will be correct, because the JSF rendering put the resources in correct order
into the list. After response is filled, the list should be cleared. Also you can do more
optimizations because you have the code in the filter....
I have code that works superb. My code also can handle browser caching and dynamic script rendering.
If anybody is interested i can share the code.

Resources