// JigsawServletContext.java
// $Id: JigsawServletContext.java,v 1.14 1998/06/12 11:18:49 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1998.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigsaw.servlet;

import java.io.*;
import java.net.*;
import java.util.*;

import javax.servlet.*;

import org.w3c.util.*;
import org.w3c.tools.resources.store.*;
import org.w3c.tools.resources.event.*;
import org.w3c.tools.resources.*;
import org.w3c.jigsaw.frames.*;

/**
 * @version $Revision: 1.14 $
 * @author  Benoît Mahé (bmahe@w3.org)
 */
public class JigsawServletContext extends StructureChangedAdapter
                                  implements ServletContext,  
					     PropertyMonitoring
{

    class Logger {
	File             logfile  = null;
	RandomAccessFile log      = null ;
	byte             msgbuf[] = null ;
	boolean          closed   = true;      

	private final String monthnames[] = {
	    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
	    "Jui", "Aug", "Sep", "Oct", "Nov", "Dec"
	};

	String getDate() {
	    Date now = new Date();
	    return (now.getDate()
		    + "/" + monthnames[now.getMonth()]
		    + "/" + (now.getYear() + 1900)
		    + ((now.getHours() < 10)
		       ? (":0" + now.getHours())
		       : (":" + now.getHours()))
		    + ((now.getMinutes() < 10)
		       ? (":0" + now.getMinutes())
		       : (":" + now.getMinutes()))
		    + ((now.getSeconds() < 10)
		       ? (":0" + now.getSeconds())
		       : (":" + now.getSeconds()))
		    + ((now.getTimezoneOffset() < 0)
		       ? " " + (now.getTimezoneOffset() / 60)
		       : " +" + (now.getTimezoneOffset() / 60)));
	}

	void log(String msg) {
	    msg = "["+getDate()+"] "+msg+"\n";
	    try {
		if ( log == null || closed)
		    openLogFile();
		if ( log != null ) {
		    int len = msg.length() ;
		    if ( len > msgbuf.length ) 
			msgbuf = new byte[len] ;
		    msg.getBytes (0, len, msgbuf, 0) ;
		    log.write (msgbuf, 0, len) ;
		}
	    } catch (IOException ex) {
		System.out.println("Can't write ("+
				   msg+") to logfile ["+
				   logfile+"] : "+ex.getMessage());
	    }
	}
 
	void log(Exception ex, String msg) {
	    log(msg+" : "+ex.getMessage());
	}

	void openLogFile() 
	    throws IOException
	{
	    RandomAccessFile old = log ;
	    log = new RandomAccessFile (logfile, "rw") ;
	    log.seek (log.length()) ;
	    closed = false;
	    if ( old != null )
		old.close () ;
	}

	void close() {
	    try {
		if (log != null)
		    log.close();
		closed = true;
	    } catch (IOException ex) {
		ex.printStackTrace();
	    }
	}

	Logger(File logfile) {
	    this.logfile = logfile;
	    this.msgbuf  = new byte[128] ;
	    log("Servlet Logger started");
	}
    }

    private ResourceReference reference = null;

    private Logger logger = null;

    private ObservableProperties props = null;

    protected static String logdir     = "logs" ;

    protected static String deflogfile = "servlets";

    public boolean propertyChanged (String name) {
	if (name.equals(ServletProps.SERVLET_LOG_FILE_P)) {
	    if (logger != null) {
		logger.close();
		File newlogfile = 
		    new File((String) props.get(
					    ServletProps.SERVLET_LOG_FILE_P));
		if (newlogfile.getPath().length() < 1) 
		    newlogfile = getServletLogFile();
		logger = new Logger(newlogfile);
	    }
	}
	return true;
    }

    public void resourceUnloaded(StructureChangedEvent evt){
	if (logger != null) {
	    logger.close();
	}
    }

    /**
     * A useful utility routine that tries to guess the content-type
     * of an object based upon its extension.
     */
    protected static String guessContentTypeFromName(String fname) {
	String ext = "";
	int i = fname.lastIndexOf('#');

	if (i != -1)
	    fname = fname.substring(0, i - 1);
	i = fname.lastIndexOf('.');
	i = Math.max(i, fname.lastIndexOf('/'));
	i = Math.max(i, fname.lastIndexOf('?'));

	if (i != -1 && fname.charAt(i) == '.') {
	    ext = fname.substring(i).toLowerCase();
	}
	return (String) extension_map.get(ext);
    }

    static Hashtable extension_map = new Hashtable();

    static {
	setSuffix("", "content/unknown");
	setSuffix(".uu", "application/octet-stream");
	setSuffix(".saveme", "application/octet-stream");
	setSuffix(".dump", "application/octet-stream");
	setSuffix(".hqx", "application/octet-stream");
	setSuffix(".arc", "application/octet-stream");
	setSuffix(".o", "application/octet-stream");
	setSuffix(".a", "application/octet-stream");
	setSuffix(".bin", "application/octet-stream");
	setSuffix(".exe", "application/octet-stream");
	/* Temporary only. */
	setSuffix(".z", "application/octet-stream");
	setSuffix(".gz", "application/octet-stream");

	setSuffix(".oda", "application/oda");
	setSuffix(".pdf", "application/pdf");
	setSuffix(".eps", "application/postscript");
	setSuffix(".ai", "application/postscript");
	setSuffix(".ps", "application/postscript");
	setSuffix(".rtf", "application/rtf");
	setSuffix(".dvi", "application/x-dvi");
	setSuffix(".hdf", "application/x-hdf");
	setSuffix(".latex", "application/x-latex");
	setSuffix(".cdf", "application/x-netcdf");
	setSuffix(".nc", "application/x-netcdf");
	setSuffix(".tex", "application/x-tex");
	setSuffix(".texinfo", "application/x-texinfo");
	setSuffix(".texi", "application/x-texinfo");
	setSuffix(".t", "application/x-troff");
	setSuffix(".tr", "application/x-troff");
	setSuffix(".roff", "application/x-troff");
	setSuffix(".man", "application/x-troff-man");
	setSuffix(".me", "application/x-troff-me");
	setSuffix(".ms", "application/x-troff-ms");
	setSuffix(".src", "application/x-wais-source");
	setSuffix(".wsrc", "application/x-wais-source");
	setSuffix(".zip", "application/zip");
	setSuffix(".bcpio", "application/x-bcpio");
	setSuffix(".cpio", "application/x-cpio");
	setSuffix(".gtar", "application/x-gtar");
	setSuffix(".shar", "application/x-shar");
	setSuffix(".sh", "application/x-shar");
	setSuffix(".sv4cpio", "application/x-sv4cpio");
	setSuffix(".sv4crc", "application/x-sv4crc");
	setSuffix(".tar", "application/x-tar");
	setSuffix(".ustar", "application/x-ustar");
	setSuffix(".snd", "audio/basic");
	setSuffix(".au", "audio/basic");
	setSuffix(".aifc", "audio/x-aiff");
	setSuffix(".aif", "audio/x-aiff");
	setSuffix(".aiff", "audio/x-aiff");
	setSuffix(".wav", "audio/x-wav");
	setSuffix(".gif", "image/gif");
	setSuffix(".ief", "image/ief");
	setSuffix(".jfif", "image/jpeg");
	setSuffix(".jfif-tbnl", "image/jpeg");
	setSuffix(".jpe", "image/jpeg");
	setSuffix(".jpg", "image/jpeg");
	setSuffix(".jpeg", "image/jpeg");
	setSuffix(".tif", "image/tiff");
	setSuffix(".tiff", "image/tiff");
	setSuffix(".ras", "image/x-cmu-rast");
	setSuffix(".pnm", "image/x-portable-anymap");
	setSuffix(".pbm", "image/x-portable-bitmap");
	setSuffix(".pgm", "image/x-portable-graymap");
	setSuffix(".ppm", "image/x-portable-pixmap");
	setSuffix(".rgb", "image/x-rgb");
	setSuffix(".xbm", "image/x-xbitmap");
	setSuffix(".xpm", "image/x-xpixmap");
	setSuffix(".xwd", "image/x-xwindowdump");
	setSuffix(".htm", "text/html");
	setSuffix(".html", "text/html");
	setSuffix(".text", "text/plain");
	setSuffix(".c", "text/plain");
	setSuffix(".cc", "text/plain");
	setSuffix(".c++", "text/plain");
	setSuffix(".h", "text/plain");
	setSuffix(".pl", "text/plain");
	setSuffix(".txt", "text/plain");
	setSuffix(".java", "text/plain");
	setSuffix(".rtx", "application/rtf");
	setSuffix(".tsv", "texyt/tab-separated-values");
	setSuffix(".etx", "text/x-setext");
	setSuffix(".mpg", "video/mpeg");
	setSuffix(".mpe", "video/mpeg");
	setSuffix(".mpeg", "video/mpeg");
	setSuffix(".mov", "video/quicktime");
	setSuffix(".qt", "video/quicktime");
	setSuffix(".avi", "application/x-troff-msvideo");
	setSuffix(".movie", "video/x-sgi-movie");
	setSuffix(".mv", "video/x-sgi-movie");
	setSuffix(".mime", "message/rfc822");
    }

    static private void setSuffix(String ext, String ct) {
	extension_map.put(ext, ct);
    }

    /**
     * ServletContext implementation - Get the MIME type for given file.
     */

    public String getMimeType(String filename) {
	return guessContentTypeFromName(filename);
    }

    protected ServerInterface getServer() {
	try {
	    Resource res = reference.lock();
	    return ((ServletDirectoryFrame)res).getServer();
	} catch(InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    reference.unlock();
	}
    }

    public File getServletLogFile() {
	ServerInterface server = getServer();
	File logfile = null;
	String file = (String)
	    server.getProperties().getString(ServletProps.SERVLET_LOG_FILE_P,
					     null);
	if (file != null)
	    logfile = new File(file);
	if ((logfile != null) && (logfile.getPath().length() < 1))
	    logfile = null;
	if (logfile == null) {
	    File root_dir = server.getRootDirectory();
	    if (root_dir == null) {
		throw new RuntimeException("unable to build a default "+
					   "value for the servlet log file.");
	    }
	    logfile = new File(new File(root_dir, logdir), deflogfile);
	}
	return logfile;
    }

    /**
     * ServletContext implementation - Lookup a given servlet.
     */

    public Servlet getServlet(String name) {
	try {
	    Resource res = reference.lock();
	    return ((ServletDirectoryFrame)res).getServlet(name);
	} catch(InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    reference.unlock();
	}
    }

    /**
     * ServletContext implementation - Enumerate all servlets within context.
     */

    public Enumeration getServlets() {
	try {
	    Resource res = reference.lock();
	    return ((ServletDirectoryFrame)res).getServlets();
	} catch(InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    reference.unlock();
	}
    }

    /**
     * ServletContext implementation - Enumerate all servlets names 
     * within context.
     */

    public Enumeration getServletNames() {
	try {
	    Resource res = reference.lock();
	    return ((ServletDirectoryFrame)res).getServletNames();
	} catch(InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    reference.unlock();
	}
    }

    /**
     * ServletContext implementation - Log a message.
     */
    
    public void log(String msg) {
	logger.log(msg);
    }
    
    public void log(Exception ex, String msg) {
	logger.log(ex,msg);
    }

    /**
     * ServletContext implementation - Translate a piece of path.
     */

    public String getRealPath(String path) {
	return path;
    }

    /**
     * ServletContext implementation - Get server informations.
     */

    public String getServerInfo() {
	try {
	    Resource res = reference.lock();
	    return ((ServletDirectoryFrame)res).getServerInfo();
	} catch(InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    reference.unlock();
	}	
    }

    /**
     * ServletContext implementation - Get an attribute value.
     * We map this into the ServletWrapper attributes, without
     * support for name clashes though.
     * @param name The attribute name.
     */
    
    public Object getAttribute(String name) {
	try {
	    Resource res = reference.lock();
	    return ((ServletDirectoryFrame)res).getAttribute(name);
	} catch(InvalidResourceException ex) {
	    ex.printStackTrace();
	    return null;
	} finally {
	    reference.unlock();
	}
    }

    /**
     * Create a new ServletContext.
     * @param ref a ResourceReference pointing on a ServletDirectoryFrame.
     */
    protected JigsawServletContext(ResourceReference ref, 
				   ObservableProperties props) 
    {
	this.reference = ref;
	this.props     = props;
	props.registerObserver(this);
	this.logger = new Logger(getServletLogFile());
	try {
	    Resource res = reference.lock();
	    if (! (res instanceof ServletDirectoryFrame)) {
		throw new RuntimeException("This reference is not pointing on"+
					   " a ServletDirectoryFrame.");
	    } else {
		ServletDirectoryFrame sframe = (ServletDirectoryFrame)res;
		FramedResource resource = (FramedResource)sframe.getResource();
		resource.addStructureChangedListener(this);
	    }
	} catch(InvalidResourceException ex) {
	    throw new RuntimeException("This reference is pointing on"+
				       " an Invalid ServletDirectoryFrame.");
	} finally {
	    reference.unlock();
	}
    }

}
