package jetty;

//========================================================================
//Copyright 1997-2006 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//========================================================================

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Locale;
import java.util.TimeZone;

import javax.servlet.http.Cookie;

import org.mortbay.component.AbstractLifeCycle;
import org.mortbay.jetty.HttpHeaders;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.RequestLog;
import org.mortbay.jetty.Response;
import org.mortbay.jetty.servlet.PathMap;
import org.mortbay.log.Log;
import org.mortbay.util.DateCache;
import org.mortbay.util.RolloverFileOutputStream;
import org.mortbay.util.StringUtil;

/** 
* This {@link RequestLog} implementation outputs logs in the pseudo-standard NCSA common log format.
* Configuration options allow a choice between the standard Common Log Format (as used in the 3 log format)
* and the Combined Log Format (single log format).
* This log format can be output by most web servers, and almost all web log analysis software can understand
*  these formats.
* @author Greg Wilkins
* @author Nigel Canonizado
* 
* @org.apache.xbean.XBean element="ncsaLog"
*/
public class ApacheRequestLog extends AbstractLifeCycle implements RequestLog
{
  private String _filename;
  private boolean _extended;
  private boolean _append;
  private int _retainDays;
  private boolean _closeOut;
  private boolean _preferProxiedForAddress;
  private String _logDateFormat="dd/MMM/yyyy:HH:mm:ss Z";
  private String _filenameDateFormat = null;
  private Locale _logLocale = Locale.ENGLISH;
  private TimeZone _logTimeZone = TimeZone.getDefault();
  private String[] _ignorePaths;
  private String _logFormat = "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"";
  
  private transient OutputStream _out;
  private transient OutputStream _fileOut;
  private transient DateCache _logDateCache;
  private transient PathMap _ignorePathMap;
  private transient Writer _writer;

  
  public ApacheRequestLog()
  {
      _extended = true;
      _append = true;
      _retainDays = 31;
  }
  
  /* ------------------------------------------------------------ */
  /**
   * @param filename The filename for the request log. This may be in the format expected by {@link RolloverFileOutputStream}
   */
  public ApacheRequestLog(String filename)
  {
      _extended = true;
      _append = true;
      _retainDays = 31;
      setFilename(filename);
  }
  
  /* ------------------------------------------------------------ */
  /**
   * @param filename The filename for the request log. This may be in the format expected by {@link RolloverFileOutputStream}
   */
  public void setFilename(String filename)
  {
      if (filename != null) 
      {
          filename = filename.trim();
          if (filename.length() == 0)
              filename = null;
      }    
      _filename = filename;
  }
  
  public String getFilename() 
  {
      return _filename;
  }
  
  public String getDatedFilename()
  {
      if (_fileOut instanceof RolloverFileOutputStream)
          return ((RolloverFileOutputStream)_fileOut).getDatedFilename();
      return null;
  }
  
  /* ------------------------------------------------------------ */
  /**
   * @param format Format for the timestamps in the log file.  If not set,
   * the pre-formated request timestamp is used.
   */
  public void setLogDateFormat(String format)
  {
      _logDateFormat = format;
  }
  
  public String getLogDateFormat() 
  {
      return _logDateFormat;
  }
  
  
  public void setLogTimeZone(String tz) 
  {
      _logTimeZone = TimeZone.getTimeZone(tz);
  }
  
  public String getLogTimeZone()
  {
      return _logTimeZone.getID();
  }
  
  public void setRetainDays(int retainDays)
  {
      _retainDays = retainDays;
  }
  
  public int getRetainDays()
  {
      return _retainDays;
  }
  
  public void setExtended(boolean extended)
  {
      _extended = extended;
  }
  
  public boolean isExtended() 
  {
      return _extended;
  }
  
  public void setAppend(boolean append)
  {
      _append = append;
  }
  
  public boolean isAppend()
  {
      return _append;
  }
  
  public void setIgnorePaths(String[] ignorePaths) 
  {
      _ignorePaths = ignorePaths;
  }
  
  public String[] getIgnorePaths()
  {
      return _ignorePaths;
  }
  
  public void setPreferProxiedForAddress(boolean preferProxiedForAddress)
  {
      _preferProxiedForAddress = preferProxiedForAddress;
  }

