Recipe10.8.Refreshing Application Data


Recipe 10.8. Refreshing Application Data

Problem

You want the ability to refresh application-scoped objects cached in the servlet context.

Solution

Provide an interface, such as the one shown in Example 10-24, which indicates if an object can be refreshed.

Example 10-24. Interface used to refresh cached objects
package com.oreilly.strutsckbk.ch10; /**  * An object that can be refreshed from its original source.  */ public interface Refreshable {     public void refresh( ) throws CacheException;      }

Create an Action, such as the one shown in Example 10-25, which refreshes objects in the servlet context. Alternatively, you could create a base Action that provides a protected method for refreshing a named object. This may be necessary to avoid chaining to the ContextRefreshAction whenever you need to refresh data.

Example 10-25. Context refresh action
package com.oreilly.strutsckbk.ch10; import java.util.Enumeration; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts.action.Action; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; public class ContextRefreshAction extends Action {     public ActionForward execute(ActionMapping mapping, ActionForm form,             HttpServletRequest request, HttpServletResponse response)             throws Exception {         String name = request.getParameter("name");         ServletContext ctx = servlet.getServletContext( );         if (name != null && !"".equals(name)) {             refreshObject(name, ctx);         }         else {             Enumeration names = ctx.getAttributeNames( );             while (names.hasMoreElements( )) {                 name = (String) names.nextElement( );                 refreshObject(name, ctx);             }         }         return mapping.findForward("success");     }          private void refreshObject(String name, ServletContext ctx)              throws CacheException {         Object obj = ctx.getAttribute(name);         if (obj != null && obj instanceof Refreshable) {             ((Refreshable) obj).refresh( );                     }     } }

Action chaining should be avoided in a Struts application. For an explanation, see Sidebar 6-1 in Chapter 6.


You can define an action in your struts-config.xml file for the ContextRefreshAction:

<action    path="/ContextRefresh"            type="com.oreilly.strutsckbk.ch10.ContextRefreshAction">     <forward name="success" path="/index.jsp"/> </action>

Discussion

The Solution shows a flexible approach for refreshing application-scoped data. The Refreshable interface indicates if an object can be refreshed and provides a method that implementers use to reload cached data.

You need to use the Solution if the data in question isn't cached by some other mechanism. Data backed by a database or persistence layer is probably cached. For these data, it's better to access the data and not worry about caching.

For data not backed by persistence layerfor example, a flat file of Java properties or XML-formatted informationit's common to store those data in the servlet context. The data can be retrieved via the Servlet API or JSP tags. Data can be loaded into the servlet context using a ServletContextListener (Recipe 7.1) or a Struts PlugIn (Recipe 2-1). If the data seldom changes and you don't mind periodic application restarts, then bouncing the application will reload the data from its original source.

On the other hand, if the data may change more frequently and you don't want to restart the application, then you can use a mechanism such as the one shown in the Solution. To illustrate how this interface would be used, you can apply the Solution to the data loaded by the DigestingPlugIn shown in Recipe 10.7.

The basic approach is to provide a class that knows how to refresh itself using the Digester. First, create the RefreshableList abstract base class shown in Example 10-26.

Example 10-26. Abstract refreshable list
package com.oreilly.strutsckbk.ch10; import java.util.*; public abstract class RefreshableList implements Refreshable, List {     public void add(int arg0, Object arg1) {         backingList.add(arg0, arg1);     }     public boolean add(Object arg0) {         return backingList.add(arg0);     }     public boolean addAll(Collection arg0) {         return backingList.addAll(arg0);     }     public boolean addAll(int arg0, Collection arg1) {         return backingList.addAll(arg0, arg1);     }     public void clear( ) {         backingList.clear( );     }     public boolean contains(Object arg0) {         return backingList.contains(arg0);     }     public boolean containsAll(Collection arg0) {         return backingList.containsAll(arg0);     }     public Object get(int arg0) {         return backingList.get(arg0);     }     public int indexOf(Object arg0) {         return backingList.indexOf(arg0);     }     public boolean isEmpty( ) {         return backingList.isEmpty( );     }     public Iterator iterator( ) {         return backingList.iterator( );     }     public int lastIndexOf(Object arg0) {         return backingList.lastIndexOf(arg0);     }     public ListIterator listIterator( ) {         return backingList.listIterator( );     }     public ListIterator listIterator(int arg0) {         return backingList.listIterator(arg0);     }     public Object remove(int arg0) {         return backingList.remove(arg0);     }     public boolean remove(Object arg0) {         return backingList.remove(arg0);     }     public boolean removeAll(Collection arg0) {         return backingList.removeAll(arg0);     }     public boolean retainAll(Collection arg0) {         return backingList.retainAll(arg0);     }     public Object set(int arg0, Object arg1) {         return backingList.set(arg0, arg1);     }     public int size( ) {         return backingList.size( );     }     public List subList(int arg0, int arg1) {         return backingList.subList(arg0, arg1);     }     public Object[] toArray( ) {         return backingList.toArray( );     }     public Object[] toArray(Object[] arg0) {         return backingList.toArray(arg0);     }     protected List backingList; }

