divendres, 24 de juny del 2011

Liferay Portal Server: avoiding use of extension (Part 3)

Putting all together


Sumarizing what I explained in Part 2, we need to create:

  1. A Main servlet extending Liferay's Main Servlet
  2. A Struts ModuleConfig, and a ModuleConfigFactory
  3. A TilesDefinitionFactory
  4. A HotDeployListener
If you've read previous entries you already know why we need all of these and what are the objectives of each class, so I'm just gonna provide you with some code so that you don't need to write it down. Just copy/paste that code into the correnponding classes, package it, deploy it, and take advantage of this!

Overrinding MainServlet


package com.blogspot.aigloss.hook.hotdeploy.servlet;


import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


import org.apache.commons.digester.Digester;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.tiles.DefinitionsFactory;
import org.apache.struts.tiles.xmlDefinition.XmlDefinitionsSet;
import org.apache.struts.tiles.xmlDefinition.XmlParser;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.ResourceEntityResolver;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;


import com.blogspot.aigloss.hook.hotdeploy.struts.tiles.TilesDefinitionsFactory;
import com.liferay.portal.servlet.MainServlet;
import com.liferay.portal.spring.context.PortalApplicationContext;
import com.liferay.portal.struts.PortletRequestProcessor;
import com.liferay.portal.util.WebKeys;


public class HookHotdeployMainServlet extends MainServlet {

private static boolean addConfigs = false;
private static boolean addStrutsConfigs = false;
private static boolean addTilesDefs = false;
private static boolean addSpringFiles = false;


private static List<String> xmlStrutsConfigs = null;
private static List<String> xmlTilesDefs = null;
private static List<String> springFiles = null;


public static void addSpringFile(String filePath) {
if(filePath != null) {
if(springFiles == null) {
springFiles = new ArrayList<String>();
}
springFiles.add(filePath);
HookHotdeployMainServlet.addSpringFiles = true;
HookHotdeployMainServlet.addConfigs = true;
}
}


public static void addStrutsConfigFile(String xmlConfig) {
if(xmlConfig != null) {
if(xmlStrutsConfigs == null) {
xmlStrutsConfigs = new ArrayList<String>();
}
xmlStrutsConfigs.add(xmlConfig);
HookHotdeployMainServlet.addStrutsConfigs = true;
HookHotdeployMainServlet.addConfigs = true;
}
}


public static void addTilesDefinitions(String xmlConfig) {
if(xmlConfig != null) {
if(xmlTilesDefs == null) {
xmlTilesDefs = new ArrayList<String>();
}
xmlTilesDefs.add(xmlConfig);
HookHotdeployMainServlet.addTilesDefs = true;
HookHotdeployMainServlet.addConfigs = true;
}
}


protected synchronized void parseSpringFiles() throws ServletException {
try {
if(HookHotdeployMainServlet.addSpringFiles) {
WebApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
for(String springFile : springFiles) {
DefaultListableBeanFactory bf = (DefaultListableBeanFactory) ((PortalApplicationContext)getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)).getBeanFactory();
XmlBeanFactory beanFactory = new XmlBeanFactory(new FileSystemResource(springFile));


XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);


beanDefinitionReader.setResourceLoader(ac);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(ac));


beanDefinitionReader.loadBeanDefinitions("file:///" + springFile);


//Copy loaded fields
String[] beans = beanFactory.getBeanDefinitionNames();
for(String beanName : beans) {
try {
bf.getBean(beanName);

if(bf.isSingleton(beanName)) {
bf.destroySingleton(beanName);
} else {
bf.destroyScopedBean(beanName);
}
} catch(NoSuchBeanDefinitionException nsbd) {
}
bf.registerBeanDefinition(beanName, beanFactory.getBeanDefinition(beanName));
}
}
}
} finally {
/*
* Whatever happens these tiles config files don't have to be parsed ever again,
* since a parsing error would imply not cleaning xmlTilesDefs and that parsing error
* would happen again and again (every request would return that parsing error)
*/
springFiles = null;
HookHotdeployMainServlet.addSpringFiles = false;
}
}


