OmniFaces 3.2 has been released!
Next to a bunch of utility methods, this version adds a <o:hashParam>
and a CDNResource
, and the FullAjaxExceptionhandler
and FacesExceptionFilter
will from now log the exceptions with an UUID and user IP.
You can find the complete list of additions, changes and fixes at What's new in OmniFaces 3.2? list in showcase.
Installation
Non-Maven users: download OmniFaces 3.2 JAR and drop it in /WEB-INF/lib
the usual way, replacing the older version if any.
Maven users: use <version>3.2</version>
.
<dependency>
<groupId>org.omnifaces</groupId>
<artifactId>omnifaces</artifactId>
<version>3.2</version>
</dependency>
o:hashParam
This new brother of <f|o:viewParam>
will less or more do the same things, but then with hash query string parameters instead of request query string parameters.
The "hash query string" is the part in URL after the #
which could be formatted in the same format
as a regular request query string (the part in URL after the ?
). An example:
http://example.com/page.xhtml#foo=baz&bar=kaz
This specific part of the URL (also called hash fragment identifier) is by default not sent to the server. The <o:hashParam>
will on page load and on every window.onhashchange
event send it anyway so that the JSF model gets updated, and on every JSF ajax request update the hash query string when the corresponding JSF model value has changed.
CDNResource
This is a new javax.faces.application.Resource
subclass which can be used by your custom ResourceHandler
implementation which automatically uploads the local resources to a CDN and then returns the CDN URL instead of the local URL. The CDNResource
offers the CombinedResourceHandler
the opportunity to automatically generate a fallback URL to the local resource into the <script>
and <link rel="stylesheet">
elements generated by the associated <h:outputScript>
, <o:deferredScript>
and <h:outputStylesheet>
components. This is for now indeed only useful when you have enabled the CombinedResourceHandler
. A more general appliance may come in a future OmniFaces version which should also cover non-combined resources and images.
Imagine that you have the below custom resource handler configured as <resource-handler>
in faces-config.xml
which automatically uploads all local CSS/JS/image resources to a Amazon S3 based CDN:
public class AmazonS3ResourceHandler extends DefaultResourceHandler {
@Inject
private AmazonS3Service s3;
public AmazonS3ResourceHandler(ResourceHandler wrapped) {
super(wrapped);
}
@Override
public Resource decorateResource(Resource resource, String resourceName, String libraryName) {
if (resource != null && Utils.endsWithOneOf(resourceName, ".js", ".css", ".jpg", ".gif", ".png", ".svg")) {
return new CDNResource(resource, s3.getURL(resource, resourceName, libraryName));
}
else {
return resource;
}
}
}
Whereby the your custom AmazonS3Service
looks something like this, using the com.amazonaws:aws-java-sdk-s3
library:
@ApplicationScoped
public class AmazonS3Service {
// key = resource path, value = last modified timestamp
private static final Map<String, Long> RESOURCES = new ConcurrentHashMap<>();
private AmazonS3 client;
private String bucket;
private String baseURL;
@PostConstruct
private void init() {
client = AmazonS3ClientBuilder.standard().build(); // Use your own builder of course.
bucket = "cdn"; // Use your own S3 bucket name of course.
baseURL = "https://cdn.example.com/"; // Use your own CDN URL of course.
}
public String getURL(Resource resource, String resourceName, String libraryName) {
String path = Paths.get(Utils.coalesce(libraryName, "")).resolve(resourceName).toString();
Long lastModified = RESOURCES.computeIfAbsent(path, k -> uploadIfNecessary(resource, path));
return baseURL + path + "?v=" + lastModified;
}
private long uploadIfNecessary(Resource resource, String path) {
Long s3LastModified = null;
long localLastModified;
try {
if (resource instanceof DynamicResource) { // E.g. combined resource.
localLastModified = ((DynamicResource) resource).getLastModified();
}
else {
localLastModified = resource.getURL().openConnection().getLastModified();
}
if (client.doesObjectExist(bucket, path)) {
s3LastModified = client.getObjectMetadata(bucket, path).getLastModified().getTime();
if (localLastModified > s3LastModified) {
s3LastModified = null;
}
}
if (s3LastModified == null) {
s3LastModified = localLastModified;
upload(resource, path, s3LastModified);
}
}
catch (Exception e) {
throw new FacesException(e);
}
return s3LastModified;
}
private void upload(Resource resource, String path, long lastModified) throws Exception {
String filename = Paths.get(path).getFileName().toString();
byte[] content = Utils.toByteArray(resource.getInputStream());
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentType(resource.getContentType());
metadata.setContentLength(content.length);
metadata.setContentDisposition(Servlets.formatContentDispositionHeader(filename, false));
metadata.setLastModified(new Date(lastModified));
client.putObject(bucket, path, new ByteArrayInputStream(content), metadata);
}
}
Then the CDNResource
marker class will automatically force the CombinedResourceHandler
to generate the following <script>
and <link rel="stylesheet">
markup:
<script type="text/javascript" src="https://cdn.example.com/omnifaces.combined/XYZ.js?v=123"
onerror="document.write('<script src="/javax.faces.resource/XYZ.js.xhtml?ln=omnifaces.combined&v=123"></script>')"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.example.com/omnifaces.combined/XYZ.css?v=123"
onerror="this.onerror=null;this.href='/javax.faces.resource/XYZ.css.xhtml?ln=omnifaces.combined&v=123'" />
You see, the CombinedResourceHandler
will automatically include the onerror
attribute which points to the local URL as a fallback.
UUID and IP in exception logs
A very common requirement in real world projects is that any logging of the exception stack trace should also include an unique identifier (UUID) which in turn is also included in the error page and/or an automatic exception email to the administrator. This will make it easier to find back the stack trace in the server logs.
The OmniFaces FullAjaxExceptionHandler
and FacesExceptionFilter
will from now on also include the UUID and client IP address in the exception logs. Besides, the FacesExceptionFilter
will now also start logging exception stack traces wheres it didn't do it. The UUID is in turn available as a request attribute with the name org.omnifaces.exception_uuid
which you can if necessary easily include in your custom error page.
<li>Error ID: #{requestScope['org.omnifaces.exception_uuid']}</li>
These improvements will make it unnecessary to customize the FullAjaxExceptionHandler
and/or FacesExceptionFilter
to include the UUID.
How about OmniFaces 2.x and 1.1x?
The 2.x got on special request also the CDNResource
and hence steps from 2.6.9 to 2.7 instead of to 2.6.10. For the remainder only bugfixes are done and no other new things. As long as you're still on JSF 2.2 with CDI, you can continue using latest 2.x.
The 1.1x is basically already since 2.5 in maintenance mode. I.e. only critical bugfix versions will be released. It's currently still at 1.14.1 (May 2017), featuring the same features as OmniFaces 2.4, but without any JSF 2.2 and CDI things and therefore compatible with CDI-less JSF 2.0/2.1.
Maven download stats
Here are the 2018's Maven download stats so far:
- January 2018: 14646
- February 2018: 14786
- March 2018: 18059
- April 2018: 16642
- May 2018: 17876
- June 2018: 17432
- July 2018: TBD
The Definitive Guide to JSF in Java EE 8
Just in case if you have missed it .. I have finally written a book!
The Definitive Guide to JSF in Java EE 8 is since July 11, 2018 available at Amazon.com. This book is definitely a must read for anyone working with JSF or interested in JSF. It uncovers the history, inner workings, best practices and hidden gems of JSF. The source code of the book's examples can be found at GitHub.
No comments:
Post a Comment