TemplateServlet.java

/*
 * $Source$
 * $Revision$
 *
 * Copyright (C) 2000 Tim Joyce
 *
 * Part of Melati (http://melati.org), a framework for the rapid
 * development of clean, maintainable web applications.
 *
 * Melati is free software; Permission is granted to copy, distribute
 * and/or modify this software under the terms either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version,
 *
 *    or
 *
 * b) any version of the Melati Software License, as published
 *    at http://melati.org
 *
 * You should have received a copy of the GNU General Public License and
 * the Melati Software License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 * GNU General Public License and visit http://melati.org to obtain the
 * Melati Software License.
 *
 * Feel free to contact the Developers of Melati (http://melati.org),
 * if you would like to work out a different arrangement than the options
 * outlined here.  It is our intention to allow Melati to be used by as
 * wide an audience as possible.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Contact details for copyright holder:
 *
 *     Tim Joyce <timj At paneris.org>
 *     http://paneris.org/
 *     68 Sandbanks Rd, Poole, Dorset. BH14 8BY. UK
 */

package org.melati.servlet;

import java.io.PrintWriter;
import java.io.StringWriter;

import javax.servlet.ServletException;
import javax.servlet.ServletConfig;

import org.melati.Melati;
import org.melati.util.MelatiWriter;
import org.melati.template.ServletTemplateEngine;
import org.melati.template.ServletTemplateContext;
import org.melati.template.MultipartTemplateContext;
import org.melati.template.Template;

/**
 * Base class to use Melati with a Template Engine.
 * To create your own servlet simply extend this class, 
 * overriding the <code>doTemplateRequest</code> method.
 *
 * @author Tim Joyce
 * $Revision$
 */
public abstract class TemplateServlet extends PoemServlet {

  /**
   * Eclipse generated. 
   */
  private static final long serialVersionUID = -5228388231472549208L;
  
  // the template engine
  protected ServletTemplateEngine templateEngine;

  /**
   * Initialise the template engine.
   *
   * @param config a <code>ServletConfig</code>
   * @throws ServletException if the ServletTemplateEngine has a problem
   */
  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    templateEngine = melatiConfig.getServletTemplateEngine();
    templateEngine.init(melatiConfig, this);
  }

  /**
   * Set the ServletTemplateEngine and ServletTemplateContext in our Melati.
   * This allows us to parse any uploaded files before we enter
   * our PoemSession (so we don't hang on to transactions
   * unnecessarily).
   *
   * @param melati the current Melati
   * @throws Exception if anything goes wrong
   */
  protected void prePoemSession(Melati melati) throws Exception {
    // for this request, set the Initialised Template Engine
    melati.setTemplateEngine(templateEngine);
    ServletTemplateContext templateContext =
            templateEngine.getServletTemplateContext(melati);

    melati.setTemplateContext(templateContext);
  }

  protected void doPoemRequest(Melati melati) throws Exception {
    ServletTemplateContext templateContext = melati.getServletTemplateContext();
    // If we have a multi-part form, we use a different template context
    // which allows us to access the uploaded files as well as fields.
    // This used to be in prePoemSession, but the use case was pretty thin,
    // the main Adaptor is PoemFileFormDataAdaptor, which needs to be in session.
    String contentType = melati.getRequest().getHeader("content-type");
    if (contentType != null && contentType.length() >= 19 &&
        contentType.substring(0,19).equalsIgnoreCase("multipart/form-data")) {
      templateContext =
        new MultipartTemplateContext(melati, templateContext);
    }

    templateContext.put("melati", melati);
    templateContext.put("ml", melati.getMarkupLanguage());

    String templateName = doTemplateRequest(melati,templateContext);

    // only expand a template if we have one (it could be a redirect)
    if (templateName != null) {
      templateName = addExtension(templateName);
      templateEngine.expandTemplate(melati.getWriter(), 
                                    templateName,
                                    templateContext);
    }
  }
  
  /**
   * The template extension is added in an overridable method
   * to allow the application developer to specify their own template
   * extensions.
   * <p>
   * To obtain nice URLs one method is to call your templates 
   * <code>foo.html.wm</code> for example, your urls can then look like
   * <code>servlet/db/table/troid/method.html</code>.
   */
  protected String addExtension(String templateName) {
    if (!templateName.endsWith(templateEngine.templateExtension()))  
      return templateName + templateEngine.templateExtension();
    else
      return templateName;      
  }

   
  /**
   * Send an error message.
   * 
   * Single call to the templet loader giving purpose (error) and 
   * Exception class.
   *
   * This will look in the purpose directory, 
   * the standard templet directory and the classpath, in that order, 
   * for a templet.
   * This can no longer fail with NotFoundException, 
   * as the Object templet will always be found 
   * (or this is a broken installation).
   *
   * @param melati the {@link Melati}
   * @param e      the {@link Exception} to report
   */
  public void error(Melati melati, Exception e) {
    melati.getResponse().setStatus(httpStatusCode(e));
    ServletTemplateContext templateContext = melati.getServletTemplateContext();
    // If this a DB error which has occurred prior to 
    // the establishment of a template context
    if (templateContext == null) {
      super.error(melati, e);
    } else 

    // has it been trapped already, if so, we don't need to relog it here
    if (!(e instanceof TrappedException)) {
      try {
        // log it
        e.printStackTrace(System.err);
        // and put it on the page
        MelatiWriter mw =  melati.getWriter();
        // get rid of anything that has been written so far
        mw.reset();
        templateContext.put("melati",melati);
        templateContext.put("ml", melati.getMarkupLanguage());
        templateContext.put("object", e);
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        templateContext.put("error",sw);
        templateContext.put("sysAdminName", getSysAdminName());
        templateContext.put("sysAdminEmail", getSysAdminEmail());

        Template errorTemplate;
        errorTemplate = melati.getConfig().getTempletLoader().
              templet(melati.getTemplateEngine(), melati.getMarkupLanguage(),"error", e.getClass());
        templateEngine.expandTemplate(mw, errorTemplate, templateContext);
        melati.write();
      } catch (Exception f) {
        System.err.println("Error finding/writing error template:");
        f.printStackTrace();
        super.error(melati,e);
      }
    }
  }


  /**
   * Prepare context and establish name of template to interpolate against it. 
   *
   * Override this method to build up your own output.
   *
   * @param melati the current Melati
   * @param templateContext the current <code>ServletTemplateContext</code>
   * @return a Template name, possibly excluding extension.
   */
  protected abstract String doTemplateRequest(Melati melati, 
                                              ServletTemplateContext templateContext)
      throws Exception;
}