protected synchronized void parseTilesDefinitions() throws ServletException {
try {
if(HookHotdeployMainServlet.addTilesDefs) {
XmlDefinitionsSet xmlDefinitions = new XmlDefinitionsSet();
for(String file : xmlTilesDefs)
try
{
XmlParser xmlParser = new XmlParser();
xmlParser.setValidating(true);
xmlParser.parse(new ByteArrayInputStream(file.getBytes()), xmlDefinitions);
}
catch(Exception ex)
{
throw new ServletException("Error while parsing file\n " + file + "\n" + ex.getMessage(), ex);
}


DefinitionsFactory definitionsFactory = (DefinitionsFactory) getServletContext().getAttribute("org.apache.struts.tiles.DEFINITIONS_FACTORY");
if(definitionsFactory instanceof TilesDefinitionsFactory) {
((TilesDefinitionsFactory) definitionsFactory).addXmlDefinitions(xmlDefinitions);
} else {
TilesDefinitionsFactory factory = new TilesDefinitionsFactory(definitionsFactory);
factory.addXmlDefinitions(xmlDefinitions);
getServletContext().setAttribute("org.apache.struts.tiles.DEFINITIONS_FACTORY", factory);
}
((PortletRequestProcessor)getServletContext().getAttribute(WebKeys.PORTLET_STRUTS_PROCESSOR)).init(this, getModuleConfig());
}
} finally {
/*
* Whatever happens these tiles config files don't have to be parsed ever again,
* since a parsing error would imply not cleaning xmlTilesDefs and that parsing error
* would happen again and again (every request would return that parsing error)
*/
xmlTilesDefs = null;
HookHotdeployMainServlet.addTilesDefs = false;
}
}


protected synchronized void parseStrutsConfigs() throws ServletException {
try {
if(HookHotdeployMainServlet.addStrutsConfigs) {
Digester digester = initConfigDigester();
ModuleConfig moduleConfig = getModuleConfig();
digester.push(moduleConfig);
for(String is : xmlStrutsConfigs) {
try {
digester.parse(new ByteArrayInputStream(is.getBytes()));
} catch (Exception e) { 
throw new ServletException("Error while parsing file /WEB-INF/struts-config.xml", e);
}
digester.push(moduleConfig);
}
getServletContext().setAttribute("org.apache.struts.action.MODULE" + moduleConfig.getPrefix(), moduleConfig);


PortletRequestProcessor portletReqProcessor = (PortletRequestProcessor) getServletContext().getAttribute(WebKeys.PORTLET_STRUTS_PROCESSOR); 
if (portletReqProcessor == null) {
portletReqProcessor =
PortletRequestProcessor.getInstance(this, moduleConfig);


getServletContext().setAttribute(
WebKeys.PORTLET_STRUTS_PROCESSOR, portletReqProcessor);
}
}
} finally {
/*
* Whatever happens these struts config files don't have to be parsed ever again,
* since a parsing error would imply not cleaning xmlStrutsConfigs and that parsing error
* would happen again and again (every request would return that parsing error)
*/
xmlStrutsConfigs = null;
HookHotdeployMainServlet.addStrutsConfigs = false;
}
}


protected ModuleConfig getModuleConfig() {
return (ModuleConfig) getServletContext().getAttribute("org.apache.struts.action.MODULE");
}


public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if(HookHotdeployMainServlet.addConfigs) {
parseStrutsConfigs();
parseTilesDefinitions();
parseSpringFiles();
HookHotdeployMainServlet.addConfigs = false;
}
super.service(request, response);
}
}

Creating Struts ModuleConfigFactory and ModuleConfig



package com.blogspot.aigloss.hook.hotdeploy.config;


import java.io.Serializable;
import com.blogspot.aigloss.hook.hotdeploy.config.impl.ModuleConfig;


public class ModuleConfigFactory extends org.apache.struts.config.ModuleConfigFactory implements Serializable {
public ModuleConfigFactory(){}
public ModuleConfig createModuleConfig(String prefix)
{
return new ModuleConfig(prefix);
}
}



package com.blogspot.aigloss.hook.hotdeploy.config.impl;


import org.apache.struts.config.ActionConfig;
import org.apache.struts.config.ActionConfigMatcher;
import org.apache.struts.config.impl.ModuleConfigImpl;


public class ModuleConfig extends ModuleConfigImpl {


private static final long serialVersionUID = 1L;


public ModuleConfig(String prefix) {
super(prefix);
}


@Override
public void freeze() {
        ActionConfig aconfigs[] = findActionConfigs();
        matcher = new ActionConfigMatcher(aconfigs);
}
}

Creating TilesDefinitionsFactory



package com.blogspot.aigloss.hook.hotdeploy.struts.tiles;


import java.util.Iterator;


