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
006 package fc.util.pagetemplate;
007
008 import java.io.*;
009 import java.util.*;
010
011 import fc.util.*;
012 import fc.io.*;
013 import fc.jdbc.*;
014
015 /**
016 A page template is a simplified molly page, that runs in a non web environment. Good for creating
017 dynamic web pages (for email campaigns, etc) from the command line.
018 <p>
019 There is another alternate/simpler template mechanism here: {@link fc.util.Template}.
020 <p>
021 The page template loads template files (can have any suffix but .template is suggested as
022 opposed 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
024 is loaded/run. If a template is changed, it is automatically recompiled, reloaded and
025 rerun. If the template has a compilation error, that page remains unloaded until the
026 error is fixed.
027 <p>
028 The output of the template is written to an output destination (could be a buffer, string, file, etc)
029 and the invoker can then send that output to an email destination.
030 <p>
031 Template pages only use the system classpath for compilation. Pages can refer to any class
032 found in the system classpath.
033 <p>
034 Here is some sample usage:
035 <pre><code>
036 TemplatePage pt = TemplateMgr.<b>getTemplate</b>("/path/to/file.template");
037 pt.<b>render</b>(new File("/path/to/output.file"));
038
039 //To optionally send information to the page, you can say (before calling render):
040 Map m = new HashMap();
041 m.put(...whatever..);
042 pt.setContext(m);
043 </code></pre>
044
045 @author hursh jain
046 */
047 public final class TemplateMgr
048 {
049 private static final boolean dbg = false;
050
051 static Map pagemap = new HashMap();
052 static Log log;
053 static File tmpdir;
054 static String classpath;
055
056 /**
057 Returns a runnable page template, alias for the {@link getTemplate(String)} method.
058
059 @param templateFile the full path to the template file
060 */
061 public static TemplatePage get(String templateFilePath) throws Exception
062 {
063 return getTemplate(new File(templateFilePath));
064 }
065
066 /**
067 Returns a runnable page template, alias for the {@link getTemplate(File)} method.
068
069 @param templateFile the full path to the template file
070 */
071 public static TemplatePage get(File templateFile) throws Exception
072 {
073 return getTemplate(templateFile);
074 }
075
076 /**
077 Returns a runnable page template.
078
079 @param templateFile the full path to the template file
080 */
081 public static TemplatePage getTemplate(String templateFilePath) throws Exception
082 {
083 return getTemplate(new File(templateFilePath));
084 }
085
086 /**
087 Returns a runnable page template.
088
089 @param templateFile the template file
090 */
091 public 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 /*
203 a.template -> a_template
204 b.template -> b_template
205 c.template -> c_template
206 p/q.template -> p_q.template
207 5!#.template -> 5__template ---> name clash
208 5!!.template -> 5__template ---> name clash
209
210 So a simplistic mapping(any bad classname chars--> '_') will not work.
211 We should therefore hex-encode every special char..not sure if doing this right now.
212 */
213 static 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 /**
259 Interactive page manager use for testing.
260 */
261 public 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 }