Archive for category Java
Deploy your app to the root context
This is an easy trick which I am sure most of you already know.
Let’s take a Java application called MyAddressBook. Its generated war file could be called myaddressbook.war
.
By default, when you deploy this web application to Tomcat, the URL to access it will be http://localhost:8080/myaddressbook/. And if you point a domain name such as ‘addressbook.com’ to this server, the URL would be http://addressbook.com/myaddressbook/.
I don’t know for you but I don’t like to systematically have the subfolder ‘myaddressbook’ after my domain. But maybe I am too picky! ๐
The idea is to deploy our application in the Tomcat root context.
You have two ways of doing this:
- Define a
ROOT.xml
file in yourconf/Catalina/localhost
folder, or; - Rename your war file to
ROOT.war
.
Note that the case is important, it has to be ROOT in UPPERCASE! ๐
Once this is done, you will be able to call your application via the URL http://localhost:8080/
or http://addressbook.com/. Way better! ๐
How to monitor your Java application
It is very good to have your application and your database running on a Linux or Windows server, but who will tell you if your website is down? Who said “the users”? ๐ฏ No, you won’t look very professional if you wait for a user complaint.
What you need to do is to monitor your server. But which tools to use? There are so many on the market… ๐
I tested a few of them (Hyperic, Nagios, Zenoss, Cacti, Monitis) but the one I choose is Zabbix. What I like with Zabbix is its price (free ๐ ) and the fact that it is easy to configure and very flexible. Indeed, you can monitor anything you want on the machine (CPU, network, disk space, services, etc)! ๐
However, if you also want to monitor your Tomcat application server or even Hibernate Java library, you need some more work.
To monitor Tomcat, you first need to enable JMX Remote. To do so, you can have a look at the official documentation or simply add the following parameters to your Tomcat startup script:
export CATALINA_OPTS='-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=8999 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false'
Then, I will recommend you to deploy Zapcat JMX Zabbix Bridge in your Tomcat web server. This tool has been developed to simplify the communication between the JMX management API inside Java applications and the Zabbix monitoring tool.
To read the installation instructions for Tomcat, please click on the following link:
http://www.kjkoster.org/zapcat/Tomcat_How_To.html
Finally, if you are using Hibernate in your Java application and would like to monitor it, you have to instantiate and configure a Hibernate MBean (org.jboss.hibernate.jmx.Hibernate
) that will be responsible for constructing a Hibernate SessionFactory
and exposing it to your application through JNDI.
Here are the lines you need to add to your context XML file:
<bean id="mbeanExporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false"> <property name="server" ref="mbeanServerFactory"/> <property name="beans"> <map> <entry key="org.hibernate:type=statistics" value-ref="hibernateMBean"/> </map> </property> </bean> <bean id="mbeanServerFactory" class="org.springframework.jmx.support.MBeanServerFactoryBean"> <property name="locateExistingServerIfPossible" value="true"/> </bean> <bean id="hibernateMBean" class="org.hibernate.jmx.StatisticsService"> <property name="sessionFactory" ref="sessionFactory"/> <property name="statisticsEnabled" value="true"/> </bean>
No saved view state
How many of you did already get the following error when working with JSF?
I would be surprised if none of you got it at least once! ๐
HTTP ERROR: 500
/web/home.htmlNo saved view state could be found for the view identifier: /web/home.html
RequestURI=/web/home.html
Caused by:
javax.faces.application.ViewExpiredException: /web/home.htmlNo saved view state could be found for the view identifier: /web/home.html
at org.apache.myfaces.lifecycle.RestoreViewExecutor.execute(RestoreViewExecutor.java:88)
at org.apache.myfaces.lifecycle.LifecycleImpl.executePhase(LifecycleImpl.java:103)
at org.apache.myfaces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:76)
at javax.faces.webapp.FacesServlet.service(FacesServlet.java:151)
at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1093)
at org.apache.myfaces.webapp.filter.ExtensionsFilter.doFilter(ExtensionsFilter.java:341)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:83)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:178)
at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:368)
at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:495)
at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
...
This very common error happens because your session has timed out. As you probably already know, JSF is storing the view state of your page in session. Obviously, when the session has timed out, it can’t restore the view state and so throws a ViewExpiredException
.
The solution for this problem is to add the following lines in your web.xml file:
<context-param> <param-name>facelets.BUILD_BEFORE_RESTORE</param-name> <param-value>true</param-value> </context-param>
When this initialization parameter is turned on, it changes ViewHandler.restoreView()
to build the view before asking the StateManager
for help.
However, if you are using RichFaces, for some reason this is breaking a few Ajax components! ๐
To be honest with you, I didn’t investigate in depth why these components don’t work with this parameter set to true.
What I did instead is to make sure the session actually never times out! ๐
To do that, I am polling the server every 29 minutes (as my session time out is set to 30 minutes). Obviously, you can poll the server only every 59 minutes if you set your session time out to 60 minutes.
Here is the code I used to poll the server every 29 minutes:
<h:form> <a4j:poll id="poll" interval="1740000" limitToList="true" /> </h:form>
Note that the interval attribute is in milliseconds (29 minutes x 60 x 1000 = 1,740,000 milliseconds).
This solution is far from being perfect! Indeed, if, for example, a user doesn’t close his browser during the night, it means that we will have to keep the session opened during hours! ๐
But, as far as I am concerned, it is still better than to throw an exception at the user face. ๐
RichFaces is too greedy
For my first post, let’s talk a little bit about RichFaces.
RichFaces is a very powerful JSF library which allows you to easily integrate Ajax capabilities into your website. To see all the components RichFaces has to offer, please click on the following link: http://livedemo.exadel.com/richfaces-demo/richfaces/actionparam.jsf
I am now using this framework for about two years and I quite like how easy it is to use! ๐ However, at the start, I was only using it in small projects with less than two hundred of unique visitors a day.
That is why I didn’t discover earlier that RichFaces was using so much memory! Indeed, I am now working on a project that has nearly 50,000 unique visitors a day. When we decided to rebuild it using JSF and RichFaces, I didn’t think that 6Gb of RAM wouldn’t be enough to keep it running! ๐ฎ
People who knows JSF would say: “Why don’t you use ‘client’ as state saving method?”. Well, because of the complexity of my pages, the size of these pages increases so much that it can easily reach 2Mb (which is not acceptable). So, I need to keep the view state on server side.
Still in JSF, there is a way of compressing the view state before saving it in session (part of the web.xml file):
<context-param> <param-name>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</param-name> <param-value>true</param-value> </context-param>
Strangely enough, the second parameter (COMPRESS_STATE_IN_SESSION
) doesn’t work with RichFaces. After investigating the code, it seems that the RichFaces state manager (org.ajax4jsf.application.AjaxStateManager
) doesn’t support compression at all! ๐
Why not? Well, I didn’t find a good reason so I decided to compress it myself. ๐ The solution is to extend the AjaxStateManager and compress the view state before saving it in session and uncompress it when retrieving it from the session. Pretty easy!!! ๐
Enough talking, here is the code of this custom state manager:
package org.ajax4jsf.application; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import javax.faces.FacesException; import javax.faces.application.StateManager; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.faces.render.ResponseStateManager; import org.ajax4jsf.context.ContextInitParameters; /** * Overrides the Ajax state manager to enable the state compression. * @author Stรฉphane Moreau */ public class MyAjaxStateManager extends AjaxStateManager { /** * Only applicable if state saving method is "server" (= default) and if * <code>org.apache.myfaces.SERIALIZE_STATE_IN_SESSION</code> is * <code>true</code> (= default). * If <code>true</code> (default) the serialized state will be compressed before * it is written to the session. * If <code>false</code> the state will not be compressed. */ private static final String COMPRESS_SERVER_STATE_PARAM = "org.apache.myfaces.COMPRESS_STATE_IN_SESSION"; /** * Default value for <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> * context parameter. */ private static final boolean DEFAULT_COMPRESS_SERVER_STATE_PARAM = true; private static final int UNCOMPRESSED_FLAG = 0; private static final int COMPRESSED_FLAG = 1; private final ComponentsLoader componentLoader; public MyAjaxStateManager(StateManager stateManager) { super(stateManager); componentLoader = new ComponentsLoaderImpl(); } /** * Reads the value of the <code>org.apache.myfaces.COMPRESS_STATE_IN_SESSION</code> * context parameter. * @see COMPRESS_SERVER_STATE_PARAM * @param context <code>FacesContext</code> for the request we are processing. * @return boolean true, if the server state steam should be compressed */ protected static boolean isCompressStateInSession(FacesContext context) { String value = context.getExternalContext().getInitParameter( COMPRESS_SERVER_STATE_PARAM); boolean compress = DEFAULT_COMPRESS_SERVER_STATE_PARAM; if (value != null) { compress = Boolean.valueOf(value); } return compress; } @Override protected Object[] buildViewState(FacesContext context) { Object[] viewStateArray = null; UIViewRoot viewRoot = context.getViewRoot(); if (null != viewRoot && !viewRoot.isTransient()) { TreeStructureNode treeStructure = (TreeStructureNode) getTreeStructureToSave(context); Object state = getComponentStateToSave(context); if (isSavingStateInClient(context)) { viewStateArray = new Object[]{treeStructure, state}; } else { viewStateArray = saveStateInSession(context, treeStructure, handleSaveState(context, state)); } } return viewStateArray; } @Override public UIViewRoot restoreView(FacesContext context, String viewId, String renderKitId) { UIViewRoot viewRoot = null; ResponseStateManager responseStateManager = getRenderKit(context, renderKitId).getResponseStateManager(); TreeStructureNode treeStructure = null; Object[] state = null; Object[] serializedView = null; if (isSavingStateInClient(context)) { serializedView = (Object[]) responseStateManager.getState(context, viewId); if (null != serializedView) { treeStructure = (TreeStructureNode) serializedView[0]; state = (Object[]) serializedView[1]; } } else { serializedView = restoreStateFromSession(context, viewId, renderKitId); if (null != serializedView) { treeStructure = (TreeStructureNode) serializedView[0]; state = (Object[]) handleRestoreState(context, serializedView[1]); } } if (null != treeStructure) { viewRoot = (UIViewRoot) treeStructure.restore(componentLoader); if (null != viewRoot && null != state) { viewRoot.processRestoreState(context, state[0]); restoreAdditionalState(context, state[1]); } } return viewRoot; } private static final Object handleSaveState(FacesContext context, Object state) { if (ContextInitParameters.isSerializeServerState(context)) { ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); ObjectOutputStream oas = null; try { OutputStream os = baos; if(isCompressStateInSession(context)) { os.write(COMPRESSED_FLAG); os = new GZIPOutputStream(os, 1024); } else { os.write(UNCOMPRESSED_FLAG); } oas = new ObjectOutputStream(os); oas.writeObject(state); oas.flush(); } catch (Exception e) { throw new FacesException(e); } finally { if (oas != null) { try { oas.close(); } catch (IOException ignored) { } } } return baos.toByteArray(); } else { return state; } } private static final Map<String,Class<?>> PRIMITIVE_CLASSES = new HashMap<String,Class<?>>(9, 1.0F); static { PRIMITIVE_CLASSES.put("boolean", boolean.class); PRIMITIVE_CLASSES.put("byte", byte.class); PRIMITIVE_CLASSES.put("char", char.class); PRIMITIVE_CLASSES.put("short", short.class); PRIMITIVE_CLASSES.put("int", int.class); PRIMITIVE_CLASSES.put("long", long.class); PRIMITIVE_CLASSES.put("float", float.class); PRIMITIVE_CLASSES.put("double", double.class); PRIMITIVE_CLASSES.put("void", void.class); } private static final Object handleRestoreState(FacesContext context, Object state) { if (ContextInitParameters.isSerializeServerState(context)) { ObjectInputStream ois = null; try { ByteArrayInputStream bais = new ByteArrayInputStream((byte[]) state); InputStream is = bais; if(is.read() == COMPRESSED_FLAG) { is = new GZIPInputStream(is); } ois = new ObjectInputStream(is) { @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String name = desc.getName(); try { return Class.forName(name, true, Thread.currentThread().getContextClassLoader()); } catch (ClassNotFoundException cnfe) { Class<?> clazz = PRIMITIVE_CLASSES.get(name); if (clazz != null) { return clazz; } else { throw cnfe; } } } }; return ois.readObject(); } catch (Exception e) { throw new FacesException(e); } finally { if (ois != null) { try { ois.close(); } catch (IOException ignored) { } } } } else { return state; } } }
To enable it, just add the following lines in your faces config file:
<application> <state-manager>org.ajax4jsf.application.SearchMedicaStateManager</state-manager> </application>
With this state manager, my web application is now using less than 1Gb of memory and is working perfectly fine! ๐