// HTTPResource.java
// $Id: HTTPResource.java,v 1.1 1997/05/29 11:58:34 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.tools.resources.jigsaw ; // move to w3c.jigsaw.resources

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

import w3c.tools.resources.*;
import w3c.tools.resources.event.*;
import w3c.tools.resources.impl.*;
import w3c.jigsaw.http.* ;
import w3c.www.mime.*;
import w3c.www.http.*;

/**
 * a very basic version of HTTP Resource.
 * All resources given by the HTTP server must inherit from this one.
 */

public class HTTPResource extends ResourceImpl {
    /**
     * Condition check return code - Condition existed but failed.
     */
    public static final int COND_FAILED = 1;
    /**
     * Condition check return code - Condition existed and succeeded.
     */
    public static final int COND_OK = 2;


    // Methods allowed by that class in general:
    protected static HttpTokenList _allowed = null;
    static {
	// allowed shares _allowed value, that's a feature.
	String str_allowed[] = { "GET", "HEAD", "OPTIONS" };
	_allowed = HttpFactory.makeStringList(str_allowed);
    }
    // Methods allowed by instances of that class in particular:
    protected        HttpTokenList  allowed = _allowed;
 
    // should be matching the context

    static final String PROP_PARENT           = "parent";
    static final String PROP_URL              = "url";
    static final String PROP_CONTEXT          = "context";
    static final String PROP_QUALITY          = "quality";
    static final String PROP_TITLE            = "title";
    static final String PROP_CONTENT_LANGUAGE = "content-language";
    static final String PROP_CONTENT_TYPE     = "content-type";
    static final String PROP_CONTENT_ENCODING = "content-encoding";
    static final String PROP_CONTENT_LENGTH   = "content-length";
    static final String PROP_LAST_MODIFIED    = "last-modified";
    static final String PROP_ICON             = "icon";
    static final String PROP_MAXAGE           = "maxage";
    static final String PROP_OID              = "oid";

    // The HTTPResource keeps a cache of ready to use Http values. This 
    // allows to save converting to/from wire rep these objects. Not 
    // much CPU time, but also memory is spared.

    HTTPResource  parent          = null;
    HttpMimeType  contenttype     = null;
    HttpInteger   contentlength   = null;
    HttpDate      lastmodified    = null;
    HttpTokenList contentencoding = null;
    HttpTokenList contentlanguage = null;
    int           oid             = -1;

     // more to be implemented


    /**
     * Get this resource's help url.
     * @return An URL, encoded as a String, or <strong>null</strong> if not
     * available.
     */

    // is this really necessary? FIXME

    public String getHelpURL() {
//	httpd server = getServer();
//	if ( server == null ) 
//	    return null;
//	String docurl = server.getDocumentationURL();
	String docurl = null;
	if ( docurl == null )
	    return null;
	return docurl + "/" + getClass().getName() + ".html";
    }

    public String getHelpURL(String topic) {
	return null;
    }

    /**
     * Catch setValue, to maintain cached header values correctness.
     * @param idx The index of the attribute to be set.
     * @param value The new value for the attribute.
     * 
     * A listener should be aware of that and update the headers
     */

    public synchronized void setValue(String name, Object value) 
	throws IllegalAccessException
    {
	super.setValue(name, value);
	if ( name.equals(PROP_CONTENT_TYPE))
	    contenttype = null;
	if ( name.equals(PROP_CONTENT_LENGTH))
	    contentlength = null;
	if ( name.equals(PROP_CONTENT_ENCODING))
	    contentencoding = null;
	if ( name.equals(PROP_CONTENT_LANGUAGE))
	    contentlanguage = null;
	// Any attribute setting modifies the last modified time:
	lastmodified = null;
    }

    public HTTPResource getParent()
	throws IllegalAccessException
    {
	return (HTTPResource) getValue(PROP_PARENT);
    }
 
    public String getURLPath()
	throws IllegalAccessException
    {
	return (String) getValue(PROP_URL);
    }

    public URL getURL(Request request)
	throws IllegalAccessException
    {
	String upath = request.getURLPath();
	try {
	    return new URL(request.getURL(), upath);
	} catch (MalformedURLException ex) {
	    throw new RuntimeException("unable to build "
                                       + getURLPath()
				       + " full URL, from server "
				 
//                                      + getServer().getURL());
				       );
	}
    }

    /**
     * Get this resource quality.
     * @return The resource quality, or some negative value if not defined.
     */

    public double getQuality() 
	throws IllegalAccessException
    {
	return ((Double)getValue(PROP_QUALITY)).doubleValue() ;
    }
   
     /**
     * Get this resource title.
     * @return This resource's title, or <strong>null</strong> if not 
     *    defined.
     */

    public String getTitle()
	throws IllegalAccessException 
    {
	return (String)getValue(PROP_TITLE) ;
    }
   
    public String getContentLanguage()
	throws IllegalAccessException
    {
	return (String) getValue(PROP_TITLE) ;
    }

    public String getContentEncoding()
	throws IllegalAccessException
    {
	return (String) getValue(PROP_CONTENT_ENCODING);
    }

