001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002// The Molly framework is freely distributable under the terms of an 003// MIT-style license. For details, see the molly pages web site at: 004// http://www.mollypages.org/. Use, modify, have fun ! 005 006package fc.util.pagetemplate; 007 008import java.io.*; 009import java.util.*; 010 011import fc.util.*; 012import fc.io.*; 013import fc.jdbc.*; 014 015/** 016A page template is a simplified molly page, that runs in a non web environment. Good for creating 017dynamic web pages (for email campaigns, etc) from the command line. 018<p> 019There is another alternate/simpler template mechanism here: {@link fc.util.Template}. 020<p> 021The page template loads template files (can have any suffix but .template is suggested as 022opposed to .mp to keep the non-web distinction clear). The template file is compiled 023(uses the system temp directory for compilation) as needed and the resulting class file 024is loaded/run. If a template is changed, it is automatically recompiled, reloaded and 025rerun. If the template has a compilation error, that page remains unloaded until the 026error is fixed. 027<p> 028The output of the template is written to an output destination (could be a buffer, string, file, etc) 029and the invoker can then send that output to an email destination. 030<p> 031Template pages only use the system classpath for compilation. Pages can refer to any class 032found in the system classpath. 033<p> 034Here is some sample usage: 035<pre><code> 036TemplatePage pt = TemplateMgr.<b>getTemplate</b>("/path/to/file.template"); 037pt.<b>render</b>(new File("/path/to/output.file")); 038 039//To optionally send information to the page, you can say (before calling render): 040Map m = new HashMap(); 041m.put(...whatever..); 042pt.setContext(m); 043</code></pre> 044 045@author hursh jain 046*/ 047public final class TemplateMgr 048{ 049private static final boolean dbg = false; 050 051static Map pagemap = new HashMap(); 052static Log log; 053static File tmpdir; 054static String classpath; 055 056/** 057Returns a runnable page template, alias for the {@link getTemplate(String)} method. 058 059@param templateFile the full path to the template file 060*/ 061public static TemplatePage get(String templateFilePath) throws Exception 062 { 063 return getTemplate(new File(templateFilePath)); 064 } 065 066/** 067Returns a runnable page template, alias for the {@link getTemplate(File)} method. 068 069@param templateFile the full path to the template file 070*/ 071public static TemplatePage get(File templateFile) throws Exception 072 { 073 return getTemplate(templateFile); 074 } 075 076/** 077Returns a runnable page template. 078 079@param templateFile the full path to the template file 080*/ 081public static TemplatePage getTemplate(String templateFilePath) throws Exception 082 { 083 return getTemplate(new File(templateFilePath)); 084 } 085 086/** 087Returns a runnable page template. 088 089@param templateFile the template file 090*/ 091public static TemplatePage getTemplate(File templateFile) throws Exception 092 { 093 if (! templateFile.exists()) 094 throw new IllegalArgumentException("The specified template file " + templateFile + "] does not exist. How am I supposed to load it, eh ?"); 095 096 StringBuilder buf = new StringBuilder(1028); 097 098 log = Log.getDefault(); 099 classpath = System.getProperty("java.class.path"); 100 tmpdir = new File(System.getProperty("java.io.tmpdir")); 101 102 TemplatePage page = null; 103 104 final CharArrayWriter fbuf = new CharArrayWriter(128); 105 106 String classname = getClassNameFromPageName(templateFile); 107 108 File javafile = new File(tmpdir, classname + ".java"); 109 File classfile = new File(tmpdir, classname + ".class"); 110 111 if (dbg) { 112 System.out.println( 113 String.format("tmpdir=%s, TemplatePage=%s, javafile=%s, classfile=%s\n", 114 tmpdir, templateFile, javafile,classfile)); 115 } 116 117 synchronized (TemplateMgr.class) 118 { 119 long page_modified = templateFile.lastModified(); 120 long java_modified = javafile.lastModified(); //returns 0 if !exist 121 long class_modified = classfile.lastModified(); //returns 0 if !exist 122 123 if (dbg) 124 { 125 System.out.format( 126 " %-20s %10d\n %-20s %10d\n %-20s %10d\n", 127 "Modified: page:", page_modified, 128 "java:", java_modified, 129 "class:", class_modified); 130 } 131 132 if ( java_modified == 0L || page_modified > java_modified) 133 { 134 if (dbg) System.out.format("page_mod > java_mod, parsing the page.........\n"); 135 TemplateParser parser = new TemplateParser(templateFile, javafile, classname, log); 136 137 log.bug("PARSING page:", javafile.getPath()); 138 139 try { 140 parser.parse(); 141 } 142 catch (IOException e) { 143 //the parser may write a partially/badly written file if the parse failed. 144 if (javafile.exists()) { 145 javafile.delete(); 146 } 147 throw e; //rethrow the parse exception 148 } 149 150 java_modified = javafile.lastModified(); //since newly parsed 151 } 152 153 boolean forceReload = false; 154 //Java source could be generated or hacked by hand 155 // if nothing needs compiling, then we still need to load the 156 // page the first time it's accessed 157 if ( class_modified == 0L || java_modified > class_modified) 158 { 159 if (dbg) System.out.format("java_mod > class_mod, compiling the java source.........\n"); 160 161 log.bug("COMPILING page:", javafile.getPath()); 162 163 //src_encoding can be null, that's fine. 164 TemplateCompiler pc = new TemplateCompiler(javafile, classpath); 165 166 if (! pc.compile()) 167 throw new TemplateParseException(pc.getError()); 168 169 forceReload = true; //since we recompiled, we reload even if 170 //page exists in the page cache 171 } 172 173 final boolean page_in_map = pagemap.containsKey(templateFile.getAbsolutePath()); 174 175 if (forceReload || ! page_in_map) 176 { 177 TemplateClassLoader loader = new TemplateClassLoader(log); 178 Class c = loader.loadClass(TemplatePage.PACKAGE_NAME + "." + classfile.getCanonicalPath()); 179 180 if (dbg) System.out.println("class = " + c); 181 page = (TemplatePage) c.newInstance(); 182 if (dbg) System.out.println("page = " + page); 183 page.setSourcePath(templateFile); 184 185 if (page_in_map) { 186 TemplatePage oldpage = (TemplatePage) pagemap.get(templateFile.getAbsolutePath()); 187 //notused 188 //oldpage.destroy(); 189 } 190 191 pagemap.put(templateFile.getAbsolutePath(), page); 192 } 193 else{ 194 page = (TemplatePage) pagemap.get(templateFile.getAbsolutePath()); 195 } 196 } 197 198 if (dbg) log.bug("Returning PAGE=", page); 199 return page; 200 } 201 202/* 203a.template -> a_template 204b.template -> b_template 205c.template -> c_template 206p/q.template -> p_q.template 2075!#.template -> 5__template ---> name clash 2085!!.template -> 5__template ---> name clash 209 210So a simplistic mapping(any bad classname chars--> '_') will not work. 211We should therefore hex-encode every special char..not sure if doing this right now. 212*/ 213static String getClassNameFromPageName(File templateFile) throws IOException 214 { 215 if (dbg) System.out.println("getClassNameFromPageName(), page=["+templateFile+"]"); 216 217 StringBuilder buf = new StringBuilder(); 218 char c; 219 220 String path = templateFile.getAbsolutePath(); 221 222 c = path.charAt(0); 223 if (c == '/' || c == File.separatorChar) { 224 buf.append("_"); 225 } 226 else if (! Character.isJavaIdentifierStart(c)) { 227 buf.append(Integer.toHexString(c)); 228 } 229 else { 230 buf.append(c); 231 } 232 233 for (int n = 1; n < path.length(); n++) 234 { 235 c = path.charAt(n); 236 237 if (c == '/' || c == File.separatorChar || c == '.') { 238 buf.append("_"); 239 } 240 else { 241 if (! Character.isJavaIdentifierPart(c)) 242 buf.append(Integer.toHexString(c)); 243 else 244 buf.append(c); 245 } 246 } 247 248 String str = buf.toString(); 249 250 if (str.length() > 1 && str.startsWith("_")) { 251 str = str.substring(1); //starting with _ is ugly 252 } 253 254 return str; 255 } 256 257 258/** 259Interactive page manager use for testing. 260*/ 261public static void main (String args[]) throws Exception 262 { 263 Args myargs = new Args(args); 264 myargs.setUsage("java fc.util.template.TemplateMgr \n" 265 + "-file <path-to-page-template-file>\n" 266 + "-out <path-to-output-file>\n" 267 + " ------------------- optional ------------------ \n" 268 + "-conf <db-config file>\n" 269 + "-dbg|debug (turns log level to debug)\n" 270 ); 271 272 log = Log.getDefault(); 273 log.setLevel(Log.INFO); 274 275 if (myargs.flagExists("dbg") || myargs.flagExists("debug")) { 276 log.setLevel(Log.DEBUG); 277 } 278 279 Map m = new HashMap(); 280 281 String conf = myargs.get("conf"); 282 if (conf != null) { 283 PropertyMgr props = new FilePropertyMgr(new File(conf)); 284 ConnectionMgr cmgr = new SimpleConnectionMgr(props); 285 m.put("cmgr", cmgr); 286 } 287 288 TemplatePage pt = TemplateMgr.getTemplate(myargs.getRequired("file")); 289 pt.setContext(m); 290 pt.render(new File(myargs.getRequired("out"))); 291 } //main 292 293}