  private String constructRecord( Request req, Response response ){
      StringBuffer buf = new StringBuffer(160);
      int len = _logFormat.length();
      for( int i = 0; i < len; i++ ){
          String qualifier = "";
          char c = _logFormat.charAt(i);
          if( c == '%' ){
              // Use the Apache formatting
              c = _logFormat.charAt(++i);
              if( c == '>' ){
                  // Ignore
                  c = _logFormat.charAt(++i);
              }
              else if( c == '{' ){
                  StringBuffer info = new StringBuffer();
                  while( ++i < len && (c = _logFormat.charAt(i)) != '}' ){
                      info.append(c);
                  }
                  qualifier = info.toString();
                  c = _logFormat.charAt(++i);
              }
              
              switch( c ){
              case 'b' : // The size of the contents
                  long responseLength = response.getContentCount();
                  if( responseLength >=0 ){
                      buf.append( responseLength );
                  }
                  else { 
                      buf.append(" - ");
                  }
                  break;
                  
              case 'B' : // The latency time [UNOFFICIAL]
                  buf.append( System.currentTimeMillis() - req.getTimeStamp() );
                  break;

              case 'C' : // Cookies [UNOFFICIAL]
                  Cookie[] cookies = req.getCookies(); 
                  if (cookies == null || cookies.length == 0)
                      buf.append('-');
                  else {
                      buf.append(" \"");
                      for (int j = 0; i < cookies.length; i++) 
                      {
                          if (j != 0) buf.append(';');
                          buf.append(cookies[j].getName());
                          buf.append('=');
                          buf.append(cookies[j].getValue());
                      }
                      buf.append("\"");
                  }
                  break;
                  
              case 'H' :
                  buf.append(req.getProtocol());
                  break;

              case 'h' : // host (IP address)
                  buf.append(req.getServerName());
                  break;

              case 'i' :
                  String header = req.getHeader(qualifier);
                  if( header == null ) header = "-";
                  buf.append(header);
                  break;
                  
              case 'l' : // ignored.
                  String addr = null;
                  if (_preferProxiedForAddress) 
                  {
                      addr = req.getHeader(HttpHeaders.X_FORWARDED_FOR);
                  }

                  if (addr == null) addr = req.getRemoteAddr();
                  buf.append( addr );
                  break;
                  
              case 'm' : // Method
                  buf.append(req.getMethod());
                  break;
                  
              case 'q' : // Query-string
                  String queryString = req.getQueryString();
                  buf.append( queryString == null ? "-" : queryString );
                  break;
                  
              case 'r' : // The complete request
                  buf.append( req.getMethod() ).append(' ')
                          .append(req.getRequestURI());
                  queryString = req.getQueryString();
                  if( queryString != null ) buf.append( '?' ).append( queryString );
                  buf.append(' ').append(req.getProtocol());
                  break;
                  
              case 's' : // Return status code
                  int status = response.getStatus();
                  if( status <=0 ) status = 404;
                  buf.append((char)('0'+((status/100)%10)));
                  buf.append((char)('0'+((status/10)%10)));
                  buf.append((char)('0'+(status%10)));                  
                  break;
                  
              case 't' :
                  try {
                      buf.append('['); //.append(dateFormat.format(new Date()))
                      if (_logDateCache!=null){
                          // SimpleDateFormat dateFormat = new SimpleDateFormat( qualifier.length() == 0 ? "dd/MMM/yyyy:HH:mm:ss Z" : qualifier, Locale.ENGLISH );
                          buf.append(_logDateCache.format(req.getTimeStamp()));
                      }
                      else
                          buf.append(req.getTimeStampBuffer().toString());
                      buf.append(']');

                  }
                  catch( IllegalArgumentException ex ){
                      Log.warn("IllegalArgumentException: " + ex.getMessage() );
                  }
                  break;

              case 'U' : // Path
                  buf.append(req.getRequestURI());
                  break;
                  
              case 'u' :
                  String remoteUser = req.getRemoteUser();
                  buf.append( remoteUser == null ? "-" : remoteUser );
                  break;
                  
              case 'v' :
                  buf.append(req.getLocalName());
                  break;
                  
              default : // Not supported
                  Log.info( "%" + c + " not supported." );
                  buf.append('-');
                  break;
              }
          }
          else {
              buf.append(c);
          }
      }
      return buf.toString();
  }

  
  public void log(Request request, Response response)
  {
      if (!isStarted()) 
          return;
      
      try 
      {
          if (_ignorePathMap != null && _ignorePathMap.getMatch(request.getRequestURI()) != null)
              return;
          
          if (_fileOut == null)
              return;

          
          
          String log = constructRecord( request, response );
          
          synchronized(_writer)
          {
              _writer.write(log);
              _writer.write(StringUtil.__LINE_SEPARATOR);
              _writer.flush();
          }
      } 
      catch (IOException e) 
      {
          Log.warn(e);
      }
      
  }
  
  protected void doStart() throws Exception
  {
      if (_logDateFormat!=null)
      {       
          _logDateCache = new DateCache(_logDateFormat, _logLocale);
          if( _logTimeZone != null ){
              _logDateCache.setTimeZone(_logTimeZone);
          }
      }
      
      if (_filename != null) 
      {
          _fileOut = new RolloverFileOutputStream(_filename,_append,_retainDays,_logTimeZone,_filenameDateFormat,null);
          _closeOut = true;
          Log.info("Opened "+getDatedFilename());
      }
      else 
          _fileOut = System.err;
      
      _out = _fileOut;
      
      if (_ignorePaths != null && _ignorePaths.length > 0)
      {
          _ignorePathMap = new PathMap();
          for (int i = 0; i < _ignorePaths.length; i++) 
              _ignorePathMap.put(_ignorePaths[i], _ignorePaths[i]);
      }
      else 
          _ignorePathMap = null;
      
      _writer = new OutputStreamWriter(_out);
      super.doStart();
  }

  protected void doStop() throws Exception
  {
      super.doStop();
      try {if (_writer != null) _writer.flush();} catch (IOException e) {Log.ignore(e);}
      if (_out != null && _closeOut) 
          try {_out.close();} catch (IOException e) {Log.ignore(e);}
          
      _out = null;
      _fileOut = null;
      _closeOut = false;
      _logDateCache = null;
      _writer = null;
  }

  /* ------------------------------------------------------------ */
  /**
   * @return the log File Date Format
   */
  public String getFilenameDateFormat()
  {
      return _filenameDateFormat;
  }

  /* ------------------------------------------------------------ */
  /** Set the log file date format.
   * @see {@link RolloverFileOutputStream#RolloverFileOutputStream(String, boolean, int, TimeZone, String, String)}
   * @param logFileDateFormat the logFileDateFormat to pass to {@link RolloverFileOutputStream}
   */
  public void setFilenameDateFormat(String logFileDateFormat)
  {
      _filenameDateFormat=logFileDateFormat;
  }

/**
 * @return the logFormat
 */
public String getLogFormat() {
    return _logFormat;
}

/**
 * @param logFormat the logFormat to set
 */
public void setLogFormat(String logFormat) {
    this._logFormat = logFormat;
}

}

