During my work on a custom JSF library I was looking for a way how to read the metadata from composite components in order to create a nice self-documented library. Based on this
great article from Ed Burns I implemented a nice documentation framework which extracts all metadata from the VDL. Main idea:
FacesContext fc = FacesContext.getCurrentInstance();
ViewDeclarationLanguage vdl = fc.getApplication().getViewHandler().getViewDeclarationLanguage(fc, "/views/home.xhtml");
Resource ccResource = fc.getApplication().getResourceHandler().createResource(resourceName, libraryName);
BeanInfo metadata = vdl.getComponentMetadata(context, ccResource);
PropertyDescriptor attributes[] = metadata.getPropertyDescriptors();
// read metadata for cc:attributes, cc:clientBehavior, cc:valueHolder, cc:actionSource, cc:facet
...
The
createResource method above expects the resource name and library name to access the metadata of a composite component. The library name is known. Assume the composite components are placed in a JAR file under the folder
/META-INF/resources/com/foo That means, the library name is
com/foo. The problem is to get names of all resources below the
com/foo. Resources are XHTML files as composite components. For instance, for this structure
/META-INF/resources/com/foo/mycomponent1.xhtml
/META-INF/resources/com/foo/mycomponent2.xhtml
the resource names would be
mycomponent1.xhtml and
mycomponent1.xhtml. Without file extensions, they are the same as tag names of composite components. Is there a simple and reliable way to read these names from a WAR archive? Yes, sure, there is a simple method to read resources located in one Java archive from another Java archive. JBoss has
Virtual File System (VFS). A good documentation can be found
here. Please don't confuse it with
Apache Commons VFS. These are different things with perhaps the same goal. In JBoss 6 / 7 application servers, if we read JAR files from the classpath, URLs start with
vfs (meaning virtual file system) and not with
file as usually. So that JBoss VFS is very handy here because it is exactly for
vfs handling. The implemented approach is also working with all other application servers. I tested successful:
- JBoss 6 / 7
- Jetty 8
- GlassFish 3
- Tomcat 7
- WebLogic 12
The next method shows the approach. To get an URL for the interested JAR file, we need to know any Java class in this JAR file. This can be a marker interface or any other interface, abstract class as well. Obtained
Class object can be passed into the method along with the library name.
public void extractMetadata(Class clazz, String libraryName) throws IOException, URISyntaxException {
VirtualFile virtualFile;
Closeable handle = null;
URL url = clazz.getProtectionDomain().getCodeSource().getLocation();
String protocol = url.getProtocol();
if ("vfs".equals(protocol)) {
URLConnection conn = url.openConnection();
virtualFile = (VirtualFile) conn.getContent();
} else if ("file".equals(protocol)) {
virtualFile = VFS.getChild(url.toURI());
File archiveFile = virtualFile.getPhysicalFile();
TempFileProvider provider = TempFileProvider.create("tmp", Executors.newScheduledThreadPool(2));
handle = VFS.mountZip(archiveFile, virtualFile, provider);
} else {
throw new UnsupportedOperationException("Protocol " + protocol + " is not supported");
}
List<VirtualFile> files = virtualFile.getChild("/META-INF/resources/" + libraryName).getChildren();
List<String> resourceNames = new ArrayList<String>(files.size());
for (VirtualFile ccFile : files) {
resourceNames.add(ccFile.getName());
}
if (handle != null) {
handle.close();
}
FacesContext fc = FacesContext.getCurrentInstance();
ViewDeclarationLanguage vdl = fc.getApplication().getViewHandler().getViewDeclarationLanguage(fc,
"/views/home.xhtml");
for (String resourceName : resourceNames) {
Resource ccResource = fc.getApplication().getResourceHandler().createResource(resourceName, libraryName);
BeanInfo metadata = vdl.getComponentMetadata(fc, ccResource);
// extract metadata
...
}
}
As I said, supporting of two URL protocols,
vfs and
file, was enough for me to get it working on all modern app. servers. The main goal of this method is collecting
VirtualFile
instances below a certain folder.
VirtualFile allows to get other infos such as size, name, path, whether the object is a file or directory, etc. It allows to get
children und provides many other traversal operations. For the
file protocol, we have to mount the archive file in the Virtual File System (like in UNIX). Once mounted, the archive structure is accessible like a normal file system. The call
virtualFile.getChild("/META-INF/resources/" + libraryName).getChildren() returns
all children (
VirtualFile instances) below our
/META-INF/resources/com/foo/. We iterate through and collect resource (file) names.
Hi Oleg, I bought the EBook PRimeFaces CookBook today but it not coming with capters 11 and 12
ReplyDeleteChapter 11, Writing Custom Components
Chapter 12, PrimeFaces Extensions in Action
What happens to that chapters??? Sorry if i wrote here i dont know how contact you. Thanks!
Hi Sebastian. First, thanks for buying the book.
ReplyDeleteThe Packt Publishing told us that the current book's size has exceeded the planned size and they offer two last chapters as bonus for free downloading. I think you can download the last two chapters as PDFs from the book's page, somewhere here http://www.packtpub.com/primefaces-leading-jsf-component-suite-cookbook/book
I think the book should contain this information too. If not, let me know, I will ask Packt.
Yes i read the table of contents of the book and they put two links to download the chapters, but the links are not available yet. I send an email to them. Thanks for the anwser!
ReplyDeleteThanks for this post. I would like to add that I had to use the following dependency:
ReplyDelete<dependency>
<groupId>org.jboss</groupId>
<artifactId>jboss-vfs</artifactId>
<version>3.2.2.Final</version>
</dependency>