public class TilesDefinitionsFactory extends ComponentDefinitionsFactoryWrapper {


private DefinitionsFactory instance = null; private org.apache.struts.tiles.xmlDefinition.DefinitionsFactory definitionsFactory = null;

public TilesDefinitionsFactory(DefinitionsFactory factory) {
this.instance = (ComponentDefinitionsFactoryWrapper) factory;
}

@SuppressWarnings("rawtypes")
public synchronized void addXmlDefinitions(XmlDefinitionsSet xmlDefinitions) throws ServletException {
if(xmlDefinitions != null) {
try {
if(this.definitionsFactory == null) {
this.definitionsFactory = new org.apache.struts.tiles.xmlDefinition.DefinitionsFactory(xmlDefinitions);
}
xmlDefinitions.resolveInheritances();
XmlDefinition xmlDefinition;
for(Iterator i = xmlDefinitions.getDefinitions().values().iterator(); i.hasNext(); this.definitionsFactory.putDefinition(new ComponentDefinition(xmlDefinition)))
xmlDefinition = (XmlDefinition)i.next();
} catch (NoSuchDefinitionException e) {
throw new ServletException("Error while loading Tiles Definitions");
}
}
}

@Override
public void destroy() {
this.instance.destroy();
this.definitionsFactory = null;
this.instance = null;
}

@Override
public DefinitionsFactoryConfig getConfig() {
return this.instance.getConfig();
}

@Override
public ComponentDefinition getDefinition(String name,
ServletRequest request, ServletContext servletContext)
throws NoSuchDefinitionException, DefinitionsFactoryException {
ComponentDefinition definition = this.instance.getDefinition(name, request, servletContext);
if(definition == null && this.definitionsFactory != null) {
definition = definitionsFactory.getDefinition(name, request, servletContext);
}
return definition;
}

@Override
public void init(DefinitionsFactoryConfig config,
ServletContext servletContext) throws DefinitionsFactoryException {
this.instance.init(config, servletContext);
}

@Override
public void setConfig(DefinitionsFactoryConfig config,
ServletContext servletContext) throws DefinitionsFactoryException {
this.instance.setConfig(config, servletContext);
}

@Override
public String toString() {
return this.instance.toString();
}
}

Creating the HotdeployListener


package com.blogspot.aigloss.hook.hotdeploy.portal;


import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;


import javax.servlet.ServletContext;


import org.apache.catalina.loader.WebappClassLoader;
import org.xml.sax.InputSource;


import com.blogspot.aigloss.hook.hotdeploy.servlet.HookHotdeployMainServlet;
import com.liferay.portal.deploy.hot.BaseHotDeployListener;
import com.liferay.portal.kernel.deploy.hot.HotDeployEvent;
import com.liferay.portal.kernel.deploy.hot.HotDeployException;
import com.liferay.portal.kernel.util.HttpUtil;
import com.liferay.portal.kernel.util.PortalClassLoaderUtil;




