// THIS SOFTWARE IS PROVIDED BY SOFTARIS PTY.LTD. AND OTHER METABOSS
// CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTARIS PTY.LTD.
// OR OTHER METABOSS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF SOFTARIS PTY.LTD. OR OTHER METABOSS CONTRIBUTORS ARE ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Copyright 2000-2005 Softaris Pty.Ltd. All Rights Reserved.
package com.metaboss.sdlctools.services.jdktools.impl;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jamon.TemplateProcessor;
import org.jamon.codegen.JamonParseException;
import org.jamon.emit.EmitMode;
import com.metaboss.enterprise.bs.BSException;
import com.metaboss.enterprise.bs.BSNamingAndDirectoryServiceInvocationException;
import com.metaboss.enterprise.bs.BSUnexpectedProgramConditionException;
import com.metaboss.javatemplate.JavaTemplateContext;
import com.metaboss.javatemplate.JavaTemplateContextMapImpl;
import com.metaboss.javatemplate.JavaTemplateException;
import com.metaboss.sdlctools.services.jdktools.BSJamonTemplateProcessor;
import com.metaboss.sdlctools.services.jdktools.BSJavaCompiler;
import com.metaboss.sdlctools.services.jdktools.CompilationResult;
import com.metaboss.sdlctools.services.jdktools.MergeResult;
import com.metaboss.sdlctools.services.jdktools.SourceType;
import com.metaboss.util.DirectoryUtils;
import com.metaboss.util.JarClassLoader;
import com.metaboss.util.StringUtils;
/** This class implements template merging functionality based on Jamon */
public class BSJamonTemplateProcessorImpl implements BSJamonTemplateProcessor
{
// Commons Logging instance.
private static final Log sLogger = LogFactory.getLog(BSJamonTemplateProcessorImpl.class);
/** Thread safe store of template classes used before.
* We need to specify parent classloader so the template classes will be able to
* address Jamon library */
private JarClassLoader mJarClassLoader = null;
// Default constructor
public BSJamonTemplateProcessorImpl() throws BSException
{
try
{
mJarClassLoader = new JarClassLoader(BSJamonTemplateProcessorImpl.class.getClassLoader());
}
catch(IOException e)
{
throw new BSException("Unable to initialise Jamon template processor",e);
}
}
/* Merges given template with given set of properties. */
public MergeResult mergeTemplate(String pSourceTemplate, String pSourceTemplateName, java.util.Map pContextMap) throws BSException
{
if (!mJarClassLoader.hasClass(pSourceTemplateName))
{
sLogger.debug("Template " + pSourceTemplateName + " not found in templates cache. Performing full template compilation before running it.");
try
{
CompilationResult lCompilationResult = compileTemplate(pSourceTemplate, pSourceTemplateName);;
if (!lCompilationResult.isSuccessful())
{
// Return error in form of merge failure
return MergeResult.createMergeFailure(lCompilationResult.getCompilerPrintout());
}
mJarClassLoader.addJar(lCompilationResult.getResultJar());
}
catch(IOException e)
{
throw new BSException("Jamon template source processing error. Template class name: " + pSourceTemplateName, e);
}
}
else
sLogger.debug("Template " + pSourceTemplateName + " found in templates cache. There is no need to perform full template compilation before running it.");
try
{
Class lTemplateClass = mJarClassLoader.loadClass(pSourceTemplateName);
Method lRenderMethod = lTemplateClass.getMethod("render", new Class[] { java.io.Writer.class, JavaTemplateContext.class});
Object lTemplateInstance = lTemplateClass.newInstance();
JavaTemplateContext lContext = new JavaTemplateContextMapImpl(pContextMap);
StringWriter lStringWriter = new StringWriter();
lRenderMethod.invoke(lTemplateInstance, new Object[] { lStringWriter, lContext});
return MergeResult.createMergeSuccess(lStringWriter.toString());
}
catch(NoSuchMethodException e)
{
// Generator always generates render method - so this error is unexpected
throw new BSUnexpectedProgramConditionException("Jamon template class loading error. Template class name: " + pSourceTemplateName, e);
}
catch(IllegalAccessException e)
{
// Generator always generates public constructor and render method - so this error is unexpected
throw new BSUnexpectedProgramConditionException("Jamon template rendering error. Template class name: " + pSourceTemplateName, e);
}
catch(ClassNotFoundException e)
{
// We do check if class is loaded in the jar loader before loading it in VM, so this is unexpected
throw new BSUnexpectedProgramConditionException("Java class loading error. Template class name: " + pSourceTemplateName, e);
}
catch(InstantiationException e)
{
// Generator always generates public constructor - so this error is unexpected
throw new BSUnexpectedProgramConditionException("Jamon template instantiation error. Template class name: " + pSourceTemplateName, e);
}
catch(InvocationTargetException e)
{
Throwable lTargetException = e.getTargetException();
// Allow for exceptions from inside template itself. These are arriving as java.io.WriteAbortedException
if (lTargetException instanceof java.io.WriteAbortedException)
lTargetException = ((java.io.WriteAbortedException)lTargetException).detail;
sLogger.error("Jamon template execution error",lTargetException);
// Return error in form of merge failure
return MergeResult.createMergeFailure(lTargetException.toString());
}
catch(JavaTemplateException e)
{
sLogger.error("Jamon template execution error",e);
// Return error in form of merge failure
return MergeResult.createMergeFailure(e.getMessage());
}
}
// Helper method. Does everything necessary to create executable jar for the template, loads it and returns it
private CompilationResult compileTemplate(String pSourceTemplate, String pSourceTemplateName) throws BSException
{
// Step 1. Create template file in the temporary directory
String lBaseDir = DirectoryUtils.getUniqueTempDirectoryAbsolutePath();
sLogger.debug("Template " + pSourceTemplateName + " will be generated and built in " + lBaseDir + " directory.");
String lTemplateSourceRootDirPath = lBaseDir + File.separator + "template";
File lTemplateSourceRootDir = new File(lTemplateSourceRootDirPath);
String lTemplateFileRelativeName = StringUtils.replace(pSourceTemplateName,".",File.separator) + ".jamon";
String lTemplateFileAbsoluteName = lTemplateSourceRootDirPath + File.separator + lTemplateFileRelativeName;
File lTemplateFile = new File(lTemplateFileAbsoluteName);
FileWriter lFileWriter = null;
PrintWriter lWriter = null;
try
{
DirectoryUtils.ensureNewCleanDirectory(lTemplateFile.getParentFile().getAbsolutePath());
lWriter = new PrintWriter(lFileWriter = new FileWriter(lTemplateFile));
// This bit is to pass standard parameters
lWriter.println("<%args>");
lWriter.println("com.metaboss.javatemplate.JavaTemplateContext pContext;");
lWriter.println("</%args>");
// This bit is the front of the wrapper used to catch all sorts of exceptions
// Jamon's generator only allows to throw java.io.IOExceptions out of render methods. Not only it
// forces template writers to write lots of exception handling code, it also forces everyone to somehow
// "squese" all checked exceptions into the java.io.Exception. We add automatically code to do that
// Any checked exception will automatically be packaged into the java.io.WriteAbortedException
// (yes we could have created the dedicated one, but this one seemed to have almost right name).
// The renderer (see somewhere in this file) will expect this kind of exception and interpret it's contents
lWriter.println("<%java>");
lWriter.println("try");
lWriter.println("{");
lWriter.println("</%java>");
lWriter.print(pSourceTemplate);
// This bit is the bottom of the wrapper used to catch all sorts of exceptions sorts of exceptions
lWriter.println("<%java>");
lWriter.println("}");
lWriter.println("catch (Exception e)");
lWriter.println("{");
lWriter.println(" throw new java.io.WriteAbortedException(\"Exception caught during rendering of the '" + pSourceTemplateName + "' template.\",e);");
lWriter.println("}");
lWriter.println("</%java>");
}
catch(IOException e)
{
throw new BSException("Error while preparing template for compilation",e);
}
finally
{
if (lFileWriter != null)
{
try
{
lFileWriter.flush();
}
catch(IOException e)
{
// Ignore
}
try
{
lFileWriter.close();
}
catch(IOException e)
{
// Ignore
}
}
if (lWriter != null)
{
lWriter.flush();
lWriter.close();
}
}
// Step 2. Invoke Jamon java class generator and generate java source. Use inprocess invocation, so it is faster
String lGeneratedJavaCodeDirPath = lBaseDir + File.separator + "gensrc";
File lGeneratedJavaCodeDir = new File(lGeneratedJavaCodeDirPath);
try
{
DirectoryUtils.ensureNewCleanDirectory(lGeneratedJavaCodeDirPath);
new TemplateProcessor(lGeneratedJavaCodeDir, lTemplateSourceRootDir, TemplateProcessor.class.getClassLoader(),EmitMode.STANDARD).generateSource(lTemplateFileRelativeName);
}
catch(JamonParseException e)
{
throw new BSException("Parsing error while generating java source from the '" + pSourceTemplateName + "' template.",e);
}
catch(IOException e)
{
throw new BSException("IO Error while generating java source from the '" + pSourceTemplateName + "' template.",e);
}
// Step 3. Compile generated source
try
{
Context lContext = new InitialContext();
BSJavaCompiler lJavaCompiler = (BSJavaCompiler)lContext.lookup(BSJavaCompiler.COMPONENT_URL);
CompilationResult lCompilationResult = lJavaCompiler.compileLocalSource(lGeneratedJavaCodeDirPath, SourceType.METABOSS_DEVTIME);
if (lCompilationResult.isSuccessful())
{
// We have reached the end of last step and everything is still successfull
// we can now safely delete the temporary directory
DirectoryUtils.deleteDirectory(new File(lBaseDir));
}
return lCompilationResult;
}
catch(IOException e)
{
throw new BSException("Error while compiling generated source.",e);
}
catch(NamingException e)
{
throw new BSNamingAndDirectoryServiceInvocationException("Error while compiling generated source.",e);
}
}
}
|