Sunday, December 15, 2013

[Java] Caching Using XMLBeans

In this example, I will create caching service. The properties file  will be cached during start-up of application server - I used Apache Tomcat version 6. I decided to used Servlet to make caching portable. There are other ways of caching properties file based on the application server, e.g. in Websphere - Custom Service can be used but that would require update to code when application is deployed to other application server like JBoss. Lazy loading is also another way but that would impact the performance of the initial process that invoke the caching process.

Now, let us start coding the caching process...


Step 1: Create XSD for caching configuration.

What I want is to have a configuration file that I can use to define caching classes. I want it re-usable so that other Developer can define their own caching classes. Below is the schema that define the XML configuration file.

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://mago.com/cache">
  <xs:element name="CacheConfiguration">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Configuration" maxOccurs="unbounded" minOccurs="0">
          <xs:complexType>
            <xs:sequence>
              <xs:element type="xs:string" name="Key"/>
              <xs:element type="xs:string" name="ClassName"/>
              <xs:element type="xs:string" name="ConfigurationFile"/>
              <xs:element type="xs:string" name="Description"/>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>

</xs:schema>


Below is the XML equivalent which I will save in a file - cache.xml. This file can have multiple configuration entries.

<?xml version="1.0" encoding="utf-8"?>
<CacheConfiguration>
  <Configuration>
    <Key>DummyConfig</Key>
    <ClassName>com.mago.cache.example.DummyClass</ClassName>
    <ConfigurationFile>C:/dummy1.properties</ConfigurationFile>
    <Description>This is dummy cache class</Description>
  </Configuration>
</CacheConfiguration>


Step 2: Generate XSD classes using XMLBeans.

Generate XMLBeans classes using scomp command. Add generated Jar file to Eclipse project build properties.

scomp -out CacheConfiguration.jar cache-config.xsd


Step 3: Create interface class that has cache() and getCache() methods.


import java.util.Map;

public interface Cache 

{
public void cache(String propertiesFileAbsolutPath);
public Map<String,String> getCache();


}//end class


Step 4: Create cache implementation class.

import java.util.Map;

public class DummyCache implements Cache {


   private Map<String,String> map;


@Override

public void cache(String propertiesFileAbsolutPath) 
 {
Enumeration enuKeys = null; 
  map = new HashMap<String,String>(); 
  Properties properties = new Properties(); 
  try { 
    properties.load(new FileInputStream(propertiesFileAbsolutPath)); 
  } catch (FileNotFoundException e1) 
  { 
     e1.printStackTrace(); 
  } catch (IOException e1) 
  { 
    e1.printStackTrace(); 
  } 

  enuKeys = properties.keys(); 

  while (enuKeys.hasMoreElements()) 
  { 
    String key = (String) enuKeys.nextElement(); 
    String value = properties.getProperty(key); 
    map.put(key, value); 
    LOGGER.info("Caching " + value + " with key " + key); 
  }
}

@Override

public Map<String, String> getCache() 
 {
return map;
}

}//end class



Step 5: Create singleton class that reads cache.xml.

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.xmlbeans.XmlException;
import com.mago.cache.CacheConfigurationDocument;
import com.mago.cache.CacheConfigurationDocument.CacheConfiguration;
import com.mago.cache.CacheConfigurationDocument.CacheConfiguration.Configuration;


public class CacheManager {


private final Logger LOGGER = Logger.getLogger(CacheManager.class .getName()); 
private static CacheManager instance;
private static Map cacheMap;

public static CacheManager getInstance(){
if (instance == null) {
instance = new CacheManager();
cacheMap = new HashMap();
}
return instance;
}

/**
* Add objects to cache using reflection.
*/
public void cache(){
//Use reflection
Class cls = null;
Object obj = null;
Method method = null;
Class noparams[] = {};
Map<String,String> cachedItems = null;
//XMLBeans
CacheConfigurationDocument cacheConfigDoc = null;
CacheConfiguration cacheConfig = null;
//Get config file
String configurationFile = "c:/cache.config";
LOGGER.info("Configration file is " + configurationFile);
File xmlFile = new File(configurationFile); 
//Parse using xmlbeans
try {
cacheConfigDoc = CacheConfigurationDocument.Factory.parse(xmlFile);
cacheConfig = cacheConfigDoc.getCacheConfiguration();
Configuration[] configuration = cacheConfig.getConfigurationArray();
for(Configuration c : configuration){
//Load
try {
cls = Class.forName(c.getClassName());
obj = cls.newInstance();
method = cls.getDeclaredMethod("cache", new Class[]{String.class});
method.invoke(obj, c.getConfigurationFile());
method = cls.getDeclaredMethod("getCache", noparams);
cachedItems = (Map<String,String>)method.invoke(obj, null);
//Add to Map
cacheMap.put(c.getKey(),cachedItems);

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}

}//end for
} catch (XmlException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

}//end method

/**
* Search value base on key.
* @param objectName
* @param key
* @return
*/
public String getCacheItemValue(String objectName,String key)
 {
Map cacheItems = (Map)cacheMap.get(objectName.trim());
String cacheValue = (String)cacheItems.get(key.trim());
return cacheValue;
}

   
   /**
   * Re-cache
   */
   public void reCache()
   {
cache();
   }

}//end class




Step 6: Create Servlet class that will call CacheManager during startup.


import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.logging.Logger;
import com.mago.cache.example.CacheManager;
import java.io.IOException;


/**

 * Servlet implementation class CacheServlet
 */
public class CacheServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final static Logger LOGGER = Logger.getLogger(CacheServlet.class.getName());


    /**

     * Default constructor. 
     */
    public CacheServlet() {
    }
    

public void init() throws ServletException 

 {
CacheManager.getInstance().cache();
}//end method

/**

* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String req = request.getParameter("action");
if("recache".equalsIgnoreCase(req))
  {
CacheManager.getInstance().reCache();
}else if("getvalue".equalsIgnoreCase(req))
  {
String cache = request.getParameter("cache");
String key = request.getParameter("key");
String value = CacheManager.getInstance().getCacheItemValue(cache, key);
LOGGER.info("Value from cache is " + value);
}

}//end method


/**

* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException 
{

}


}//end class



Step 7: Update web.xml. Set Servlet load-on-startup to 1 - this means, Servlet will be loaded during Apache Tomcat start-up.


<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>StartupService</display-name>
<servlet>
<description>
</description>
<display-name>CacheServlet</display-name>
<servlet-name>CacheServlet</servlet-name>
<servlet-class>com.mago.cache.example.CacheServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CacheServlet</servlet-name>
<url-pattern>/CacheServlet</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
</web-app>



Step 7: Start Apache Tomcat and check if the caching is successful using Servlet - http://localhost:8080/<web context>/CacheServlet?action=getValue&cache=DummyConfig&key=<key of the property>

That's it!

Note : I use BareTail to monitor log file. It is similar to the 'tail' command in Unix or Linux.



No comments:

Post a Comment