// PutableDirectory.java
// $Id: PutableDirectory.java,v 1.16 1998/01/23 11:59:47 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

package w3c.jigsaw.resources;

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

import w3c.tools.store.*;
import w3c.www.mime.*;
import w3c.www.http.*;
import w3c.tools.sorter.*;
import w3c.jigsaw.http.*;

/**
 * A Directoryresource that is able to create documents in reply to PUT.
 */

public class PutableDirectory extends DirectoryResource {
    private static HttpTokenList _browse_allowed = null;
    static {
	String str_allowed[] = { "HEAD" , "GET" , "OPTIONS" , "BROWSE" };
	_browse_allowed = HttpFactory.makeStringList(str_allowed);
    }

    /**
     * Attribute index - Allow the GNN browse method.
     */
    protected static int ATTR_BROWSABLE = -1 ;

    static {
	Attribute   a = null ;
	Class     cls = null;

	try {
	    cls     = Class.forName("w3c.jigsaw.resources.PutableDirectory") ;
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    System.exit(1) ;
	}
	// The browsable flag:
	a = new BooleanAttribute("browsable"
				 , Boolean.FALSE
				 , Attribute.EDITABLE) ;
	ATTR_BROWSABLE = AttributeRegistry.registerAttribute(cls, a) ;
    }

    /**
     * Get this class browsable flag.
     */

    public boolean getBrowsableFlag() {
	return getBoolean (ATTR_BROWSABLE, false) ;
    }

    /**
     * Create  new resource.
     * @param name The identifier of the new resource.
     * @param request The request that trigered this resource creation.
     */

    protected HTTPResource createResource(String name, Request request)
	throws HTTPException
    {
	// Create an empty file:
	File    file    = new File(getDirectory(), name) ;
	boolean created = false ;

	if ( ! file.exists() ) {
	    try {
		(new RandomAccessFile(file, "rw")).close() ;
		created = true ;
	    } catch (Exception ex) {
		created = false ;
	    }
	}
	if ( ! created ) {
	    // Error, we don't wan't to allow to overide files !
	    Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
	    error.setContent("Unable to create the appropriate file: "
			     + request.getURLPath());
	    throw new HTTPException (error) ;
	}
	// Go to the indexer:
	HTTPResource resource = null ;
	try {
	    resource = createDefaultResource(name) ;
	    if ( resource == null ) {
		Reply error = request.makeReply(HTTP.BAD_REQUEST) ;
		error.setContent("Failed to create resource "
				 + name + ". The indexer wasn't able to "
				 + "deduce a resource for it.") ;
		throw new HTTPException (error);
	    } 
	} finally {
	    // If we wasn't able to create the resource, delete empty file.
	    if ( resource == null ) 
		file.delete() ;
	}
	return resource ;
    }

    /**
     * The lookup method creates resources on the fly...
     * The funny thing here, is that you need to create the resource
     * <em>before</em> handling the PUT, since it will determine how to
     * handle it.
     * @param ls The current lookup state.
     * @param lr The lookup result being constructed.
     * @exception HTTPException If some error occurs.
     * @return A boolean, <strong>true</strong> of lookup has completed,
     * <strong>false</strong> if the caller should continue the lookup process.
     */

    public boolean lookup(LookupState ls, LookupResult lr)
	throws HTTPException
    {
	// We might be able to create a new 
	if ((!ls.hasMoreComponents()) || (ls.countRemainingComponents() > 1)) {
	    if (ls.getRequest().getMethod().equals("PUT") &&
		getExtensibleFlag()) {
		// if the directory doesn't exist, create it!
		String name = ls.peekNextComponent();
		File f = new File(getDirectory(), name);
		if (!f.exists()) {
		    f.mkdir();
		    HTTPResource r = createDefaultResource(name);
		    if (r == null)
			f.delete();
		}
	    }
	    // We are just crossing the directory:
	    return super.lookup(ls, lr) ;
	} else {
	    // We might well want to create a resource:
	    String       name   = ls.peekNextComponent() ;
	    HTTPResource result = null;
	    try {
		result = (HTTPResource) lookup(name) ;
	    } catch (InvalidResourceException ex) {
		result = null;
	    }
	    if (result == null) {
		// Create the resource, then fallback to directory behavior
		Request request = ls.getRequest() ;
		if ((request == null) || request.getMethod().equals("PUT")) 
		    createResource(name, request);
		
	    } 
	    // Target resource exists, we don't need to create it
	    return super.lookup(ls, lr);
	}
    }

    /**
     * Handle the browse method.
     * @param request The request to handle.
     */

    protected static MimeType  browsetype = null;

    protected synchronized MimeType getBrowseType() {
	if ( browsetype == null ) {
	    try {
		browsetype = new MimeType("application/x-navibrowse");
	    } catch (Exception ex) {
		ex.printStackTrace();
	    }
	}
	return browsetype;
    }
	    
    /**
     * A present to GNNPress users !
     * This method implements the <code>BROWSE</code> method that
     * AOL press (or GNN press, or whatever its last name is) expects.
     * @param request The request to process.
     * @exception HTTPException If some error occurs.
     * @return A Reply instance.
     */

    public Reply browse (Request request)
	throws HTTPException
    {
	Enumeration  enum      = enumerateResourceIdentifiers() ;
	Vector       resources = Sorter.sortStringEnumeration(enum) ;
	int          rsize     = ((resources == null) ? 0 : resources.size()) ;
	StringBuffer sb        = new StringBuffer() ;

	// As we have enumerated all resources, just looking the store is ok
	for (int i = 0 ; i < rsize ; i++) {
	    String       rname = (String) resources.elementAt(i) ;
	    HTTPResource r     = null;
	    try {
		r = lookupStore(rname) ;
	    } catch (InvalidResourceException ex) {
		continue;
	    }
	    if ( r instanceof DirectoryResource ) {
		sb.append("application/x-navidir "
			  + rname
			  + "\r\n") ;
	    } else {
		sb.append(r.getContentType().toString()
			  + " "
			  + rname
			  + "\r\n") ;
	    }
	}
	Reply reply = request.makeReply(HTTP.OK) ;
	reply.addPragma("no-cache");
	reply.setNoCache();
	reply.setContent(sb.toString()) ;
	reply.setContentType(getBrowseType());
	return reply ;
    }

    /**
     * We hanlde (in some cases) one extended method.
     * @param request The request to handle.
     */

    public Reply extended (Request request)
	throws HTTPException, ClientException
    {
	String method = request.getMethod() ;
	if ((method != null) && method.equals("BROWSE") && getBrowsableFlag())
	    return browse(request) ;
	return super.extended(request) ;
    }

    public void initialize(Object values[]) {
	super.initialize(values);
	if ( getBrowsableFlag() )
	    allowed = _browse_allowed;
    }
}