This class decorates the List interface with the Refreshable interface. The only abstract method is refresh( ). You can extend this class to provide different ways of refreshing list contents. An implementation that uses the Digester is shown in Example 10-27.

Example 10-27. Using the Digester to refresh a list
package com.oreilly.strutsckbk.ch10; import java.net.URL; import java.util.List; import org.apache.commons.digester.Digester; import org.apache.commons.digester.xmlrules.DigesterLoader; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class DigestedList extends RefreshableList {     private static Log log = LogFactory.getLog(DigestedList.class);     public DigestedList(List list, URL sourceUrl, URL rulesUrl) {         this.sourceUrl = sourceUrl;         this.rulesUrl = rulesUrl;         this.backingList = list;     }     public synchronized void refresh( ) throws CacheException {         Digester digester = DigesterLoader.createDigester(rulesUrl);         try {             List list = (List) digester.parse(sourceUrl.openStream( ));             if (list != null)                  backingList = list;             else {                 log.error("Returned list was null due to unknown error");                 throw new CacheException("Backing list was null.");             }         } catch (Exception e) {             log.error("Unable to redigest list.", e);             throw new CacheException("Unable to redigest list.");         }     }     private URL sourceUrl;     private URL rulesUrl; }

This class maintains instance variables for the original URL of the data source file and the URL of the Digester rules used to parse the source file. In the refresh( ) method, an instance of a Digester is created. The data in the source file is parsed, and the results are cast into a java.util.List. If the returned data is null or any other exception occurs, a CacheException is thrown.

Now that you have the classes that implement the Refreshable interface, you need to create a mechanism for loading the data on the application startup. The DigestingListPlugIn in Example 10-28 extends the DigestingPlugIn for the purpose of creating refreshable lists and storing them in the servlet context. It overrides storeGeneratedObject()to provide this functionality.

Example 10-28. Plug-in for reloading a Digester-based refreshable list
package com.oreilly.strutsckbk.ch10; import java.io.IOException; import java.net.URL; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.plugins.DigestingPlugIn; public class DigestingListPlugin extends DigestingPlugIn {     private static Log log = LogFactory.getLog(DigestingPlugIn.class);     public DigestingListPlugin( ) {     }     protected void storeGeneratedObject(Object obj) {         if (!(obj instanceof List))              throw new IllegalArgumentException(                 "Digested object must be a list but is:"+obj);         List list = (List) obj;         URL sourceUrl =  null;         URL rulesUrl =  null;         try {             sourceUrl = getConfigURL(configPath, configSource);             rulesUrl = getConfigURL(digesterPath, digesterSource);             DigestedList digestedList = new DigestedList(list,                                                           sourceUrl,                                                           rulesUrl );             servlet.getServletContext( ).setAttribute(key, digestedList);         } catch (IOException e) {             log.error("Unable to create URL.", e);         }     } }

You configure a DigestingListPlugIn plug-in in the struts-config.xml file like the DigestingPlugIn:

<plug-in className="com.oreilly.strutsckbk.ch10.DigestingListPlugIn">     <set-property property="key"                       value="serverTypes"/>     <set-property property="configSource"                       value="servlet"/>     <set-property property="configPath"                       value="/WEB-INF/server-types.xml"/>     <set-property property="digesterPath"                       value="/WEB-INF/lvb-digester-rules.xml"/> </plug-in>

To see the refreshable lists in action, you need to modify the data source. You can modify the file using a text editor, the hard part is finding the file. The DigestingListPlugIn, like the DigestingPlugIn (Recipe 10.7), uses the configSource property to treat the data source file (identified by the configPath property) as relative to the classpath (classpath), the filesystem (file), or the web application context (servlet). The default value is servlet. If you were to deploy the Solution to Tomcat 5, you would find the file located at $CATALINA_HOME/webapps/jsc-ch10/WEB-INF/server-types.xml. Once you modify the file, you can use your browser to post a request to a URL that triggers the ContextRefreshAction:

http://localhost/jsc-ch10/ContextRefresh.do

If you wanted only to refresh a specific object, you can specify it by name:

http://localhost/jsc-ch10/ContextRefresh.do?name=serverTypes

Now that you've got a generic mechanism for refreshing data, you can extend this mechanism for reloading property files, comma-separated value files, and other types.

See Also

The subject of caching data comes up often on the struts-user mailing list. If you search the archives for caching, you will find a number of threads related to this topic.

The DigestingPlugIn is specifically discussed in Recipe 10.7.



    Jakarta Struts Cookbook
    Jakarta Struts Cookbook
    ISBN: 059600771X
    EAN: 2147483647
    Year: 2005
    Pages: 200

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net