public class HookHotdeployListener extends BaseHotDeployListener {


public void invokeDeploy(HotDeployEvent event) throws HotDeployException {
ServletContext servletContext = event.getServletContext();


if(!isHookPlugin(servletContext)) {
return;
}
loadStrutsConfigs(event);
loadTilesConfigs(event);
addClassLoader(event.getServletContext());
loadSpringConfigs(event);
}


private void loadSpringConfigs(HotDeployEvent event) throws HotDeployException {
ServletContext servletContext = event.getServletContext();
try {
if(servletContext.getResource("/WEB-INF/applicationContext.xml") != null) {
HookHotdeployMainServlet.addSpringFile(servletContext.getRealPath("/WEB-INF/applicationContext.xml"));
}
} catch (MalformedURLException e) {
throwHotDeployException(event, "Error loading applicationContext.xml while registering hook for ", e);
}
}


protected void addClassLoader(ServletContext servletContext) {
WebappClassLoader webappClassLoader = (WebappClassLoader)PortalClassLoaderUtil.getClassLoader();
String classesUrl = servletContext.getRealPath("/WEB-INF/classes/")+"/";
String[] files = new File(classesUrl).list(new FilenameFilter() {
public boolean accept(File dir, String name) {
boolean accept = (name != null && name.endsWith(".class"));
if(!accept) {
File d = new File(dir, name);
if(d.isDirectory()) {
for(String file : d.list()) {
if(accept(d, file)) {
return true;
}
}
}
}
return accept;
}
});

if(files != null && files.length > 0) {
webappClassLoader.addRepository("file:///" + classesUrl);
}
String libUrl = servletContext.getRealPath("/WEB-INF/lib/")+"/";
files = new File(libUrl).list(new FilenameFilter() {
public boolean accept(File dir, String name) {
return (name != null && name.endsWith(".jar"));
}
});
if(files != null && files.length > 0) {
for(String file : files) {
webappClassLoader.addRepository("file:///" + libUrl + file);
}
}
}


protected void loadTilesConfigs(HotDeployEvent event) throws HotDeployException {
try {
String content = getFileContent("/WEB-INF/tiles-defs.xml", event.getServletContext());
if(content != null && content.length() > 0) {
HookHotdeployMainServlet.addTilesDefinitions(content);
}
} catch (IOException e) {
throwHotDeployException(event, "Error loading tiles-defs.xml while registering hook for ", e);
}
}


protected void loadStrutsConfigs(HotDeployEvent event) throws HotDeployException {
try {
String content = getFileContent("/WEB-INF/struts-config.xml", event.getServletContext());
if(content != null && content.length() > 0) {
HookHotdeployMainServlet.addStrutsConfigFile(content);
}
} catch (IOException e) {
throwHotDeployException(event, "Error loading struts-config.xml while registering hook for ", e);
}
}


private String getFileContent(String path, ServletContext servletContext) throws IOException {
String content = null;
URL strutsConfigFile = servletContext.getResource(path);
if(strutsConfigFile != null) {
InputStream input = null;
InputSource is = new InputSource(strutsConfigFile.toExternalForm());
input = strutsConfigFile.openStream();
is.setByteStream(input);
content = read(input);
}
return content;
}


private boolean isHookPlugin(ServletContext servletContext) {
String xml = null;
try {
xml = HttpUtil.URLtoString(servletContext.getResource("/WEB-INF/liferay-hook.xml"));
} catch (Exception e) {
//do nothing
}


return (xml != null);
}


private String read(InputStream is) {
StringBuilder res = new StringBuilder();
int c;
try {
while((c = is.read()) != -1) {
res.append((char)c);
}
} catch (IOException e) {
e.printStackTrace();
}
return res.toString();
}


public void invokeUndeploy(HotDeployEvent arg0) throws HotDeployException {
}
}
Deployment


Once you've package that code, just :
  1. copy the resulting JAR file in Liferay's ROOT/WEB-INF/lib directory
  2. in ROOT app's web.xml:
    • modify Main Servlet's class to be your new Main Servlet class
    • add/modify Main Servlet's initParam configFactory to have your new ModuleConfigFactory class as value
  3. add your new HotDeployListener to Liferay's portal-ext.properties
That's all. After following those steps, any struts-config.xml file located inside WEB-INF's directory of a hook will be parsed and loaded by Struts engine (overriding existing ones if necessary), at runtime, so that those configurations can be used from then on. Same for tiles configurations found in tiles-defs.xml file in the same directory, and Spring config files.

Thing to keep in mind
  • Be careful on using this features since other struts/tiles/spring definitions can be overridden accidentally.
  • Struts configurations (mappings) lookup methods are not synchronized. This means that if you hot deploy a hook and new struts configurations are loaded, there may be some running threads trying to use one of these configurations before their struts configuration information has been synchronized with changes.
Get the packaged JAR file and a sample hook here!

dissabte, 18 de juny del 2011

Oracle database not working without internet connection

Some days ago I came across a problem with an Oracle XE database that was not working when laptod didn't have connection to the internet: any application trying to connect to XE was spitting errors about unkown SIDs and stuff like that..
It was installed in a recent Linux installation...

If you fall into the same problem, check out the Loopback configuration for that Linux installation. In my case, just had to add the following line to my /etc/networks file:


  loopback 127.0.0.1

That solved the problem.

Hope this helps!

Liferay Portal Server: avoiding use of extension (Part 2)

Creating the skeleton for Zeep o'Tron
As told in my previous entry our objective relies on creating "HookHotDeployer that feeds our new methods in Liferay's Main Servlet so that this can load all the necessary stuff". So, first of all, we'll need to:

  1. Extend Liferay's Main Servlet class (com.liferay.portal.servlet.MainServlet), wich we will call HotdeployMainServlet from now on
  2. Create a HookHotDeployListener, extending com.liferay.portal.kernel.deploy.hot.BaseHotDeployListener


Both of them will have three main methods: one to load struts configurations, one to load tiles configurations, and one to load hook's classes and libraries into Liferay's classloader. Three of them will work the same way:

  1. The HookHotDeployer will check if there are any of these three elements to loaaddClassLoaderRepositoryd
  2. If so, it will retrieve the necessary information (the content of the corresponding files) and inform HotdeployMainServlet through some static methods
  3. In each request, HotdeployMainServlet will check if there are any configurations/classes to load, if so, it will do it before calling its parent class service method.