    public MimeType getContentType()
	throws IllegalAccessException
    {
	return (MimeType) getValue(PROP_CONTENT_TYPE);
    }

    public int getContentLength()
	throws IllegalAccessException
    {
	return ((Integer) getValue(PROP_CONTENT_LENGTH)).intValue();
    }

    public long getMaxAge()
	throws IllegalAccessException
    {
	return ((Long) getValue(PROP_MAXAGE)).longValue();
    }

    public long getLastModified()
	throws IllegalAccessException
    {
	return ((Long) getValue(PROP_LAST_MODIFIED)).longValue();
    }
    
    public String getIcon()
 	throws IllegalAccessException
    {
	return (String) getValue(PROP_ICON);
    }

    /**
     * Get this resource's object identifier.
     * An object identifier is to be used specifically in etags. It's purpose
     * is to uniquify the etag of a resource. It's computed as a random number
     *, on demand only.
     * @return A uniq object identifier for that resource, as an inteeger.
     */

    public int getOid() 
	throws IllegalAccessException
    {
	oid = ((Integer)getValue(PROP_OID)).intValue();
	if ( oid == -1 ) {
	    double d = Math.random() * ((double) Integer.MAX_VALUE);
	    // synchronized on not synchronized?
	    synchronized (this) {
		oid = (int) d;
		setValue(PROP_OID, new Integer(oid));
	    }
	}
	return oid;
    }

    // getContext ???
    // getServer ???


    /**
     * Set an HTTPResource attribute.
     * Mark modified should also update our last-modification time.
     * @param idx The index of the value to be set.
     * @param value Its new value.
     */
		
    public synchronized void markModified() {
	modified = true;
    }

    // FIXME

    /**
     * Update the cached headers value.
     * Each resource maintains a set of cached values for headers, this
     * allows for a nice sped-up in headers marshalling, which - as the 
     * complexity of the protocol increases - becomes a bottleneck.
     */

    protected void updateCachedHeaders() {
	// Precompute a set of header values to keep by:
	if ( contenttype == null )
	    try {
		contenttype = HttpFactory.makeMimeType(getContentType());
	    } catch (IllegalAccessException ex) {
		ex.printStackTrace();
	    }
	if (contentlength == null) {
	    int cl = -1;
	    try {
		cl = getContentLength();
	    } catch (IllegalAccessException ex) {
		ex.printStackTrace();
	    }
	    if ( cl >= 0 )
		contentlength = HttpFactory.makeInteger(cl);
	}
	if ( lastmodified == null ) {
	    long lm = -1;
	    try {
		lm = getLastModified();
	    } catch (IllegalAccessException ex) {
		ex.printStackTrace();
	    }	
	    if ( lm > 0 )
		lastmodified = HttpFactory.makeDate(lm);
	}
	if ( //definesAttribute(PROP_CONTENT_ENCODING) &&
	    (contentencoding==null))
	    try {
		contentencoding = 
		    HttpFactory.makeStringList(getContentEncoding());
	    } catch (IllegalAccessException ex) {
		ex.printStackTrace();
	    }
	    if (//definesAttribute(PROP_CONTENT_LANGUAGE) &&
		(contentlanguage==null))
	    try {
		contentlanguage = 
		    HttpFactory.makeStringList(getContentLanguage());
	    } catch (IllegalAccessException ex) {
		ex.printStackTrace();
	    }	
    }
    

    public Reply createDefaultReply(Request request, int status) {
	Reply reply = request.makeReply(status);
	updateCachedHeaders();
	if ( status != HTTP.NOT_MODIFIED ) {
	    if ( contentlength != null )
		reply.setHeaderValue(Reply.H_CONTENT_LENGTH, contentlength);
	    if ( contenttype != null )
		reply.setHeaderValue(Reply.H_CONTENT_TYPE, contenttype);
	    if ( lastmodified != null )
		reply.setHeaderValue(Reply.H_LAST_MODIFIED, lastmodified);
	    if ( contentencoding != null )
		reply.setHeaderValue(Reply.H_CONTENT_ENCODING,contentencoding);
	    if ( contentlanguage != null )
		reply.setHeaderValue(Reply.H_CONTENT_LANGUAGE,contentlanguage);
	}
	long maxage = 0;
	try {
	    maxage = getMaxAge();
	} catch (IllegalAccessException ex) {
	    ex.printStackTrace();
	}
	if ( maxage >= 0 ) {
	    if (reply.getMajorVersion() >= 1 ) {
		if (reply.getMinorVersion() >= 1) {
		    reply.setMaxAge((int) (maxage / 1000));
		} 
		// If max-age is zero, say what you mean:
		long expires = (System.currentTimeMillis()
				+ ((maxage == 0) ? -1000 : maxage));
		reply.setExpires(expires);
	    }
	}
	// Set the date of the reply (round it to secs):
	reply.setDate((System.currentTimeMillis() / 1000L) * 1000L);
	return reply;
    }

