tag:blogger.com,1999:blog-70374983898337261242024-03-13T14:07:16.070+01:00Developing in JavaThis blog is about software development in Java Enterprise Edition (Java EE 5 & 6) using the Seam framework.Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.comBlogger8125tag:blogger.com,1999:blog-7037498389833726124.post-38952650181289560952019-01-04T13:35:00.000+01:002019-01-04T13:35:24.153+01:00Groovy 3.0.0 with JDK 10 on Windows 7+<h2>
<span style="font-family: inherit;">How to run Groovy 3.0.0 with JDK 10 on Windows 7+?</span></h2>
<br />
Since the advent of the Java Module System, and the removal of the classes under the javax.xml.bind package from the JDK, running Groovy became problematic under Java versions 9+. Groovy 3.0.0-alpha-3 installed on Windows 7 and using JDK 10.0.2 cannot run out of the box, instead we get this error message, running hello.groovy (containing only the <span style="font-family: Courier New, Courier, monospace;"><b>print "Hello World"</b></span> line)<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://3.bp.blogspot.com/-tHHYFMTt2AI/XC9PflhQ4QI/AAAAAAAAcPw/9_aGMUhE040Fobszpih5ApYQClBCMsWJgCLcBGAs/s1600/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A15s.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img alt="λ groovy hello WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/C:/bin/Groovy/GROOVY~1.0/lib/groovy-3.0.0-alpha-3.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int) WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1 WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release Caught: java.lang.NoClassDefFoundError: Unable to load class groovy.xml.jaxb.JaxbGroovyMethods due to missing dependency javax/xml/bind/JAXB Context java.lang.NoClassDefFoundError: Unable to load class groovy.xml.jaxb.JaxbGroovyMethods due to missing dependency javax/xml/bind/JAXBContext at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)" border="0" data-original-height="183" data-original-width="1183" height="60" src="https://3.bp.blogspot.com/-tHHYFMTt2AI/XC9PflhQ4QI/AAAAAAAAcPw/9_aGMUhE040Fobszpih5ApYQClBCMsWJgCLcBGAs/s400/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A15s.JPG" title="" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Running a groovy script results in error.</td></tr>
</tbody></table>
<br />
Also groovy console can't run scripts:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://4.bp.blogspot.com/-YHW9JmFQ4Cg/XC9OFwFnXyI/AAAAAAAAcPk/J2FZHFznPaYiaNfILNAzahpWYi-rS0L4QCLcBGAs/s1600/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A13s.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img alt="groovy> print "Hello World" Exception thrown java.lang.NoClassDefFoundError: Unable to load class groovy.xml.jaxb.JaxbGroovyMethods due to missing dependency javax/xml/bind/Marshaller at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)" border="0" data-original-height="208" data-original-width="979" height="84" src="https://4.bp.blogspot.com/-YHW9JmFQ4Cg/XC9OFwFnXyI/AAAAAAAAcPk/J2FZHFznPaYiaNfILNAzahpWYi-rS0L4QCLcBGAs/s400/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A13s.JPG" title="Groovy Console can't run scripts" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Groovy Console can't run scripts</td></tr>
</tbody></table>
<br />
<br />
The solution is easy, setting the <span style="font-family: Courier New, Courier, monospace;"><b>JAVA_TOOL_OPTIONS</b></span> environment variable to<br />
<b><span style="font-family: Courier New, Courier, monospace;">--add-modules=java.xml.bind</span></b> will solve the problem. (System Properties dialog/ Advanced tab / Environment Variables button / Environment variables dialog / System variables list / New ... )<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://3.bp.blogspot.com/-msC66xyzkKs/XC9KMwwJSfI/AAAAAAAAcPE/hZ3CQfLzNBgO6eaEZ_oUocSthz7KLDq6QCLcBGAs/s1600/JAVA_TOOL_OPTIONS.PNG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img alt="JAVA_TOOL_OPTIONS --add-modules=javax.xml.bind" border="0" data-original-height="183" data-original-width="379" src="https://3.bp.blogspot.com/-msC66xyzkKs/XC9KMwwJSfI/AAAAAAAAcPE/hZ3CQfLzNBgO6eaEZ_oUocSthz7KLDq6QCLcBGAs/s1600/JAVA_TOOL_OPTIONS.PNG" title="Setting the system property JAVA_TOOL_OPTIONS to --add-modules=javax.xml.bind" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Setting the system property JAVA_TOOL_OPTIONS to --add-modules=javax.xml.bind</td></tr>
</tbody></table>
<br />Starting a console after this setting and running groovy scripts still gives a lot of warnings, but the scripts are run (note the "Hello World" at the bottom):<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://3.bp.blogspot.com/-murPGipDsL4/XC9LsyqEKnI/AAAAAAAAcPQ/nRMB8SDN-xkOt55ekjT7M9Q82rn6Dv5pwCLcBGAs/s1600/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A1s.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img alt="λ groovy hello Picked up JAVA_TOOL_OPTIONS: --add-modules=java.xml.bind WARNING: An illegal reflective access operation has occurred WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/C:/bin/Groovy/GROOVY~1.0/lib/groovy-3.0.0-alpha-3.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int) WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1 WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations WARNING: All illegal access operations will be denied in a future release Hello World" border="0" data-original-height="162" data-original-width="818" height="78" src="https://3.bp.blogspot.com/-murPGipDsL4/XC9LsyqEKnI/AAAAAAAAcPQ/nRMB8SDN-xkOt55ekjT7M9Q82rn6Dv5pwCLcBGAs/s400/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A1s.JPG" title="The output of running the hello.groovy script after setting the JAVA_TOOL_OPTIONS environment variable" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The output of running the hello.groovy script after setting the JAVA_TOOL_OPTIONS environment variable</td></tr>
</tbody></table>
<br />
The Groovy Console is also working properly:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-s-y5lDEoTSE/XC9MaC2_JxI/AAAAAAAAcPY/6rGzqKrtkjMumcyU86ua0p6QmvYqGJFIQCLcBGAs/s1600/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A12s.JPG" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img alt="groovy> print "Hello World" Hello World" border="0" data-original-height="201" data-original-width="267" src="https://1.bp.blogspot.com/-s-y5lDEoTSE/XC9MaC2_JxI/AAAAAAAAcPY/6rGzqKrtkjMumcyU86ua0p6QmvYqGJFIQCLcBGAs/s1600/K%25C3%25A9pkiv%25C3%25A1g%25C3%25A12s.JPG" title="Output of the Groovy Console after setting the JAVA_TOOL_OPTIONS environment variable" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Output of the Groovy Console after setting the JAVA_TOOL_OPTIONS environment variable</td></tr>
</tbody></table>
<br />
<br />Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com0tag:blogger.com,1999:blog-7037498389833726124.post-54918887108555995542012-04-24T12:41:00.000+02:002012-04-24T12:41:00.773+02:00Saving on the fly generated PDF to file or database (Seam 2.2 application in JBoss AS 5.1)With Seam, you can create a pdf file on the fly using only xhtml pages, but if you
want to get the PDF file’s binary data, that is much harder task. The basic
problem, that this function was not designed to provide the binary data, rather
than just generate the PDF file using JSF custom tags. You can read the
solution in a lot of blogs and forums: just create a mock FacesContext, use
that for render the page, then extract the binary data. I have tried the solutions
described in various places, but for JBoss AS 5.1 and Seam 2.2.0 GA they didn’t
worked. The main problem was to create the mock <i>FacesContext</i>, because you have to have the <i>javax.faces.application.Application</i> and <i>javax.faces.render.RenderKitFactory</i> instances which I had no luck
to get at the time of the creation of the mock FacesContext. But by
implementing a servlet context listener, I was able to capture both values. You need to register the DocumentServlet and the custom servlet context listener into the web.xml, create the /simple.xhtml page (see later) and put the xhtml code in paragraph #3 into an other page (the menu, for example). The other classes should be put into the ejb module of your application, into the org.example.pdf package. Let's see the steps in detail:<br />
<br />
<h2>
1.Servlet Context Listener and Document Servlet registration in web.xml</h2>
In order the PDF generation on the fly to be able to work,
you must add the followings servlet mapping to the web.xml. The servlet context
listener is used to capture the javax.faces.application.Application and
javax.faces.render.RenderKitFactory instances when the servlet context
initialized.<br />
<pre class="brush:xml"><servlet>
<servlet-name>Document Store Servlet</servlet-name>
<servlet-class>org.jboss.seam.document.DocumentStoreServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Document Store Servlet</servlet-name>
<url-pattern>*.pdf</url-pattern>
</servlet-mapping>
<listener>
<listener-class>
org.example.pdf.MockServletContextListener
</listener-class>
</listener>
</pre>
<br />
<h2>
2. Page that generates PDF</h2>
You must create a an xhtml page ("/simple.xhtml"), to test the export
function:<br />
<pre class="brush:xml"><p:document xmlns:p="http://jboss.com/products/seam/pdf">
<p:chapter>Hello PDF!</p:chapter>
</p:document>
</pre>
<br />
<h2>
3. Testing xhtml code</h2>
In order to test the PDF export function, the following link
could be placed somewhere (e.g,: into the menu). This will call the
mockPDFFileSaver’s savePDF(fileName, viewName) method, which simply generated
the pdf then saves the results into the given file. This example will save the pdf into "d:\simple.pdf" <br />
<pre class="brush:xml"><s:link
includePageParams="false"
propagation="none"
value="Simple PDF test"
action="#{mockPDFFileSaver.
savePDF('d:\\simple.pdf','/simple.xhtml')}">
</s:link>
</pre>
<h2>
4.Servlet Context Listener</h2>
The listener can capture the „Application” and „Render Kit
Factory” instances. It then saves into the Seam component „factoryData”.
Lifecycle.beginCall() initializes Seam.
<br />
<pre class="brush:java">package org.example.pdf;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.render.RenderKitFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.jboss.seam.Component;
import org.jboss.seam.contexts.Lifecycle;
/**
* ServletContextListener implementation to capture the JSF Application and
* renderKitFactory. Should be registered into web.xml to work
*
* @author anemeth
*
*/
public class MockServletContextListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
Lifecycle.beginCall();
Application application = ((ApplicationFactory) FactoryFinder
.getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication();
RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
FactoryData factoryData = (FactoryData) Component.getInstance("factoryData", true);
factoryData.setApplication(application);
factoryData.setRenderKitFactory(renderKitFactory);
}
}
</pre>
<h2>
5. Mock Servlet Context</h2>
The MockServletContext from Seam
2.2 was modified in order to work under JBoss AS 5.1<br />
<pre class="brush:java">package org.example.pdf;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.ApplicationFactory;
import javax.faces.render.RenderKitFactory;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.jboss.seam.Component;
import org.jboss.seam.contexts.Lifecycle;
/**
* ServletContextListener implementation to capture the JSF Application and
* renderKitFactory. Should be registered into web.xml to work
*
* @author anemeth
*
*/
public class MockServletContextListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
}
public void contextInitialized(ServletContextEvent event) {
Lifecycle.beginCall();
Application application = ((ApplicationFactory) FactoryFinder
.getFactory(FactoryFinder.APPLICATION_FACTORY)).getApplication();
RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder
.getFactory(FactoryFinder.RENDER_KIT_FACTORY);
FactoryData factoryData = (FactoryData) Component.getInstance("factoryData", true);
factoryData.setApplication(application);
factoryData.setRenderKitFactory(renderKitFactory);
}
}
</pre>
<h2>
6. FactoryData</h2>
This is a simple Seam component for holding the
„javax.faces.application.Application” and „javax.faces.render.RenderKitFactory”<br />
<pre class="brush:java">package org.example.pdf;
import javax.faces.application.Application;
import javax.faces.render.RenderKitFactory;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
/**
* Class for holding the JSF "Application" instance and the renderKitFactory.
* @author anemeth
*
*/
@Name("factoryData")
@Scope(ScopeType.APPLICATION)
public class FactoryData {
private Application application;
private RenderKitFactory renderKitFactory;
/**
* @return the application
*/
public Application getApplication() {
return this.application;
}
/**
* @return the renderKitFactory
*/
public RenderKitFactory getRenderKitFactory() {
return this.renderKitFactory;
}
/**
* @param application
* the application to set
*/
public void setApplication(Application application) {
this.application = application;
}
/**
* @param renderKitFactory
* the renderKitFactory to set
*/
public void setRenderKitFactory(RenderKitFactory renderKitFactory) {
this.renderKitFactory = renderKitFactory;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("FactoryData [application=");
builder.append(this.application);
builder.append(", renderKitFactory=");
builder.append(this.renderKitFactory);
builder.append("]");
return builder.toString();
}
}
</pre>
<h2>
7. MockPDFFileSaver</h2>
This is a simple Seam component
to demonstrate the PDF export function<br />
<pre class="brush:java">package org.example.pdf;
import java.io.FileOutputStream;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
@Name("mockPDFFileSaver")
public class MockPDFFileSaver {
@In(create = true)
PdfExporter pdfExporter;
/**
* Saves the PDF generated by the pageName to the file denoted by the
* fileName
*
* @param fileName
* the file name
* @param pageName
* the page name
*/
public void savePDF(String fileName, String pageName) {
byte[] pdfBytes = pdfExporter.pdfExport(pageName);
try {
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(pdfBytes);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
</pre>
<h2>
8. PDFExporter</h2>
The pdf export functionality is implemented in this Seam
component. This works by rendering the given view into a mock context. The
DocumentStore then contains the rendered pdf. The real question is what the id
of the generated pdf. The DocumentStrore is a Conversation scoped component so
basically the Id will be started from 1 when a new conversation starts. But for
sure, the Id can be queried, in the case more document is generated per
conversation. <br />
<pre class="brush:java">package org.example.pdf;
import java.io.ByteArrayOutputStream;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.document.DocumentData;
import org.jboss.seam.document.DocumentStore;
import org.jboss.seam.faces.Renderer;
@Name("pdfExporter")
public class PdfExporter {
/**
* returns the byte array of the PDF file generated using the page xhtml
* @param page the page name of the xhtml for the pdf
* @return the byte array containing the PDF file data
*/
public byte[] pdfExport(String page) {
byte[] pdfBytes = null;
EmptyFacesContext emptyFacesContext = null;
try {
emptyFacesContext = new EmptyFacesContext();
try {
Renderer renderer = Renderer.instance();
renderer.render(page);
DocumentStore store = DocumentStore.instance();
String nextId = store.newId();
long docId = Long.parseLong(nextId) - 1;
DocumentData data = store.getDocumentData("" + docId);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
data.writeDataToStream(baos);
pdfBytes = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
emptyFacesContext.restore();
}
return pdfBytes;
}
}
</pre>Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com1Budapest47.4984056 19.040757847.3267646 18.7249008 47.6700466 19.356614800000003tag:blogger.com,1999:blog-7037498389833726124.post-62161958778571788012012-04-06T14:12:00.001+02:002018-07-16T01:41:00.522+02:00Localization Tool for Seam 2 applications<h2>Internationalization and Localization in Seam applications</h2>Seam uses the JSF's resource bundle approach to localization. Resource bundles are property files usually in the WEB-INF/classes folder. JSF can determine the locale using the followings (from Seam documentation):<br />
<br />
<ul style="background-color: white; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; padding-bottom: 0px; padding-top: 0px;" type="disc"><li>If there is a locale associated with the HTTP request (the browser locale), and that locale is in the list of supported locales from <tt class="literal" style="font-family: 'Courier New', Courier, monospace; font-size: 14px;">faces-config.xml</tt>, use that locale for the rest of the session.</li>
<li>Otherwise, if a default locale was specified in the <tt class="literal" style="font-family: 'Courier New', Courier, monospace; font-size: 14px;">faces-config.xml</tt>, use that locale for the rest of the session.</li>
<li>Otherwise, use the default locale of the server.</li>
</ul><br />
<br />
To change the locale manually you should place some similar code into your application (e.g.: in the menu bar):<br />
<pre class="brush:xml"><h:selectOneMenu value="#{localeSelector.language}"
onchange="submit()">
<f:selectItem itemLabel="#{messages.menu_language_en}"
itemValue="en"/>
<f:selectItem itemLabel="#{messages.menu_language_de}"
itemValue="de"/>
<f:selectItem itemLabel="#{messages.menu_language_hu}"
itemValue="hu"/>
</h:selectOneMenu>
</pre><br />
In the above example three languages used: english (en), german (de) and hungarian (hu). The labels for the selection list are also coming from the resource bundle, so they are displayed in the selected language. In the english resource bundle this looks like this way:<br />
<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>menu_language_de=German</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>menu_language_en=English</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>menu_language_hu=Hungarian</b></span><br />
<br />
while in the german resource bundle:<br />
<br />
<br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>menu_language_de=Deutsch</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>menu_language_en=English</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace; font-size: x-small;"><b>menu_language_hu=Ungarisch</b></span><br />
<br />
Seam will use the resource bundle of choice.<br />
<br />
<span style="font-size: large;">Enabling the localization in a Seam application</span><br />
<span style="font-size: large;"><br />
</span><br />
Modify the WEB-INF/components.xml to include the following:<br />
<br />
<br />
<pre class="brush:xml"><!-- Remember the locale selected -->
<international:locale-selector cookie-enabled="true"/>
</pre><br />
where international is defined as<br />
<br />
xmlns:<b>international</b>="<b>http://jboss.com/products/seam/international</b>"<br />
<br />
For more information on internationalization please see Seam documentation: <a href="http://docs.jboss.com/seam/latest/reference/en-US/html/i18n.html" target="_blank">http://docs.jboss.com/seam/latest/reference/en-US/html/i18n.html</a><br />
<br />
<br />
<span style="font-size: large;">Pratical Localization</span><br />
<br />
Let's suppose you have a Seam application developed in English language, and you have to add new languages. There could be String values in the Java code and in the xhtml pages. This tool deals only with the String literals in the xhtml pages. Every String which contains at least one letter (outside the EL expressions) are put into the resource bundle property files "messages_XX.properties". At the same time the tool changes the String literals in the xhtml pages to EL expression.<br />
<br />
For example a h:commandButton without localization looks this way:<br />
<br />
<pre class="brush:xml"><h:commandButton action="#{providerHome.persist}"
id="save" rendered="#{!providerHome.managed}"
value="Create"/>
</pre>After the tool processed the xhtml code, the above excrept looks like this:<br />
<br />
<pre class="brush:xml"><h:commandButton action="#{providerHome.persist}"
id="save" rendered="#{!providerHome.managed}"
value="#{messages.provider_commandButton_Create}"
/>
</pre>You can see that the key for the "Create" String literal from the command button is:<br />
<b>provider_commandButton_Create</b>. The first part "provider" is the name of the xhtml file without extension. The second part is the name of the JSF tag without namespace ("<b>commandButton</b>"), the last part is the String literal converted into a format suitable for being a key ("<b>Create</b>"). The non letter characters are converted into underscore. (Minus sign is not usable, because it is evaluated by the EL engine). <br />
<br />
The provider_commandButton_Create is added to the messages_en.properties: <br />
<pre><span style="font-family: 'Courier New', Courier, monospace;"><b>provider_commandButton_Create=Create</b></span></pre>and for example the messages_de properties file:<br />
<pre><span style="font-family: 'Courier New', Courier, monospace;"><b>provider_commandButton_Create=Erstellen</b></span></pre>This kind of naming convention is applied to String literals inside value attribute of JSF tags. When the literal is a text node in the xhtml file, the middle part will be "TEXT":<br />
<br />
<pre class="brush:xml"><s:decorate id="nameField" template="layout/edit.xhtml">
<ui:define name="label">#{messages.provider_TEXT_Name}
</ui:define>
<h:inputtext id="name" required="true" value="#{providerHome.instance.name}">
</h:inputtext></s:decorate>
</pre><h2><span style="font-size: large;">Localizing date/time formats</span></h2><span style="background-color: white; font-family: Times, 'Times New Roman', serif;">The localization would not be complete without date formats. For example in hungary the following format is used: 2012-04-05 12:33:17 , while the traditional german format would be: </span><span style="background-color: white; font-family: Times, 'Times New Roman', serif;">05-04-2012 12:33:17. The tool inserts the following key to the resource bundle property files: <b>date_time_format</b>. This will be used in </span><br />
<br />
<pre class="brush:xml"><h:outputtext style="align: center;" value="#{user.registrationDate}">
<f:convertdatetime pattern="#{messages.date_time_format}" timezone="CET">
</f:convertdatetime></h:outputtext>
</pre><span style="font-size: large;"><b><br />
</b></span><br />
<span style="font-size: large;"><b>Localization Workflow</b></span><br />
<br />
<ol><li>Develop your application in your preferred language (e.g,: english).</li>
<li>Create message_XX.properties files for the languages needed for localization, by copying the messages_en.properties file's content under new names, eg.: messages_de.properties. If you previously translated Seam's messages and other JSF related messages into the preferred language, you can copy the already existing properties files from other projects.</li>
<li>Make a backup. This is important since I18nGen tool overwrites all xhtml files and all messages_XX.properties files it founds.</li>
<li>Run I18nGen tool. This will populate the messages_XX.properties files with string literals from the xhtml files with the languge used for development.</li>
<li>Check the properties files. The tool will use the "old" values from the property file. There are three categories listed in the property file:</li>
<ol><li>new property: The property was not found in the original property file, but was on the xhtml page</li>
<li>old but used property: The property was in the original property file but it is also coming from the xhtml page. In this case the old value is not overwritten. If there is a conflict between the new and the old value, this will be listed in the beginning of the property file as a commented line.</li>
<li>old and not used: This means that the property was in the original property file, but not found in the xhtml pages. This could mean the property is for Seam's or JSF messages, but could also mean that you have copied the property from an other project and in your current project that property is not used, so could delete it. It is important to note that if you delete a property, the tool won't place it again in the property file, because in the xhtml page, the String literal was converted into an EL expression, and the tool skips EL expressions.</li>
</ol><li>When you prepared the property file for translation, you should decide if you translate the messages yourself or you pass them to a translator. In the later case, things are a bit complicated because the property file should be encoded in ISO-8859-1 (Latin-1 charset), and characters not in this character set should be expressed as unicode escape sequences, for example the greek letter <span style="background-color: white; text-align: center;">Π (pi) should be expressed as \u</span><span style="background-color: white; text-align: center;">03A0, but an usual translator can hardly find every non Latin-1 characrers unicode escape codes, nor he or she can use a developer tool, such as Eclipse's property editor which is capable of handling the character encoding problem. The simplest solution is to send the file as Word document, with the note that existing lines should not be broken and the EL expressions should not be changed either.</span></li>
<li><span style="background-color: white; text-align: center;">The translator does his or her job, then sends back the translated messages in a Word document.</span></li>
<li><span style="background-color: white; text-align: center;">You should convert back the results into the property file. This can be done by opening the property file in Eclipse, then copy-paste the translated content into the opened property file. Eclipse will handle the unicode escaping.</span></li>
<li><span style="background-color: white; text-align: center;">Build your application and deploy it.</span></li>
<li style="text-align: left;">You can test the translation by going to every page in your application.</li>
<li style="text-align: left;">If something is not translated or the meaining is not exactly what you think, you can change the property files. When new pages are added to the application, you can re-run the tool, which will add new properties to the resource bundle property files, and you can restart the translation process.</li>
</ol><span style="font-size: large;"><b><a name="i18ngen">I18nGen tool</a></b></span><br />
<span style="font-size: large;"><br />
</span><br />
<div>You can download the tool & source code in the form of an executable jar file from here: <a href="https://docs.google.com/open?id=0BzNHO6ChwJDbQnNqRmRHOTFTTi12TzlJOFR5OGYwQQ" target="_blank">I18nGen.jar</a></div>
<div>Github: <a href="https://github.com/nemethakos/i18ngen">https://github.com/nemethakos/i18ngen</a></div><br>
<h3>Usage</h3><div>From command line the syntax is the following:</div><div><span style="font-family: 'Courier New', Courier, monospace;"><b><br />
</b></span><br />
<span style="font-family: 'Courier New', Courier, monospace;"><b>java -jar I18nGen.jar <Full path for the directory for xhtml files> <Full path for the directory for the messages_XX.properties></b></span></div><div><br />
Example:<br />
<b><span style="font-family: 'Courier New', Courier, monospace;"><br />
</span></b><br />
<b><span style="font-family: 'Courier New', Courier, monospace;">java -jar c:\tools\I18nGen.jar c:\workspace\myprj\WebContent c:\workspace\myprj\src</span></b><br />
<b><span style="font-family: 'Courier New', Courier, monospace;"><br />
</span></b><br />
<span style="font-family: inherit;">The tool will recursively scan all files with ".xhtml" extension and collects the String literals. The xhtml format is basically XML, so in terms of XML, the tool collects string literals from text nodes and from attributes, where the attribute name is "value", except from the graphicImage tag. The tool excludes literals which has no letters on it (so the Strings which contains only whitespaces and EL expressions are missed out).</span><br />
<span style="font-family: inherit;"><br />
</span><br />
<span style="font-family: inherit;">The attribute value for the pattern attribute of the convertDateTime tag will be set to </span><br />
<span style="font-family: inherit;">"</span>#{messages.date_time_format}<span style="font-family: inherit;">" and the property date_time_format with the value of "</span>yyyy-MM-dd HH:mm:ss z<span style="font-family: inherit;">" will be added.</span><br />
<span style="font-family: inherit;"><br />
</span><br />
<span style="font-family: inherit;">The messages_XX.properties files will be rewritten in the following form. The first part is the header with a date/time. Then property "collision" information follows. The next part is the section where are properties which is used in the xhtml files but were also in the previous version of the property file. New properties are such properties which are not found in the previous version of the property file, only in the xhtml pages. The last section is for properties which were found in the previous version of the property file, but not in the xhtml pages, so you can think about deleting them (if their names are not begin with "javax.", "validator" or "org.jboss.seam", because these are JSF or Seam related messages)</span><br />
<br />
<pre>##############################################
# Generated on Fri Apr 06 13:57:39 CEST 2012
##############################################
# Comments:
# Property: "configuration_TEXT_Keystore_Password" already exist with value: "3. Keystore Password", the new value would have been: "1. Keystore Password"
# Property: "configuration_TEXT_Certificate_Alias" already exist with value: "3. Certificate Alias", the new value would have been: "1. Certificate Alias"
# Property: "configuration_TEXT_Keystore_File" already exist with value: "3. Keystore File", the new value would have been: "2. Keystore File"
# Property: "configuration_TEXT_Keystore_Password" already exist with value: "3. Keystore Password", the new value would have been: "2. Keystore Password"
# Property: "configuration_TEXT_Certificate_Alias" already exist with value: "3. Certificate Alias", the new value would have been: "2. Certificate Alias"
###############################################
# OLD but USED properties
###############################################
# error
error_TEXT_Error=Error
error_TEXT_Something_bad_happened_=Something bad happened :-(
error_param_false=false
############################################
# NEW properties
############################################
# resource
resource_TEXT_Content_Type=Content-Type
resource_TEXT_Description=Description
resource_TEXT_Download=Download
resource_commandButton_Create=Create
resource_commandButton_Delete=Delete
resource_commandButton_Update=Update
##############################################
# OLD and UNUSED properties (some entries may
# be removable, except JSF and seam properties)
##############################################
# validator
validator.assertFalse=ellen\u0151rz\u00E9s sikertelen
validator.assertTrue=ellen\u0151rz\u00E9s sikertelen
validator.email=j\u00F3l form\u00E1zott email c\u00EDmnek kell lennie
validator.future=j\u00F6v\u0151beli d\u00E1tumnak kell lennie
validator.length=a hossznak {min} \u00E9s {max} k\u00F6z\u00F6tt kell lennie
validator.max=kisebb vagy egyenl\u0151nek kell lennie a k\u00F6vetkez\u0151n\u00E9l: {value}
validator.min=nagyobbnak vagy egyenl\u0151nek kell lennie a k\u00F6vetkez\u0151n\u00E9l: {value}
validator.notNull=may not be null
validator.past=m\u00FAltb\u00E9li d\u00E1tumnak kell lennie
validator.pattern=meg kell felelnie a k\u00F6vetkez\u0151nek: "{regex}"
validator.range=a k\u00F6vetkez\u0151k k\u00F6z\u00E9 kell esnie: {min} - {max}
validator.size=a m\u00E9retnek a k\u00F6vetkez\u0151k k\u00F6z\u00F6tt kell lennie: {min} - {max}
</pre><br />
</div>Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com3tag:blogger.com,1999:blog-7037498389833726124.post-33587149010264174112012-03-20T16:12:00.000+01:002012-03-20T16:12:53.213+01:00JSF 2 Navigation Pitfalls and Solutions<span style="font-family: Georgia, 'Times New Roman', serif;">In this article I will present some cases where JSF 2 navigation will not work; in order the reader can avoid them.</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;">JSF navigation works by first calling the action method (if defined), and from the outcome of the action, the JSF implementation decides which view to display. If there is no action method, a view id can be used directly (e.g.: "landingPage", the file name without extension).</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>1. Missing <h:form> tag</b></span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>symptom</b>: By pressing the button (or clicking the link), nothing happens. If there is a <h:messages> tag in the view, the following "cryptic" message is displayed: </span><br />
<i><span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span></i><br />
<blockquote class="tr_bq"><span style="font-family: Times, 'Times New Roman', serif;"><i>The form component needs to have a UIForm in its ancestry. Suggestion: enclose the necessary components within <h:form></i></span></blockquote><i><span style="color: red; font-family: Georgia, 'Times New Roman', serif;"><br />
</span></i><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>solution</b>: Put the <h:commandButton> or <h:commandLink> inside a <h:form></span><br />
<i><span style="font-family: Georgia, 'Times New Roman', serif;">If the <h:commandButton> or <h:commandLink> component is outside of the <h:form>, or there is no <h:form> in the view, no form submit will be generated, so no navigation will occur (and the action method is also won't be called)</span></i><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>2. Missing EL brackets (#{})</b></span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b><br />
</b></span><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-mHXNnSoFB20/T2iZPHy5BhI/AAAAAAAAOXY/YqWfaOG3KT8/s1600/ScreenHunter_05+Mar.+20+15.47.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-mHXNnSoFB20/T2iZPHy5BhI/AAAAAAAAOXY/YqWfaOG3KT8/s1600/ScreenHunter_05+Mar.+20+15.47.jpg" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-family: Georgia, 'Times New Roman', serif;">Eclipse with JBoss Tools displays the action value with <br />
underline and warning icon, if the EL brackets are missing</span></td></tr>
</tbody></table><span style="font-family: Georgia, 'Times New Roman', serif;"><b><br />
</b></span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>symptom</b>: the page is redisplayed, but the action method is not called. If there is a <h:messages> tag in the view, the following error message appears: </span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<blockquote class="tr_bq"><i><span style="font-family: inherit;">Unable to find matching navigation case with from-view-id '<b>/navigation/page1.xhtml</b>' for action '<b>navigation.navigateAction1()</b>' with outcome '<b>navigation.navigateAction1()</b>'</span></i></blockquote><span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;">In this case, the button was defined as: </span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<pre class="brush:java"><h:commandButton
action="navigation.navigateAction1()"
value="Missing EL brackets"
/>
</pre><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>solution</b>: Put the action method reference between EL brackets.</span><br />
<b><span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span></b><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>note: </b>In this case, the JSF implementation thinks, that what we defined as an action method, is a view id, but it doesn't find a file called "navigation.navigateAction1().xhtml".</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>3. "void" action method (navigation rule is defined in faces-config.xml)</b></span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>symptom</b>: The action method is called; page is redisplayed, but no navigation occur, no error message is displayed.</span><br />
<b><span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span></b><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>solution</b>: change the action method to return anything (e.g.: a String)</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;">This method:</span><br />
<pre class="brush:java">// doesn't navigate because of void return value
public void doNotNavigateAction1() {
logger.info("doNotNavigateAction1");
}</pre><span style="font-family: Georgia, 'Times New Roman', serif;">should be rewritten to return anything to enable navigation: </span><br />
<br />
<pre class="brush:java">public String navigateAction1() {
logger.info("navigateAction1");
return "success";
}</pre><br />
<br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>4. action method returns null value</b></span><br />
<br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>symptom</b>: The action method is called; page is redisplayed, but no navigation occur, no error message is displayed.</span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b><br />
</b></span><br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><b>solution</b>: change the action method to return anything (e.g.: a String value), but not null.</span><br />
<span style="font-weight: 800;"><br />
</span> <span style="font-family: Georgia, 'Times New Roman', serif; font-weight: bold;">note</span><span style="font-family: Georgia, 'Times New Roman', serif;">: This is by design, if the action method returns null, this means, that the previous page should be redisplayed.</span><br />
<br />
<span style="font-family: Georgia, 'Times New Roman', serif;"><br />
</span>Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com0tag:blogger.com,1999:blog-7037498389833726124.post-51060811171998208042012-03-19T14:09:00.000+01:002012-03-19T14:09:48.285+01:00Installing new instance of JBoss 7.1.1 with Oracle 10+ database driver and datasource<span style="font-family: Arial, Helvetica, sans-serif;">Installing new instance of JBoss 7.1.1 with Oracle 10+ database driver and datasource</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;">While installing a new instance of JBoss AS 5.1 GA consisted of unzipping the zip file into a directory and copying the jdbc driver into the server's lib directory, installing JBoss AS 7.1.1 requires more work. The following steps describes how to install a new instace of JBoss AS 7.1.1 (and 7.1.0), configure the Oracle JDBC driver and set a datasource.</span><br />
<ol><li><span style="background-color: white; font-family: Arial, Helvetica, sans-serif;"><span style="line-height: 18px;">Download the </span><b style="line-height: 18px;">JBoss AS 7.1.1</b><span style="line-height: 18px;"> from </span><u><span style="color: blue;">http://www.jboss.org/jbossas/downloads/</span></u></span></li>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="background-color: white;"> </span> </span>
<li><span style="background-color: white;"><span style="font-family: Arial, Helvetica, sans-serif;"><span style="line-height: 18px;">Unzip the archive file to a directory</span><span style="line-height: 18px;"> </span><i style="line-height: 18px;">(e.g.: D:\bin\jboss-as-7.1.1.Final)</i></span></span></li>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="background-color: white;">
<li><span style="line-height: 18px;">set</span><span style="line-height: 18px;"> </span><b style="line-height: 18px;">JBOSS_HOME</b><span style="line-height: 18px;"> </span><span style="line-height: 18px;">environmental variable to the unzipped directory</span><span style="line-height: 18px;"> </span><i style="line-height: 18px;">(e.g.: "D:\bin\jboss-as-7.1.1.Final")</i></li>
<li><span style="line-height: 18px;">Run</span><span style="line-height: 18px;"> </span><b style="line-height: 18px;"><JBOSS_HOME>/bin/add-user.bat</b><span style="line-height: 18px;"> </span><span style="line-height: 18px;">to create an administrator user to be able to log in into the administration interface</span></li>
<li><span style="line-height: 18px;">Configure Oracle driver & Datasource:</span></li>
</span> </span><ol><li><span style="font-family: Arial, Helvetica, sans-serif;"><span style="background-color: white;">Create directory: </span><b style="background-color: white;"><JBOSS_HOME>\modules\oracle\jdbc\main</b></span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;"><span style="background-color: white;">Download </span><b style="background-color: white;">ojdbc6.jar</b><span style="background-color: white;"> from Oracle (</span><a class="external" href="http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html" style="background-color: white; border-bottom-width: 1px; border-color: initial; border-image: initial; border-left-width: 0px; border-right-width: 0px; border-style: initial; border-top-width: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px; outline-color: initial; outline-style: none; outline-width: initial; padding-bottom: 0px; padding-left: 0px; padding-right: 0px; padding-top: 0px;" target="_blank"><span style="color: blue;">http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-112010-090769.html</span></a><span style="background-color: white;">). Note: ojdbc14.jar was not recognized by JBoss 7.1.0 Final.</span></span></li>
<li><span style="font-family: Arial, Helvetica, sans-serif;"><span style="background-color: white;">Copy </span><b style="background-color: white;">ojdbc6.jar</b><span style="background-color: white;"> into the </span><b style="background-color: white;"><JBOSS_HOME>\modules\oracle\jdbc\main</b></span></li>
<li><span style="background-color: white; font-family: Arial, Helvetica, sans-serif;">Create the file module.xml with the following content:</span></li>
<div><span style="font-family: Arial, Helvetica, sans-serif;"><span style="line-height: 18px;"> </span></span></div><span style="font-family: Arial, Helvetica, sans-serif;"><span style="background-color: white;"><pre class="brush:xml"><?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.0" name="oracle.jdbc"><resources><resource-root path="ojdbc6.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="javax.transaction.api"/>
</dependencies>
</module>
</pre></span> </span>
<li><span style="background-color: white; font-family: Arial, Helvetica, sans-serif;">Edit the <<b>datasource></b> entry in "</span><jboss_home style="background-color: white;"><span style="font-family: Arial, Helvetica, sans-serif;">\standalone\configuration\standalone.xml", by adding the followings. (Change the <i>_datasource_name_</i>, <i>_jdbc_conncection_url_</i>, <i>_schema_name_</i>, <i>_password_</i> strings to match to your environment. The datasource name will be referenced by the your application, e.g.: in the persistence.xml)</span></jboss_home></li>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="background-color: white;"> <jboss_home></jboss_home></span> </span><pre class="brush:xml"><subsystem xmlns="urn:jboss:domain:datasources:1.0">
<datasources>
...
<datasource
jndi-name="java:jboss/datasources/<i>_datasource_name_</i>"
pool-name="_datasource_name_"
enabled="true"
use-java-context="true">
<connection-url><i>_jdbc_connection_url_</i></connection-url>
<driver>oracle</driver>
<security>
<user-name><i>_schema_name_</i></user-name>
<password><i>_password_</i></password>
</security>
</datasource>
<drivers>
<driver name="oracle" module="oracle.jdbc"/>
...
</drivers>
</datasources>
</subsystem></pre><span style="background-color: white;"> </span></ol></ol>Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com0tag:blogger.com,1999:blog-7037498389833726124.post-10754856521183770992012-02-23T15:07:00.136+01:002012-02-28T16:15:00.848+01:00Building and Deploying the Seam 3 Booking example from Eclipse 3.7 to JBoss AS 7.1<link href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" rel="stylesheet" type="text/css"></link><br />
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js" type="text/javascript">
</script><br />
<script src="http://alexgorbatchev.com/pub/sh/current/scripts/shAutoloader.js" type="text/javascript">
</script><br />
This blog entry is about how to make the JBoss Seam 3 Booking Example to build and deploy under Eclipse 3.7 SR2 to JBoss AS 7.1 (under Windows).<br />
<br />
There are two maven plugins which could help: The <i>Maven Eclipse Plugin</i> and the <i>APT Plugin</i> from Codehouse.<br />
<br />
<span style="font-size: large;">Maven 3.0.4 and Dependency versions</span><br />
<br />
I got missing version information errors when upgraded to Maven 3.0.4, so I added the version information in every missing place (I have also removed the non JBoss 7 related profiles and testing related goals) . The modified project pom somehow caused the Eclipse JDT-APT plugin to stop working, so I have added a new builder for running annotation processors (see later). This is how the modified pom looks like:<br />
<pre class="brush:xml"><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<version>3.0.1</version>
<modelVersion>4.0.0</modelVersion>
<groupId>org.jboss.seam.examples.seam-booking</groupId>
<artifactId>seam-booking</artifactId>
<packaging>war</packaging>
<name>Seam Booking Example</name>
<description>The Seam booking example using the simplified Java EE 6 programming model and packaging structure (i.e., web archive)</description>
<url>http://seamframework.org/Seam3</url>
<properties>
<jpamodelgen.version>1.1.1.Final</jpamodelgen.version>
</properties>
<dependencies>
<!-- Annotation processor for generating typed loggers -->
<dependency>
<groupId>org.jboss.solder</groupId>
<artifactId>solder-tooling</artifactId>
<scope>compile</scope>
<version>3.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.solder</groupId>
<artifactId>solder-impl</artifactId>
<scope>runtime</scope>
<version>3.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${jpamodelgen.version}</version>
<scope>provided</scope>
<!-- Excluded Hibernate-provided JPA API because it's provided by the Java EE 6 dependencies -->
<exclusions>
<exclusion>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.jboss.seam.faces</groupId>
<artifactId>seam-faces-api</artifactId>
<version>3.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.seam.faces</groupId>
<artifactId>seam-faces</artifactId>
<version>3.1.0.Final</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.jboss.seam.international</groupId>
<artifactId>seam-international</artifactId>
<version>3.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.seam.security</groupId>
<artifactId>seam-security</artifactId>
<scope>compile</scope>
<version>3.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.seam.transaction</groupId>
<artifactId>seam-transaction</artifactId>
<scope>compile</scope>
<version>3.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.picketlink.idm</groupId>
<artifactId>picketlink-idm-core</artifactId>
<version>1.3.0.GA</version>
</dependency>
<dependency>
<groupId>com.ocpsoft</groupId>
<artifactId>ocpsoft-pretty-time</artifactId>
<version>1.0.7</version>
</dependency>
<dependency>
<groupId>com.ocpsoft</groupId>
<artifactId>prettyfaces-jsf2</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-digester3</artifactId>
<version>3.2</version>
<classifier>with-deps</classifier>
</dependency>
<!-- Bean Validation Implementation; provides portable constraints @NotEmpty, @Email and @Url -->
<!-- Hibernate Validator is the only JSR-303 implementation at the moment, so we can assume it's provided -->
<!-- TODO Move Hibernate Validator to app server specific sections -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.jboss.spec</groupId>
<artifactId>jboss-javaee-6.0</artifactId>
<type>pom</type>
<scope>provided</scope>
<version>1.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.jboss.shrinkwrap.resolver</groupId>
<artifactId>shrinkwrap-resolver-impl-maven</artifactId>
<scope>runtime</scope>
<version>1.1.0-alpha-2</version>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>knowledge-api</artifactId>
<version>5.4.0.Beta1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.servicemix.bundles</groupId>
<artifactId>org.apache.servicemix.bundles.drools</artifactId>
<version>5.1.1_1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>
<defaultGoal>package</defaultGoal>
<finalName>seam-booking</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.0-alpha-4</version>
</plugin>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalBuildcommands>
<!-- annoyingly creates a bin directory <buildCommand> <name>org.eclipse.wst.jsdt.core.javascriptValidator</name> </buildCommand> -->
<buildCommand>
<name>org.jboss.tools.common.verification.verifybuilder</name>
</buildCommand>
</additionalBuildcommands>
<additionalConfig>
<file>
<name>.settings/org.maven.ide.eclipse.prefs</name>
<content>eclipse.preferences.version=1
fullBuildGoals=process-test-resources
includeModules=false
resolveWorkspaceProjects=true
resourceFilterGoals=process-resources
resources\:testResources
skipCompilerPlugin=true
version=1</content>
</file>
</additionalConfig>
<additionalProjectFacets>
<jst.jsf>2.0</jst.jsf>
</additionalProjectFacets>
<additionalProjectnatures>
<projectnature>org.eclipse.wst.jsdt.core.jsNature</projectnature>
<projectnature>org.jboss.tools.jsf.jsfnature</projectnature>
</additionalProjectnatures>
<workspace>d:\workspace</workspace>
<wtpdefaultserver>JBossAS</wtpdefaultserver>
<wtpversion>2.0</wtpversion>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<!-- The JBoss AS plugin deploys your war to a local JBoss AS container -->
<!-- To use, set the JBOSS_HOME environment variable and run: mvn package jboss-as:deploy -->
<plugin>
<groupId>org.jboss.as.plugins</groupId>
<artifactId>jboss-as-maven-plugin</artifactId>
<version>7.1.0.Final</version>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>distribution</id>
<activation>
<property>
<name>release</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jbossas7</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>arquillian</name>
<value>jbossas-managed-7</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.0</version>
<configuration>
<webResources>
<resource>
<directory>src/main/resources-jbossas7</directory>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jbossas-remote-7</id>
<activation>
<property>
<name>arquillian</name>
<value>jbossas-remote-7</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.jboss.seam.test</groupId>
<artifactId>jbossas-remote-7</artifactId>
<type>pom</type>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
<profile>
<id>jbossas-managed-7</id>
<activation>
<property>
<name>arquillian</name>
<value>jbossas-managed-7</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.jboss.seam.test</groupId>
<artifactId>jbossas-managed-7</artifactId>
<type>pom</type>
<scope>test</scope>
<version>LATEST</version>
</dependency>
</dependencies>
</profile>
</profiles>
<scm>
<connection>scm:git:git://github.com/seam/examples.git</connection>
<developerConnection>scm:git:git@github.com:seam/examples.git</developerConnection>
<url>http://github.com/seam</url>
</scm>
</project>
</pre><br />
<span style="font-size: large;">Maven Eclipse Plugin</span><br />
<br />
This plugin configures the Eclipse Workspace and creates/modifies the configuration files of Eclipse, so the maven project can be easily imported into Eclipse. I assume, you have imported the Seam Booking example project into your default eclipse workspace.<br />
<br />
First, place the following plugin declaration into the <build> section of the pom file (as you have seen under the "Maven 3.0.4 and Dependency versions" section):<br />
<br />
<pre class="brush:xml"><plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalBuildcommands>
<buildCommand>
<name>org.jboss.tools.common.verification.verifybuilder</name>
</buildCommand>
</additionalBuildcommands>
<additionalConfig>
<file>
<name>.settings/org.maven.ide.eclipse.prefs</name>
<content>eclipse.preferences.version=1
fullBuildGoals=process-test-resources
includeModules=false
resolveWorkspaceProjects=true
resourceFilterGoals=process-resources
resources\:testResources
skipCompilerPlugin=true
version=1</content>
</file>
</additionalConfig>
<additionalProjectFacets>
<jst.jsf>2.0</jst.jsf>
</additionalProjectFacets>
<additionalProjectnatures>
<projectnature>org.eclipse.wst.jsdt.core.jsNature</projectnature>
<projectnature>org.jboss.tools.jsf.jsfnature</projectnature>
</additionalProjectnatures>
<workspace>d:\workspace</workspace>
<wtpdefaultserver>JBossAS</wtpdefaultserver>
<wtpversion>2.0</wtpversion>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
</pre><br />
Now, you can execute the plugin in the seam-booking directory: <br />
<pre class="brush:shell">mvn eclipse:eclipse</pre><br />
Now the eclipse project should be refreshed, to reflect the changes. If you try to build and deploy the application, it will fail, because the src/test/java directory is configured as source and the content will be included in the resulting war. I simply removed the src/test/java and src/test/resource folders from Project Properties/Java Build Path/Source. <br />
<br />
<span style="font-size: large;">Running the Annotation Processor in Seam Solder</span><br />
You have several options to run the annotation processor to generate the typed logging class <span style="font-family: 'Courier New', Courier, monospace;">org\jboss\seam\examples\booking\logBookingLog_$logger.java.</span><span style="font-family: inherit;"> You can make Eclipse to run it or you can use ant for this purpose. I have used the APT Maven Plugin to configure the .factorypath file to enable running the annotation processor in Seam Solder Tooling, but later after adding the latest versions to the project pom's dependencies, the Eclipse APT was stopped working (no error messages, no results) so I changed to running the annotation processors using javac and ant (see later). </span><br />
<br />
<br />
<span style="font-size: large;">APT Maven Plugin</span><br />
<br />
This plugin can configure the Eclipse Annotation Processing Tool to be able to run the annotation processor in you maven project.<br />
<br />
First, place the following plugin declaration into the build section of the pom file:<br />
<br />
<pre class="brush:xml"><plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.0-alpha-4</version>
</plugin>
</pre><br />
<br />
Now, you can execute the plugin in the seam-booking directory:<br />
<pre class="brush:shell">mvn apt:eclipse</pre><br />
The plugin will change the content of the ".factorypath" file in the project root directory, adding classpath entries to it. Eclipse's APT plugin will search trough this classpath and if an annotation processor factory is found, it tries to execute. <br />
<br />
The first thing you should change is the name of the generated source directory. Go to Project properties/Java Compiler/Annotation Processing tab/Generated Source directory text box. Here enter a valid file name (e.g.: ".apt_generated", which is the default by Eclipse).<br />
<br />
To see the list of files in the annotation processor classpath, go to the Project Properties/Java Compiler/Annotation Processor/Factory Path tab. The annotation processor for Seam 3 Booking example is found in "M2_REPO/org/jboss/solder/solder-tooling/3.1.0.Final/solder-tooling-3.1.0.Final.jar", where M2_REPO is the eclipse variable for the local maven repository. The Java 6 JDK tools.jar was missing from this factory path, so I manually added as: "D:\bin\jdk1.6.0_31\lib\tools.jar".<br />
<br />
If you have installed JBoss 7.1 and configured in Eclipse, you should able to make the war file and deploy it to the application server (I assume you have installed the latest JBoss Tools eclipse plugin). <br />
<br />
<span style="font-size: large;">Using <i>ant</i> to run the annotation processors in your project</span><br />
<br />
The JDT-APT functionality of Eclipse sometimes just doesn't work (see the previous chapter), so my alternative is to use ant and javac compiler to do it. First, create a build file for ant, and save it as build.xml in your project root:<br />
<br />
<pre class="brush:xml"><?xml version="1.0" encoding="UTF-8"?>
<project name="seam-booking-annotation-processing" basedir="." default="apt">
<property name="src" value="./src/main/java" />
<target name="apt">
<javac compiler="javac1.6" srcdir="${src}">
<include name="**/*.java" />
<classpath path="${classpath}"/>
<compilerarg line="-s ${src_gen}" />
<compilerarg value="-verbose" />
<compilerarg value="-proc:only" />
</javac>
</target>
</project>
</pre><br />
Next, you should add a custom builder to your project: Project/Properties/Builders tab/New button:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-Htm_jWnW3y4/T0zSzr5ID0I/AAAAAAAAOWQ/4K9_TPBqAf8/s1600/ScreenHunter_02+Feb.+28+14.09.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="180" src="http://4.bp.blogspot.com/-Htm_jWnW3y4/T0zSzr5ID0I/AAAAAAAAOWQ/4K9_TPBqAf8/s320/ScreenHunter_02+Feb.+28+14.09.jpg" width="320" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div>Use "Ant Builder", then the "OK" button. The following dialog will appear. I have added the name "APT" to this custom builder. The Buildfile name is "build.xml" in the project root (which is "seam-booking" in the workspace)<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-Wu20VYZURw0/T0zTKrKB34I/AAAAAAAAOWY/A4CMW_KYhoU/s1600/ScreenHunter_03+Feb.+28+14.11.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="590" src="http://2.bp.blogspot.com/-Wu20VYZURw0/T0zTKrKB34I/AAAAAAAAOWY/A4CMW_KYhoU/s640/ScreenHunter_03+Feb.+28+14.11.jpg" width="640" /></a></div><br />
The interesting part is the arguments for the ant task. The "classpath" parameter is passed to the ant build file containing the classpath, and the "src_gen" parameter contains the full path of the directory where javac will place the generated java code. The directory should be created manually, since javac doesn't create it, if missing.<br />
<br />
<pre class="brush:shell">-Dclasspath=${project_classpath:seam-booking}
-Dsrc_gen=${project_loc:seam-booking}/src-gen
</pre><br />
Your builder list for the project should look like this:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-0IhoDh_NVhE/T0zSI1sWuzI/AAAAAAAAOWI/r4u4tzZlFwg/s1600/ScreenHunter_01+Feb.+28+14.06.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="369" src="http://4.bp.blogspot.com/-0IhoDh_NVhE/T0zSI1sWuzI/AAAAAAAAOWI/r4u4tzZlFwg/s640/ScreenHunter_01+Feb.+28+14.06.jpg" width="640" /></a></div><span style="font-size: large;"><br />
</span><br />
Now you should add src-gen to the source paths of your project and do a build (Ctrl-B) to test if the build file is working. If the ant task is executed, it logs to the Console. I have used ant 1.7.1 and Java 1.6 u31.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-pAXIC0FwS38/T0zVMpQ20NI/AAAAAAAAOWg/A34SAnsVCTE/s1600/ScreenHunter_04+Feb.+28+14.20.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-pAXIC0FwS38/T0zVMpQ20NI/AAAAAAAAOWg/A34SAnsVCTE/s1600/ScreenHunter_04+Feb.+28+14.20.jpg" /></a></div><br />
<span style="font-size: large;">Troubleshooting</span><br />
<br />
When the application is not deploying, usually the src/test directories are included in the source directories.<br />
<br />
When you click on a hotel name, and that results in an exception, this is probably caused by the missing typed loggers which should have been generated during the build process. In this case you should check the error log on why the annotation processor (Solder Tooling) haven't been run properly.Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com1Budapest, Hungary47.4984056 19.04075779999993847.3674751 18.836384299999938 47.629336099999996 19.245131299999937tag:blogger.com,1999:blog-7037498389833726124.post-88347475436377696702011-04-11T10:48:00.000+02:002011-04-11T13:47:49.096+02:00Dynamic Menus in RichFaces<div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-Ve47cpURx-Y/TaK_GOXccTI/AAAAAAAALq0/3gwJwA140Ig/s1600/ScreenHunter_03%2BApr.%2B11%2B10.39.jpg" imageanchor="1" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img border="0" height="97" src="http://1.bp.blogspot.com/-Ve47cpURx-Y/TaK_GOXccTI/AAAAAAAALq0/3gwJwA140Ig/s320/ScreenHunter_03%2BApr.%2B11%2B10.39.jpg" width="175" /></a></div>RichFaces has no built-in functionality for creating dynamic menus, the reason for this could be that a lot of different use case scenarios require different type of dynamic menus. This example presents the most simple case: The menu items are dynamically populated from a list, and an action method is called with the id of the menu item, when the user selects the menu item.<br />
<br />
The data of a menu item is represented by the MenuItem class:<br />
<br />
<pre class="brush:java">public class MenuItem {
/**
* Id of the menu item
*/
private int id;
/**
* Label for menu item
*/
private String label;
/**
* Constructor
*
* @param label The label fo the menu item
* @param id the id of the menu item
*/
public MenuItem(String label, int id) {
super();
this.label = label;
this.id = id;
}
/**
* @return the id
*/
public int getId() {
return this.id;
}
/**
* @return the label
*/
public String getLabel() {
return this.label;
}
/**
* @param id
* the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* @param label
* the label to set
*/
public void setLabel(String label) {
this.label = label;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("MenuItem [id=");
builder.append(this.id);
builder.append(", label=");
builder.append(this.label);
builder.append("]");
return builder.toString();
}
}
</pre><br />
The dynamic menu is supported by the DynamicMenu class. It provides the list of menu items and an action method:<br />
<br />
<pre class="brush:java">import java.util.ArrayList;
import java.util.List;
import org.apache.log4j.Logger;
import org.jboss.seam.annotations.Name;
@Name("dynMenu")
public class DynamicMenu {
private Logger log = Logger.getLogger(DynamicMenu.class.getName());
/**
* Example action method
*
* @param id the menu item id
*/
public void action(int id) {
log.info("Action called with menu item id: " + id);
}
/**
* Returns the list of menu items.
*
* @return the list of menu items
*/
public List<menuitem> getMenuItems() {
List<menuitem> menuItems = new ArrayList<menuitem>();
menuItems.add(new MenuItem("Menu Item #1", 1));
menuItems.add(new MenuItem("Menu Item #2", 2));
menuItems.add(new MenuItem("Menu Item #3", 3));
return menuItems;
}
}
</menuitem></menuitem></menuitem></pre><br />
The following code snippet contains the dynamic menu xhtml example. The key in the dynamic menu items is the <b><c:forEach></b> iterator. The namespace declaration is very important, it should be <b>xmlns:c="http://java.sun.com/jstl/core"</b>. If you use xmlns:c="http://java.sun.com/<span class="Apple-style-span" style="color: red;"><b>jsp</b></span>/jstl/core" namespace, the iterator won't work! <br />
<br />
<pre class="brush:xml"><h:form xmlns:h="http://java.sun.com/jsf/html">
...
<rich:dropDownMenu
value="Dynamic Menu Item Example"
style="text-decoration: none;"
>
<c:forEach xmlns:c="http://java.sun.com/jstl/core"
var="item"
items="#{dynMenu.getMenuItems()}"
>
<rich:menuItem
id="menuItem#{item.id}"
submitMode="ajax"
value="#{item.label}"
action="#{dynMenu.action(item.id)}"
>
</rich:menuItem>
</c:forEach>
</rich:dropDownMenu>
...
</h:form>
</pre>Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com6tag:blogger.com,1999:blog-7037498389833726124.post-10571606736303121872011-04-07T17:52:00.000+02:002011-04-11T08:28:24.813+02:00Speeding up a Seam Page in an Expensive Way<div class="separator" style="clear: both; text-align: center;"></div><span class="Apple-style-span" style="font-size: large;">The Task</span><br />
<br />
Given a Seam 2.2 application with an overview page displaying progress of students aiming to obtain a given certificate (I have changed the original purpose of the application in this article for discretion). The problem: the page was excessively slow. I was asked to cut the display time from 30 seconds to below 5 seconds.<br />
The page looked like in the following: On the left side there are the "Trainees", on the header there are the "Exams", the other table cells contains the latest dates when a student has taken the given exam and the color shows the status. (The given certificate are previously selected, in this case the "Java Programmer" Certificate).<br />
<br />
<table border="1" cellpadding="1" cellspacing="0" style="font-size: xx-small; text-align: center;"><tbody>
<tr bgcolor="#3BB9FF"> <td bgcolor="white"></td> <td>Declarations, Initialization and Scoping</td> <td>Flow Control</td> <td>API Contents</td> <td>Concurrency</td> <td>OO Concepts</td> <td>Collections / Generics</td> <td>Fundamentals</td> </tr>
<tr bgcolor="lightgreen"> <td bgcolor="white">Alger, Brian</td> <td>1/3/2011</td> <td bgcolor="orange">2/5/2011</td> <td>3/7/2011</td> <td bgcolor="silver">n/a</td> <td>5/7/2011</td> <td>6/11/2011</td> <td>7/19/2011</td> </tr>
<tr bgcolor="lightgreen"> <td bgcolor="white">Algieri, Christopher</td> <td>1/3/2011</td> <td>2/5/2011</td> <td>3/7/2011</td> <td bgcolor="orange">4/14/2011</td> <td>5/7/2011</td> <td bgcolor="silver">n/a</td> <td>7/19/2011</td> </tr>
<tr bgcolor="lightgreen"> <td bgcolor="white">Barnett, Jason</td> <td>1/3/2011</td> <td>2/5/2011</td> <td>3/7/2011</td> <td>4/14/2011</td> <td>5/7/2011</td> <td>6/11/2011</td> <td>7/19/2011</td> </tr>
<tr bgcolor="lightgreen"> <td bgcolor="white">Dobrin, Adam</td> <td>1/3/2011</td> <td>2/5/2011</td> <td>3/7/2011</td> <td>4/14/2011</td> <td bgcolor="orange">5/7/2011</td> <td>6/11/2011</td> <td>7/19/2011</td> </tr>
<tr> <td bgcolor="white">...</td> <td>...</td> <td>...</td> <td>...</td> <td>...</td> <td>...</td> <td>...</td> <td>...</td> </tr>
<tr bgcolor="lightgreen"> <td bgcolor="white">Fogelson, Benjamin</td> <td>1/3/2011</td> <td>2/5/2011</td> <td>3/7/2011</td> <td bgcolor="silver">n/a</td> <td>5/7/2011</td> <td>6/11/2011</td> <td>7/19/2011</td> </tr>
</tbody></table><br />
<span class="Apple-style-span" style="font-size: x-small;">Legend:</span><br />
<table border="1" cellpadding="1" cellspacing="0" style="font-size: xx-small; text-align: center;"><tbody>
<tr> <td bgcolor="lightgreen">Finished</td> <td bgcolor="orange">Failed</td> <td bgcolor="yellow">Waiting</td> </tr>
</tbody></table><br />
<br />
<br />
<span class="Apple-style-span" style="font-size: large;">Is There a Cheap Solution?</span><br />
<br />
I wanted to solve the problem by applying some kind of "silver bullet", so I entered "<i>speed up seam application</i>" into google. The most interesting article: <a href="http://jsfcentral.com/articles/speed_up_your_jsf_app_1.html">Speed up your Data-Driven JSF/Seam Application by Two Orders of Magnitude</a> promised a 100 times ("two orders of magnitude") speed increment by applying the following steps:<br />
<br />
<ul><li>Remove the unnecessary page action calls</li>
<li>Simplify the EL expressions in conditional rendering</li>
<li>Avoid calling intercepted methods</li>
<li>Avoid using the EL variable resolver, use the corresponding context directly to get the variable's value</li>
<li>Reduce the size of the resulting HTML code by reducing the length of IDs, paths; link JavaScript and CSS instead of embedding</li>
<li>Disable facelet refresh</li>
</ul><br />
Most of the tips are about changing the JSF page or the "page.xml", I have tried my best but with no luck...<br />
<br />
<span class="Apple-style-span" style="font-size: large;">Caching</span><br />
<br />
I have tried to use EHCache using this (and some other) guides: <a href="http://www.geeklisted.com/archives/429">Seam and EhCache – A Short Primer</a>. After installing EHCache I have to admit the performance was still low, although after refreshing the page, it was displayed a bit faster.<br />
<br />
<span class="Apple-style-span" style="font-size: large;">Investigation</span><br />
<br />
At this point it was clear that no cheap tricks will work, so I had to find out what takes 30 seconds to display the page. Adding ad-hoc instrumentation code into the page action method and into the page xhtml code and by measuring the time using Google Chrome Developer Tools, I found out that<br />
<br />
<ul><li>33% of the time spent on database queries and preparing the data in the action method</li>
<li>33% of the time spent on processing the xhtml page and produce the output</li>
<li>33% was spent on displaying the page by the client (waiting for the page data)</li>
</ul><span class="Apple-style-span" style="font-size: large;"><br />
</span><br />
<span class="Apple-style-span" style="font-size: large;">First Bottleneck: Data model</span><br />
<span class="Apple-style-span" style="font-size: large;"><br />
</span><br />
<span class="Apple-style-span" style="font-family: Times, 'Times New Roman', serif;">By looking at the Hibernate entities and queries, it was <span class="Apple-style-span" style="-webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px; color: #353535;">astonishing to see how complicated is to collect the data to display the overview. </span>Let us see the data model which is important for the display.</span><br />
<span class="Apple-style-span" style="font-size: large;"><br />
</span><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-7c4OgSm8mvc/TZ2ItT_Wo5I/AAAAAAAALpY/F_JhWDwiYTg/s1600/training.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="339" src="http://2.bp.blogspot.com/-7c4OgSm8mvc/TZ2ItT_Wo5I/AAAAAAAALpY/F_JhWDwiYTg/s640/training.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Fig 1. The actual data model</td></tr>
</tbody></table>To get a given <i>Certificate </i>the <i>Trainees </i>should take <i>Exams </i>(Certificates and Exams are linked together, but not shown in this model). If a change happens in the Trainee's status (e.g.: finishes an exam), a new <i>Training Event</i> is created linked to a <i>Status</i>. To display the table of all Trainees who are eager to get a given Certificate, the <i>latest </i>Training Events for a given Trainee-Certificate-Exam triplet should be queried. To select the latest training events a sub query is used:<br />
<br />
<br />
<pre class="brush:sql">SELECT
te FROM TrainingEvent te
WHERE
te.certificate = :certificate AND
te.timestamp
IN
(SELECT
MAX(teLatest.timestamp)
FROM
TrainingEvent teLatest
WHERE
teLatest.certificate = :certificate
GROUP BY
teLatest.exam)
</pre><br />
<br />
<span class="Apple-style-span" style="font-size: large;">Second bottleneck: RichFaces</span><br />
<span class="Apple-style-span" style="font-size: large;"><br />
</span><br />
The number of Exams for a given Certificate is variable and assigned in different parts of the application. To enable displaying <i>dynamic </i>columns for a table, RichFaces has the <i>rich:column</i><b><i>s</i> </b>JSF component. In our application this looked like this:<br />
<br />
<pre class="brush:xml"><rich:columns index="ind" style="background-color: #{certificateBean.getLatestTraining(trainee, exam) != null ?certificateBean.getLatestTraining(trainee, exam).status.backgroundColor :'#e0e0ff'};" styleclass="tightTable" value="#{certificateBean.getExams}" var="exam">
<f:facet name="header">
<h:outputtext value="#{exam.name}">
</h:outputtext></f:facet>
<ui:include src=" /trainingEventPanel.xhtml">
<ui:param name="event" value="#{certificateBean.getLatestTraining(trainee, exam)}">
</ui:param></ui:include>
</rich:columns>
</pre><br />
The problem with this is the extensive number of calls to <br />
<pre class="brush:java">certificateBean.getLatestTraining(trainee, exam)
</pre>I have eliminated the "heavy" calls to this method to check if rich:columns can be used effectively, but my "experiments" showed using rich:columns inevitably goes hand in hand with a lot of EL expression evaluation accountable for poor performance.<br />
<br />
<span class="Apple-style-span" style="font-size: large;">Terminal Solution</span><br />
<span class="Apple-style-span" style="font-size: large;"><br />
</span><br />
I have decided to basically rewrite the whole code, but leave the Hibernate entities alone — changing them would have meant changing the whole application. On the database/entities level I left unchanged everything, but my new logic was to implement a specific cache for the data needed to display the page and on the page I discarded the RichFaces components (rich:dataTable, rich:columns), instead I tried to emulate them with plain and simple HTML <table> tag.<br />
<br />
Seam components, displayed in the following figure, are used for caching. The "AppCache" is an Application scope component, which is initialized at the start of the application. It holds the <b>TraineeEventData </b>component which contains the data to be displayed (by Certificate). The <b>TraineeEventDataGenerator </b>component is used to initially "generate" the data or refresh after a change. The <b>TraineeEventDataLoader </b>component is used to query the data from the database, for greater efficiency it uses scalar queries (instead of entity queries) to generate much less network traffic.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-dOGY8rR8S8Q/TZ3PYWXs0WI/AAAAAAAALpk/HL4l-vU4_AY/s1600/appcache.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="384" src="http://1.bp.blogspot.com/-dOGY8rR8S8Q/TZ3PYWXs0WI/AAAAAAAALpk/HL4l-vU4_AY/s640/appcache.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Fig. 2: The Caching Components</td></tr>
</tbody></table><br />
On the display side, instead of rich:dataTable, simple HTML table is used for display. The rows and columns are displayed by using <ui:repeat> components. The TraineeEventData has a method which can provide the information for the table cells as a linear list, so the HTML generation can be very quick.<br />
<br />
<pre class="brush:xml"><table id="traineeEventTable" class="te_table">
<!-- header rows -->
<tr class="te_header_row">
<ui:repeat value="#{traineeEventTable.getHeaderCells()}" var="cell">
<td class="#{cell.getStyleClass()}" width="#{cell.getWidthRef()}">
<s:fragment rendered="#{cell.renderColumnHeader()}">#{cell.getContent()}</s:fragment>
</td>
</ui:repeat>
</tr>
<!-- normal rows -->
<ui:repeat value="#{traineeEventTable.getRows()}" var="row" offset="1">
<tr class="te_normal_row">
<ui:repeat var="cell" value="#{row.cells}">
<td width="#{cell.columnIndex==0?'200':''}" bgcolor="#{cell.getBackgroundColorRef()}" class="#{cell.getStyleClass()}">
<!-- Trainee names -->
<s:fragment rendered="#{cell.renderTraineeHeader()">
<table class="te_trainee_table">
<tr>
<td>
<h:outputText rendered="#{cell.renderTraineeHeader()}" value="#{cell.trainee.traineeDisplayName}" id="traineecell" title="#{cell.hint}"/>
</td>
</tr>
</table>
</s:fragment>
<!-- not available content -->
<s:fragment rendered="#{cell.isNotAvailable()}">n/a</s:fragment>
<!-- normal content -->
<s:fragment rendered="#{cell.isContentRendered()}">
<h:outputText style="color: #{cell.textColorRef};" id="cellcontent" value="#{cell.content}" title="#{cell.hint}"/>
</s:fragment>
</td>
</ui:repeat>
</tr>
</ui:repeat>
</table>
</pre><br />
The s:fragment component is used to control generation of entire blocks.<br />
<br />
What happens on an update which affects the trainee status table? Originally there was a periodic process using Quartz scheduler to update the table, but it was inadequate for the users. Then I was thinking about making a Hibernate event handler which observes changes in the appropriate entities and updates the data when necessary. The final solution is even simpler, a small utility method is called on those locations when the data is changed:<br />
<br />
<pre class="brush:java">package com.trainee.util;
import com.trainee.TraineeEventDataGenerator;
import org.jboss.seam.Component;
public class AppCacheUtil {
/**
* Calls the Application Cache Updater to update the trainee status table
* data
*/
public static void updateAppCache() {
TraineeEventDataGenerator generator = (TraineeEventDataGenerator) Component
.getInstance("traineeEventDataGenerator");
generator.generate();
}
}
</pre><br />
<span class="Apple-style-span" style="font-size: large;">Moral</span><br />
<br />
What lesson we can draw from this case? The first is that the ER model should be always efficient even if the application we are working on is just a prototype (as it were in my case). With a better data model the page display time could have been initially much less. On the other hand the current technology (e.g.: RichFaces) using out-of-the-box is not always able to provide the desired performance and in these cases a custom solution should be made.Ákos Némethhttp://www.blogger.com/profile/08965426141206903845noreply@blogger.com1