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    }