Adding classes to Liferay's classloader
To add hook's classes and libraries to Liferay's classloader we will create a method in HookHotDeployListener, wich I will call "addClassLoader", receiving the hook's servlet context, wich we can get from the event passad to HookHotDeployListener's invokeDeploy method.
In addClassloader, we will need to check if there are some class files under the WEB-INF/classes directory, and/or some jar files under the WEB-INF/lib directory. If so, we will add the classes dir as a new repository to the corresponding classloader (Liferay's one), and add those jar files, individually, as new repositories to that classloader too.
And that's it!
Let's put some code on those words...

/**
* Checks if there are classes and/or JARs within the Hook's classloader and adds them as new
* repositories to the current application classloader
*
* @param servletContext Hook's servlet context
*/
protected void addClassLoader(ServletContext servletContext) {
   WebappClassLoader webappClassLoader = (WebappClassLoader)PortalClassLoaderUtil.getClassLoader();
   String classesUrl = servletContext.getRealPath("/WEB-INF/classes/")+"/";
   String[] files = new File(classesUrl).list(new FilenameFilter() {
      public boolean accept(File dir, String name) {
         boolean accept = (name != null && name.endsWith(".class"));
         if(!accept) {
            File d = new File(dir, name);
            if(d.isDirectory()) {
               for(String file : d.list()) {
                  if(accept(d, file)) {
                     return true;
                  }
               }
            }
         }
         return accept;
      }
   });


   if(files != null && files.length > 0) {
      addClassLoaderRepository(webappClassLoader, classesUrl);
   }
   String libUrl = servletContext.getRealPath("/WEB-INF/lib/")+"/";
   files = new File(libUrl).list(new FilenameFilter() {
      public boolean accept(File dir, String name) {
         return (name != null && name.endsWith(".jar"));
      }
   });
   if(files != null && files.length > 0) {
      for(String file : files) {
         addClassLoaderRepository(webappClassLoader, libUrl + file);
      }
   }
}


/**
* Add a new repositry to Liferay's classloader if not already added
*
* @param webappClassLoader Liferay's classloader
* @param pathToRepository Path to the repository to be added
*/
protected void addClassLoaderRepository(WebappClassLoader webappClassLoader, String pathToRepository) {
   if(!classloaderRepositories.contains(pathToRepository)) {
      webappClassLoader.addRepository("file:///" + pathToRepository);
      classloaderRepositories.add(pathToRepository);
   }
}

You can put that code in the HookHotDeployer and call it whenever a hook is deployed.
As you've seen, we have'nt, already, extended Liferay's Main Servlet, it is beacuse that is only needed for the Struts and Tiles part of this module.

Adding and overwriting Struts configurations
To load and/or overwrite Liferays Struts configurations, we will let the new hotdeployer read those new configurations found in the deployed hook, and tell Main Servlet to apply them.
So the sequence is:

  1. A hook is hotdeployed and our HookHotdeployer reads that hook's struts-config file
  2. The HookHotdeployer extracts those configurations from the file, and sends them to our Main Servlet
  3. The new main servlet stores thos configurations and writes down that there are configurations to be loaded, through a static class variable called addStrutsConfigs, and what are those configurations, through a static variable called xmlStrutsConfigs.
  4. On the next request to the Main Servlet, it will check wheter there are new configurations to be loaded, and, if so, loads them using a method called addStrutsConfigFile


Before showing the code, I'll explain one important thing you should know:
When Liferay ends loading its struts configurations, those are freezed, so that they can not be changed later. To avoid this, we will implement our own Struts module config factory, and our own ModuleConfig. The new factory will only have to overwrite the method "createModuleConfig(String prefix)"
so that this returns our ModuleConfig. On the other hand, the new ModuleConfig will just override the method freeze so that it does just nothing.

Here is the code that reads the Hook's Struts configurations:


/**
* Loads struts configurations found in hook's WEB-INF/struts-config.xml file
* @param event
* @throws HotDeployException
*/
protected void loadStrutsConfigs(HotDeployEvent event) throws HotDeployException {
try {
String content = getFileContent("/WEB-INF/struts-config.xml", event.getServletContext());
if(content != null && content.length() > 0) {
HotdeployMainServlet.addStrutsConfigFile(content);
}
} catch (IOException e) {
throwHotDeployException(event, "Error loading struts-config.xml while registering hook for ", e);
}
}

As you can see, a static method addStrutsConfig is called on HotdeployMainServlet (our extension of Liferay's Main Servlet), this method is, actually, the responsible for loading those configs on Liferay's Struts contex:

protected synchronized void parseStrutsConfigs() throws ServletException {

try {
if(HotdeployMainServlet.addStrutsConfigs) {
Digester digester = initConfigDigester();
ModuleConfig moduleConfig = getModuleConfig();
digester.push(moduleConfig);
for(String is : xmlStrutsConfigs) {
try {
digester.parse(new ByteArrayInputStream(is.getBytes()));
} catch (Exception e) { 
throw new ServletException("Error while parsing file /WEB-INF/struts-config.xml", e);
}
digester.push(moduleConfig);
}
getServletContext().setAttribute("org.apache.struts.action.MODULE" + moduleConfig.getPrefix(), moduleConfig);


PortletRequestProcessor portletReqProcessor = (PortletRequestProcessor) getServletContext().getAttribute(WebKeys.PORTLET_STRUTS_PROCESSOR); 
if (portletReqProcessor == null) {
portletReqProcessor =
PortletRequestProcessor.getInstance(this, moduleConfig);


getServletContext().setAttribute(
WebKeys.PORTLET_STRUTS_PROCESSOR, portletReqProcessor);
}
}
} finally {
/*
* Whatever happens these struts config files don't have to be parsed ever again,
* since a parsing error would imply not cleaning xmlStrutsConfigs and that parsing error
* would happen again and again (every request would return that parsing error)
*/
xmlStrutsConfigs = null;
HotdeployMainServlet.addStrutsConfigs = false;
}
}

The condition "HotdeployMainServlet.addStrutsConfigs" wil be explained further on. By the moment, just let you know that it is read from a properties file that let's you tell the module wich configurations should be loaded or not, so that we can keep some control over zeep o'Tron.

Adding/overriding Tiles configurations
Following the same strategy as with Struts configurations the Hook Hotdeployer will send to our Main Servlet what is to be loaded.
So, in our hotdeployer we can add:


public static void addTilesDefinitions(String xmlConfig) {
if(xmlConfig != null) {
if(xmlTilesDefs == null) {
xmlTilesDefs = new ArrayList<String>();
}
xmlTilesDefs.add(xmlConfig);
HotdeployMainServlet.addTilesDefs = true;
HotdeployMainServlet.addConfigs = true;
}
}
And in our Main Servlet:

protected synchronized void parseTilesDefinitions() throws ServletException {
try {
if(HotdeployMainServlet.addTilesDefs) {
XmlDefinitionsSet xmlDefinitions = new XmlDefinitionsSet();
for(String file : xmlTilesDefs)
try
{
XmlParser xmlParser = new XmlParser();
xmlParser.setValidating(true);
xmlParser.parse(new ByteArrayInputStream(file.getBytes()), xmlDefinitions);
}
catch(Exception ex)
{
throw new ServletException("Error while parsing file\n " + file + "\n" + ex.getMessage(), ex);
}


DefinitionsFactory definitionsFactory = (DefinitionsFactory) getServletContext().getAttribute("org.apache.struts.tiles.DEFINITIONS_FACTORY");
if(definitionsFactory instanceof TilesDefinitionsFactory) {
((TilesDefinitionsFactory) definitionsFactory).addXmlDefinitions(xmlDefinitions);
} else {
TilesDefinitionsFactory factory = new TilesDefinitionsFactory(definitionsFactory);
factory.addXmlDefinitions(xmlDefinitions);
getServletContext().setAttribute("org.apache.struts.tiles.DEFINITIONS_FACTORY", factory);
}
((PortletRequestProcessor)getServletContext().getAttribute(WebKeys.PORTLET_STRUTS_PROCESSOR)).init(this, getModuleConfig());
}
} finally {
/*
* Whatever happens these tiles config files don't have to be parsed ever again,
* since a parsing error would imply not cleaning xmlTilesDefs and that parsing error
* would happen again and again (every request would return that parsing error)
*/
xmlTilesDefs = null;
HotdeployMainServlet.addTilesDefs = false;
}
}


And that's it! Following those headlines you will be able to modify/add classes, struts and spring configurations, and tiles definitions to Liferay Portal Server just by using hook, that can be hot deployed!

If you don't feel like getting those clues and pieces, and building up the code yourself, in the next and final entry for these series, you'll find the full code and deployment instructions.

Keep reading about this in Part 3!