/*
  Copyright (C) 2006 Helge Hess

  This file is part of JOPE.

  JOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  JOPE 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 Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.opengroupware.jope.servlets;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.appserver.core.WOApplication;
import org.opengroupware.jope.appserver.core.WOCookie;
import org.opengroupware.jope.appserver.core.WORequest;
import org.opengroupware.jope.appserver.core.WOResponse;
import org.opengroupware.jope.foundation.UString;

public class WOServletAdaptor extends HttpServlet {
  private static final long serialVersionUID = 8379846205230754066L;
  private final Log log = LogFactory.getLog("WOServletAdaptor");

  // TODO: this must be a cross-servlet/context hash
  //       possibly this must be a weak reference so that
  //       the app goes away if all servlets went away.
  protected Map<String,WOApplication> appRegistry = 
    new ConcurrentHashMap<String, WOApplication>(4);
  
  /* Note: remember that the ivars are thread-shared */
  private WOApplication WOApp = null;

  /* application registry */
  
  public synchronized void initApplicationWithName(String _name) {
    if (this.WOApp != null) // TODO: is this valid in THREADs?
      return;
        
    if ((this.WOApp = this.appRegistry.get(_name)) != null)
      /* already cached, eg setup by a different servlet */
      return;
    
    /* find class of application */

    Class cl = null;
    try {
      cl = Class.forName(_name);
    }
    catch (ClassNotFoundException cnfe) {
      this.log.fatal("did not find WOApp class: " + _name);
      return;
    }
    
    /* instantiate application class */
    
    WOApplication app = null;
    try {
      app = (WOApplication)cl.newInstance();
    }
    catch (InstantiationException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    catch (IllegalAccessException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    if (app == null) {
      this.log.fatal("did not find WOApp class: " + _name);
      return;
    }
    
    /* register new object */
    
    this.appRegistry.put(_name, app);
    app = null;
    
    /* 
     * Note: we use the registry because another thread might have been faster
     *       with registration.
     */
    this.WOApp = this.appRegistry.get(_name);
  }

  /* deliver WOResponse to ServletResponse */
  
  public void sendWOResponseToServletResponse
    (WOResponse _wr, HttpServletResponse _sr)
    throws IOException
  {
    this.log.debug("sending WOResponse to Servlet ...");
    
    /* set response status */
    _sr.setStatus(_wr.status());
    byte[] content = _wr.content();
    
    /* setup content type */
    
    String s = _wr.headerForKey("content-type"); 
    if (s != null)
      _sr.setContentType(s);
    else {
      this.log.warn("No `content-type` set in response - " +
                       "defaulting to `application/octet-stream`");
      _sr.setContentType("application/octet-stream");
    }
    
    /* setup content length */
    
    int contentLen = -1;
    if ((s = _wr.headerForKey("content-length")) != null) {
      try {
        contentLen = Integer.parseInt(s);
      }
      catch (NumberFormatException e) {
        this.log.error("failed to parse given content-length: " + s, e);
        contentLen = -1;
      }
    }
    if (contentLen == -1 && content != null)
      contentLen = content.length;
    
    if (contentLen != -1)
      _sr.setContentLength(contentLen);
    
    /* deliver headers */
    
    Map<String,List<String>> headers = _wr.headers();
    if (headers != null) {
      for (String k: headers.keySet()) {
        if (k.equals("content-type"))
          continue;
        if (k.equals("content-length"))
          continue;
        if (k.equals("cookie") || k.equals("set-cookie"))
          continue;
        
        List<String> v = headers.get(k);
        if (v == null) continue;
        if (v.size() == 0) continue;
        
        s = UString.componentsJoinedByString(v, ", ");
        _sr.addHeader(k, s);
      }
    }
    
    /* deliver cookies */
    
    for (WOCookie k: _wr.cookies())
      _sr.addHeader("set-cookie", k.headerString());
    
    /* deliver content */
    
    OutputStream os = _sr.getOutputStream();
    if (content != null)
      os.write(content);
    os.flush();
  }
  
  protected void woService(HttpServletRequest _rq, HttpServletResponse _r) {
    this.log.debug("woService ...");
    
    WORequest  rq;
    WOResponse r;
    
    rq = new WOServletRequest(_rq, _r);

    try {
      this.log.debug("  dispatch ...");
      r = this.WOApp.dispatchRequest(rq);
      
      if (r != null) {
        this.log.debug("  flush ...");
        r.flush();
      
        if (!r.isStreaming())
          this.sendWOResponseToServletResponse(r, _r);
      }
      else
        this.log.debug("  got no response.");
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    
    if (rq != null) {
      rq.dispose(); /* this will delete temporary files, eg of file uploads */
      rq = null;
    }
    
    this.log.debug("done woService.");
  }

  /* servlet methods */
  
  @Override
  public void init(ServletConfig _cfg) throws ServletException {
    // Jetty: org.mortbay.jetty.servlet.ServletHolder$Config@114024
    super.init(_cfg);
    
    this.initApplicationWithName
      ((String)_cfg.getServletContext().getAttribute("WOAppName"));
  }

  @Override
  protected void doGet(HttpServletRequest _rq, HttpServletResponse _r)
    throws ServletException, IOException
  {
    this.woService(_rq, _r);
  }
  
  @Override
  protected void doPost(HttpServletRequest _rq, HttpServletResponse _r)
    throws ServletException, IOException
  {
    /* Note: apparently the superclass service() method performs additional
     *       processing on the form values. So we let it do that.
     */
    this.woService(_rq, _r);
  }
  
  protected static String[] stdMethods = { "GET", "POST", "PUT", "DELETE" };
  
  @Override
  protected void service(HttpServletRequest _rq, HttpServletResponse _r)
    throws ServletException, IOException
  {
    boolean isStdMethod = false;
    for (int i = 0; i < stdMethods.length; i++) {
      if (_rq.getMethod().equals(stdMethods[i])) {
        isStdMethod = true;
        break;
      }
    }
    if (isStdMethod) {
      this.log.debug("service standard method: " + _rq.getMethod());
      super.service(_rq, _r);
    }
    else {
      this.log.debug("service custom method: " + _rq.getMethod());
      this.woService(_rq, _r);
    }
    this.log.debug("done service.");
  }
}