    /**
     * Check the <code>If-Match</code> condition of that request.
     * @param request The request to check.
     * @return An integer, either <code>COND_FAILED</cond> if condition
     * was checked, but failed, <code>COND_OK</code> if condition was checked
     * and succeeded, or <strong>0</strong> if the condition was not checked
     * at all (eg because the resource or the request didn't support it).
     */

    public int checkIfMatch(Request request) {
	return 0;
    }


    /**
     * Check the <code>If-None-Match</code> condition of that request.
     * @param request The request to check.
     * @return An integer, either <code>COND_FAILED</cond> if condition
     * was checked, but failed, <code>COND_OK</code> if condition was checked
     * and succeeded, or <strong>0</strong> if the condition was not checked
     * at all (eg because the resource or the request didn't support it).
     */

    public int checkIfNoneMatch(Request request) {
	return 0;
    }


    /**
     * Check the <code>If-Modified-Since</code> condition of that request.
     * @param request The request to check.
     * @return An integer, either <code>COND_FAILED</cond> if condition
     * was checked, but failed, <code>COND_OK</code> if condition was checked
     * and succeeded, or <strong>0</strong> if the condition was not checked
     * at all (eg because the resource or the request didn't support it).
     */

    public int checkIfModifiedSince(Request request) {
	return 0;
    }

    /**
     * Check the <code>If-Unmodified-Since</code> condition of that request.
     * @param request The request to check.
     * @return An integer, either <code>COND_FAILED</cond> if condition
     * was checked, but failed, <code>COND_OK</code> if condition was checked
     * and succeeded, or <strong>0</strong> if the condition was not checked
     * at all (eg because the resource or the request didn't support it).
     */

    public int checkIfUnmodifiedSince(Request request) {
	return 0;
    }

    // Default Calling methods

    /**
     * The default OPTIONS method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException In case of errors.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply options(Request request)
	throws HTTPException, ClientException
    {
	Reply reply = createDefaultReply(request, HTTP.OK);
	// Removed unused headers:
	reply.setContentLength(-1);
	reply.setContentType(null);
	// Add the allow header:
	if ( allowed != null )
	    reply.setHeaderValue(Reply.H_ALLOW, allowed);
	return reply;
    }

    /**
     * The default GET method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException Always thrown, to return a NOT_IMPLEMENTED
     *    error.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply get (Request request) 
	throws HTTPException, ClientException
    {
	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
	error.setContent("Method GET not implemented.") ;
	throw new HTTPException (error) ;
    }


    /**
     * The default HEAD method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException Always thrown, to return a NOT_IMPLEMENTED
     *    error.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply head (Request request) 
	throws HTTPException, ClientException
    {
	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
	error.setContent("Method HEAD not implemented.") ;
	throw new HTTPException (error) ;
    }

    /**
     * The default POST method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException Always thrown, to return a NOT_IMPLEMENTED
     *    error.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply post (Request request) 
	throws HTTPException, ClientException
    {
	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
	error.setContent("Method POST not implemented.") ;
	throw new HTTPException (error) ;
    }

    /**
     * The default DELETE method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException Always thrown, to return a NOT_IMPLEMENTED
     *    error.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply delete(Request request)
	throws HTTPException, ClientException
    {
	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
	error.setContent("Method DELETE not implemented.") ;
	throw new HTTPException (error) ;
    }

    /**
     * The default LINK method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException Always thrown, to return a NOT_IMPLEMENTED
     *    error.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply link(Request request) 
	throws HTTPException, ClientException
    {
	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
	error.setContent("Method LINK not implemented.") ;
	throw new HTTPException (error) ;
    }

    /**
     * The default UNLINK method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException Always thrown, to return a NOT_IMPLEMENTED
     *    error.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply unlink(Request request)
	throws HTTPException, ClientException
    {
	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
	error.setContent("Method UNLINK not implemented.") ;
	throw new HTTPException (error) ;
    }

    /**
     * The handler for unknown method replies with a not implemented.
     * @param request The request to handle.
     * @exception HTTPException Always thrown, to return a NOT_IMPLEMENTED
     *    error.
     * @exception ClientException If the client instance controling the
     * request processing got a fatal error.
     */

    public Reply extended(Request request)
	throws HTTPException, ClientException
    {
	Reply error = request.makeReply(HTTP.NOT_IMPLEMENTED) ;
	error.setContent("Method "+request.getMethod()+" not implemented.") ;
	throw new HTTPException (error) ;
    }

    public Reply perform(Request request) 
	throws HTTPException, ClientException
    {
	String method = request.getMethod();
	
	if ( method.equals("GET")) {
	    return get(request);
	} 
	if ( method.equals("HEAD")) {
	    return head(request);
	}
	if ( method.equals("POST")) {
	    return post(request);
	}
	if ( method.equals("OPTIONS")) {
	    return options(request);
	}
	if ( method.equals("DELETE")) {
	    return delete(request);
	} 
	if ( method.equals("LINK")) {
	    return link(request);
	}
	if( method.equals("UNLINK")) {
	    return unlink(request);
	}
	return extended(request);
    }
}
