001: // THIS SOFTWARE IS PROVIDED BY SOFTARIS PTY.LTD. AND OTHER METABOSS
002: // CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
003: // BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
004: // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SOFTARIS PTY.LTD.
005: // OR OTHER METABOSS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
006: // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
007: // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
008: // OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
009: // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
010: // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
011: // EVEN IF SOFTARIS PTY.LTD. OR OTHER METABOSS CONTRIBUTORS ARE ADVISED OF THE
012: // POSSIBILITY OF SUCH DAMAGE.
013: //
014: // Copyright 2000-2005 © Softaris Pty.Ltd. All Rights Reserved.
015: package com.metaboss.sdlctools.services.jdktools.impl;
016:
017: import java.io.File;
018: import java.io.FileWriter;
019: import java.io.IOException;
020: import java.io.PrintWriter;
021: import java.io.StringWriter;
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024:
025: import javax.naming.Context;
026: import javax.naming.InitialContext;
027: import javax.naming.NamingException;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.jamon.TemplateProcessor;
032: import org.jamon.codegen.JamonParseException;
033: import org.jamon.emit.EmitMode;
034:
035: import com.metaboss.enterprise.bs.BSException;
036: import com.metaboss.enterprise.bs.BSNamingAndDirectoryServiceInvocationException;
037: import com.metaboss.enterprise.bs.BSUnexpectedProgramConditionException;
038: import com.metaboss.javatemplate.JavaTemplateContext;
039: import com.metaboss.javatemplate.JavaTemplateContextMapImpl;
040: import com.metaboss.javatemplate.JavaTemplateException;
041: import com.metaboss.sdlctools.services.jdktools.BSJamonTemplateProcessor;
042: import com.metaboss.sdlctools.services.jdktools.BSJavaCompiler;
043: import com.metaboss.sdlctools.services.jdktools.CompilationResult;
044: import com.metaboss.sdlctools.services.jdktools.MergeResult;
045: import com.metaboss.sdlctools.services.jdktools.SourceType;
046: import com.metaboss.util.DirectoryUtils;
047: import com.metaboss.util.JarClassLoader;
048: import com.metaboss.util.StringUtils;
049:
050: /** This class implements template merging functionality based on Jamon */
051: public class BSJamonTemplateProcessorImpl implements
052: BSJamonTemplateProcessor {
053: // Commons Logging instance.
054: private static final Log sLogger = LogFactory
055: .getLog(BSJamonTemplateProcessorImpl.class);
056:
057: /** Thread safe store of template classes used before.
058: * We need to specify parent classloader so the template classes will be able to
059: * address Jamon library */
060: private JarClassLoader mJarClassLoader = null;
061:
062: // Default constructor
063: public BSJamonTemplateProcessorImpl() throws BSException {
064: try {
065: mJarClassLoader = new JarClassLoader(
066: BSJamonTemplateProcessorImpl.class.getClassLoader());
067: } catch (IOException e) {
068: throw new BSException(
069: "Unable to initialise Jamon template processor", e);
070: }
071: }
072:
073: /* Merges given template with given set of properties. */
074: public MergeResult mergeTemplate(String pSourceTemplate,
075: String pSourceTemplateName, java.util.Map pContextMap)
076: throws BSException {
077: if (!mJarClassLoader.hasClass(pSourceTemplateName)) {
078: sLogger
079: .debug("Template "
080: + pSourceTemplateName
081: + " not found in templates cache. Performing full template compilation before running it.");
082: try {
083: CompilationResult lCompilationResult = compileTemplate(
084: pSourceTemplate, pSourceTemplateName);
085: ;
086: if (!lCompilationResult.isSuccessful()) {
087: // Return error in form of merge failure
088: return MergeResult
089: .createMergeFailure(lCompilationResult
090: .getCompilerPrintout());
091: }
092: mJarClassLoader.addJar(lCompilationResult
093: .getResultJar());
094: } catch (IOException e) {
095: throw new BSException(
096: "Jamon template source processing error. Template class name: "
097: + pSourceTemplateName, e);
098: }
099: } else
100: sLogger
101: .debug("Template "
102: + pSourceTemplateName
103: + " found in templates cache. There is no need to perform full template compilation before running it.");
104:
105: try {
106: Class lTemplateClass = mJarClassLoader
107: .loadClass(pSourceTemplateName);
108: Method lRenderMethod = lTemplateClass.getMethod("render",
109: new Class[] { java.io.Writer.class,
110: JavaTemplateContext.class });
111: Object lTemplateInstance = lTemplateClass.newInstance();
112:
113: JavaTemplateContext lContext = new JavaTemplateContextMapImpl(
114: pContextMap);
115: StringWriter lStringWriter = new StringWriter();
116: lRenderMethod.invoke(lTemplateInstance, new Object[] {
117: lStringWriter, lContext });
118: return MergeResult.createMergeSuccess(lStringWriter
119: .toString());
120: } catch (NoSuchMethodException e) {
121: // Generator always generates render method - so this error is unexpected
122: throw new BSUnexpectedProgramConditionException(
123: "Jamon template class loading error. Template class name: "
124: + pSourceTemplateName, e);
125: } catch (IllegalAccessException e) {
126: // Generator always generates public constructor and render method - so this error is unexpected
127: throw new BSUnexpectedProgramConditionException(
128: "Jamon template rendering error. Template class name: "
129: + pSourceTemplateName, e);
130: } catch (ClassNotFoundException e) {
131: // We do check if class is loaded in the jar loader before loading it in VM, so this is unexpected
132: throw new BSUnexpectedProgramConditionException(
133: "Java class loading error. Template class name: "
134: + pSourceTemplateName, e);
135: } catch (InstantiationException e) {
136: // Generator always generates public constructor - so this error is unexpected
137: throw new BSUnexpectedProgramConditionException(
138: "Jamon template instantiation error. Template class name: "
139: + pSourceTemplateName, e);
140: } catch (InvocationTargetException e) {
141: Throwable lTargetException = e.getTargetException();
142: // Allow for exceptions from inside template itself. These are arriving as java.io.WriteAbortedException
143: if (lTargetException instanceof java.io.WriteAbortedException)
144: lTargetException = ((java.io.WriteAbortedException) lTargetException).detail;
145: sLogger.error("Jamon template execution error",
146: lTargetException);
147: // Return error in form of merge failure
148: return MergeResult.createMergeFailure(lTargetException
149: .toString());
150: } catch (JavaTemplateException e) {
151: sLogger.error("Jamon template execution error", e);
152: // Return error in form of merge failure
153: return MergeResult.createMergeFailure(e.getMessage());
154: }
155: }
156:
157: // Helper method. Does everything necessary to create executable jar for the template, loads it and returns it
158: private CompilationResult compileTemplate(String pSourceTemplate,
159: String pSourceTemplateName) throws BSException {
160: // Step 1. Create template file in the temporary directory
161: String lBaseDir = DirectoryUtils
162: .getUniqueTempDirectoryAbsolutePath();
163: sLogger.debug("Template " + pSourceTemplateName
164: + " will be generated and built in " + lBaseDir
165: + " directory.");
166: String lTemplateSourceRootDirPath = lBaseDir + File.separator
167: + "template";
168: File lTemplateSourceRootDir = new File(
169: lTemplateSourceRootDirPath);
170: String lTemplateFileRelativeName = StringUtils.replace(
171: pSourceTemplateName, ".", File.separator)
172: + ".jamon";
173: String lTemplateFileAbsoluteName = lTemplateSourceRootDirPath
174: + File.separator + lTemplateFileRelativeName;
175: File lTemplateFile = new File(lTemplateFileAbsoluteName);
176: FileWriter lFileWriter = null;
177: PrintWriter lWriter = null;
178: try {
179: DirectoryUtils.ensureNewCleanDirectory(lTemplateFile
180: .getParentFile().getAbsolutePath());
181: lWriter = new PrintWriter(lFileWriter = new FileWriter(
182: lTemplateFile));
183: // This bit is to pass standard parameters
184: lWriter.println("<%args>");
185: lWriter
186: .println("com.metaboss.javatemplate.JavaTemplateContext pContext;");
187: lWriter.println("</%args>");
188:
189: // This bit is the front of the wrapper used to catch all sorts of exceptions
190: // Jamon's generator only allows to throw java.io.IOExceptions out of render methods. Not only it
191: // forces template writers to write lots of exception handling code, it also forces everyone to somehow
192: // "squese" all checked exceptions into the java.io.Exception. We add automatically code to do that
193: // Any checked exception will automatically be packaged into the java.io.WriteAbortedException
194: // (yes we could have created the dedicated one, but this one seemed to have almost right name).
195: // The renderer (see somewhere in this file) will expect this kind of exception and interpret it's contents
196: lWriter.println("<%java>");
197: lWriter.println("try");
198: lWriter.println("{");
199: lWriter.println("</%java>");
200:
201: lWriter.print(pSourceTemplate);
202:
203: // This bit is the bottom of the wrapper used to catch all sorts of exceptions sorts of exceptions
204: lWriter.println("<%java>");
205: lWriter.println("}");
206: lWriter.println("catch (Exception e)");
207: lWriter.println("{");
208: lWriter
209: .println(" throw new java.io.WriteAbortedException(\"Exception caught during rendering of the '"
210: + pSourceTemplateName + "' template.\",e);");
211: lWriter.println("}");
212: lWriter.println("</%java>");
213: } catch (IOException e) {
214: throw new BSException(
215: "Error while preparing template for compilation", e);
216: } finally {
217: if (lFileWriter != null) {
218: try {
219: lFileWriter.flush();
220: } catch (IOException e) {
221: // Ignore
222: }
223: try {
224: lFileWriter.close();
225: } catch (IOException e) {
226: // Ignore
227: }
228: }
229: if (lWriter != null) {
230: lWriter.flush();
231: lWriter.close();
232: }
233: }
234:
235: // Step 2. Invoke Jamon java class generator and generate java source. Use inprocess invocation, so it is faster
236: String lGeneratedJavaCodeDirPath = lBaseDir + File.separator
237: + "gensrc";
238: File lGeneratedJavaCodeDir = new File(lGeneratedJavaCodeDirPath);
239: try {
240: DirectoryUtils
241: .ensureNewCleanDirectory(lGeneratedJavaCodeDirPath);
242: new TemplateProcessor(lGeneratedJavaCodeDir,
243: lTemplateSourceRootDir, TemplateProcessor.class
244: .getClassLoader(), EmitMode.STANDARD)
245: .generateSource(lTemplateFileRelativeName);
246: } catch (JamonParseException e) {
247: throw new BSException(
248: "Parsing error while generating java source from the '"
249: + pSourceTemplateName + "' template.", e);
250: } catch (IOException e) {
251: throw new BSException(
252: "IO Error while generating java source from the '"
253: + pSourceTemplateName + "' template.", e);
254: }
255:
256: // Step 3. Compile generated source
257: try {
258: Context lContext = new InitialContext();
259: BSJavaCompiler lJavaCompiler = (BSJavaCompiler) lContext
260: .lookup(BSJavaCompiler.COMPONENT_URL);
261: CompilationResult lCompilationResult = lJavaCompiler
262: .compileLocalSource(lGeneratedJavaCodeDirPath,
263: SourceType.METABOSS_DEVTIME);
264: if (lCompilationResult.isSuccessful()) {
265: // We have reached the end of last step and everything is still successfull
266: // we can now safely delete the temporary directory
267: DirectoryUtils.deleteDirectory(new File(lBaseDir));
268: }
269: return lCompilationResult;
270: } catch (IOException e) {
271: throw new BSException(
272: "Error while compiling generated source.", e);
273: } catch (NamingException e) {
274: throw new BSNamingAndDirectoryServiceInvocationException(
275: "Error while compiling generated source.", e);
276: }
277: }
278: }
|