// ResourceBroker.java
// $Id: ResourceBroker.java,v 1.16 1997/07/30 12:00:31 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html


package w3c.jigsaw.admin;

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

import w3c.jigsaw.daemon.*;
import w3c.tools.store.*;
import w3c.www.mime.*;
import w3c.www.http.*;
import w3c.jigsaw.http.*;
import w3c.jigsaw.resources.*;

/**
 * The server side resource broker.
 */

public class ResourceBroker extends HTTPResource {
    /**
     * The MIME type Jigsaw uses for tunneling the admin protocol.
     */
    public static MimeType conftype = null;
    static {
	try {
	    conftype = new MimeType("x-jigsaw/config");
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
    }

    /**
     * The object that knows how to write the admin protocol.
     */
    protected AdminWriter writer = null;
    /**
     * The ServerHandlerManager we export.
     */
    protected ServerHandlerManager shm = null;
    /**
     * The controlling ServerHandler.
     */
    protected AdminServer admin = null;

    /**
     * Trigger an HTTP exception.
     * @param request The request we couldn't fulfill.
     * @param msg The error message.
     * @exception HTTPException Always thrown.
     */

    protected void error(Request request, String msg) 
	throws HTTPException
    {
	Reply reply = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
	reply.setContent(msg);
	throw new HTTPException(reply);
    }

    protected Reply okReply(Request request, byte bits[]) {
	Reply reply = request.makeReply(HTTP.OK);
	reply.setContentType(conftype);
	if ( bits != null ) {
	    ByteArrayInputStream in = new ByteArrayInputStream(bits);
	    reply.setContentLength(bits.length);
	    reply.setStream(in);
	}
	return reply;
    }

    protected Reply okReply(Request request) {
	return okReply(request, null);
    }

    /**
     * Check that request incomming content type.
     * @param request The request to check.
     * @exception HTTPException If the request type doesn't match admin.
     */

    protected void checkContentType(Request request) 
	throws HTTPException
    {
	if ( request.getContentType().match(conftype) < 0 ) 
	    error(request, "invalid MIME type: "+request.getContentType());
    }

    /**
     * Get a data input stream out of that request input stream
     * @param request The request to get data from.
     * @exception HTTPException If we couldn't get the request's content.
     * @return A DataInputStream instance to read the request's content.
     */

    protected DataInputStream getDataInputStream(Request request) 
	throws HTTPException
    {
	// How fun HTTP/1.1 is, allowing us to double the network traffic :-(
	// If this is a 1.1 request, send a 100 continue:
	Client client = request.getClient();
	if ( client != null ) {
	    try {
		client.sendContinue();
	    } catch (IOException ex) {
		throw new HTTPException(ex.getMessage());
	    }
	}
	// Now, only, get the data:
	try {
	    return new DataInputStream(request.getInputStream());
	} catch (IOException ex) {
	    error(request, "invalid request");
	}
	// not reached:
	return null;
    }

    /**
     * Lookup the target of the given request.
     * @param request The request whose target is to be fetched.
     * @return A Resource instance.
     * @exception HTTPException If the resource couldn't be located.
     */

    public Resource lookup(Request request) 
	throws HTTPException
    {
	// Create lookup state and get rid of root resource requests:
	LookupState   ls = new LookupState(request);
	LookupResult  lr = new LookupResult(null);
	Resource cr = null;
	ls.markInternal();
	if ( ! ls.hasMoreComponents() ) 
	    return admin.getRoot();
	// Lookup the target resource:
	String name = ls.getNextComponent();
	ServerHandler sh = shm.lookupServerHandler(name);
	if ( sh == null ) {
	    if(name.equals("realms")) {
		cr = admin.getRealmCatalogResource();
	    } else if (name.equals("control")) {
		cr = admin.getControlResource();
	    } else {
		error(request, "unknown server handler");
	    }
	} else {
	    // Lookup that resource, from the config resource of that server:
	    cr = sh.getConfigResource();
	}
	if ( cr != null ) {
	    Resource r = cr;
	    while ( ls.hasMoreComponents() ) {
		if ( ! ( r instanceof ContainerInterface) )
		    error(request, "url too long");
		try {
		    r = ((ContainerInterface) r).lookup(ls.getNextComponent());
		} catch (InvalidResourceException ex) {
		    error(request, "unable to restore resource");
		}
	    }
	    if ( r == null )
		error(request, "unknown resource");
	    String query = request.getQueryString();
	    if ( query != null ) {
		// Querying a filter !
		if ( ! (r instanceof FilteredResource) )
		    error(request, "not a filtered resource");
		ResourceFilter f[] = ((FilteredResource) r).getFilters();
		for (int i = 0 ; i < f.length ; i++)
		    if (f[i].getIdentifier().equals(query))
			return f[i];
	    } else {
		// Emit back this resource (after a check):
		return r;
	    }
	} else {
	    return cr;
	}
	error(request, "unknown resource");
	// not reached
	return null;
    }

    /**
     * Set a set of attribute values for the target resource.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteSetValues(Request request) 
	throws HTTPException
    {
	DataInputStream in = getDataInputStream(request);
	// Get the target resource to act on:
	Resource r = lookup(request);
	// Decode the new attribute values:
	Attribute attrs[] = r.getAttributes();
	int       idx     = -1;
	try {
	    while ((idx = in.readInt()) != -1) {
		Attribute a = attrs[idx];
		Object    v = a.unpickle(in);
		try {
		    r.setValue(idx, v);
		} catch (IllegalAttributeAccess ex) {
		    ex.printStackTrace();
		}
	    }
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// All the changes done, return OK:
	return okReply(request);
    }

    /**
     * Get a set of attribute values.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteGetValues(Request request) 
	throws HTTPException
    {
	DataInputStream in = getDataInputStream(request);
	// Get the target resource:
	Resource              r    = lookup(request);
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	Attribute attrs[] = r.getAttributes();
	int       idx     = -1;
	try {
	    while ((idx = in.readInt()) != -1) {
		Object v = r.getValue(idx, null);
		if ((v != null) && !attrs[idx].checkFlag(Attribute.DONTSAVE)) {
		    out.writeInt(idx);
		    attrs[idx].pickle(out, v);
		} else {
		    out.writeInt(-2-idx);
		}
	    }
	    out.writeInt(-1);
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    /**
     * Get the set of attributes for the given resource.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteGetAttributes(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);
	// This request has no content
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	try {
	    writer.writeAttributes(out, r.getAttributes());
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    /**
     * Enumerate the resource identifiers of that resource.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteEnumerateIdentifiers(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);
	// Check that the resource is a container resource:
	if ( ! (r instanceof ContainerInterface) ) 
	    error(request, "not a container");
	ContainerInterface cr = (ContainerInterface) r;
	// This request has no content
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	Enumeration           e    = cr.enumerateResourceIdentifiers(true);
	try {
	    while ( e.hasMoreElements() ) 
		out.writeUTF((String) e.nextElement());
	    out.writeUTF("");
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    /**
     * Return a resource back to the client.
     * @param request The request to handle.
     * @return A Reply instance.
     * @exception HTTPException If some error occurs.
     */

    public Reply remoteLoadResource(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);

	// This request has no content
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	try {
	    writer.writeResource(out, r);
	    out.close();
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Setup the reply:
	return okReply(request, bout.toByteArray());
    }

    public Reply remoteRegisterFilter(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);
	if ( ! ( r instanceof FilteredResource) )
	    error(request, "can't add filter to non-filtered resource");
	// Handle the request:
	try {
	    DataInputStream in = getDataInputStream(request);
	    // Get the request paraneters:
	    String id  = in.readUTF();
	    String cls = in.readUTF();
	    // Create the filter:
	    ResourceFilter filter = null;
	    try {
		filter = (ResourceFilter) Class.forName(cls).newInstance();
	    } catch (Exception ex) {
		error(request, "invalid filter class "+cls);
	    }
	    // Register the filter:
	    Hashtable defs = null;
	    if ((id != null) && ! id.equals("") ) {
		defs = new Hashtable(3);
		defs.put("identifier", id);
	    } else {
		defs = null;
	    } 
	    ((FilteredResource) r).registerFilter(filter, defs);
	    // Send back the whole resource (inclding new filter):
	    ByteArrayOutputStream bout = new ByteArrayOutputStream();
	    DataOutputStream      out  = new DataOutputStream(bout);
	    try {
		writer.writeResource(out, filter);
		out.close();
	    } catch (IOException ex) {
		error(request, "bad request");
	    }
	    return okReply(request, bout.toByteArray());
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// not reached:
	return null;
    }

    public Reply remoteUnregisterFilter(Request request) 
	throws HTTPException
    {
	Resource r = lookup(request);
	
	if(!(r instanceof FilteredResource)) {
	    error(request, "Can't unregister frames from a non-filtered" +
		  " resource");
	}
	try {
	    DataInputStream in = getDataInputStream(request);
	    String identifier = in.readUTF();
	    // find the indentifier
	    System.out.println("UNREGISTERING " + identifier);
	    ResourceFilter f[] = ((FilteredResource) r).getFilters();
	    for (int i = 0 ; i < f.length ; i++)
		if (f[i].getIdentifier().equals(identifier)) {
		    ((FilteredResource) r).unregisterFilter(f[i]);
		    System.out.println("UNREGISTERED " + identifier);
		    return okReply(request);
		}
	    error(request, "Filter " + identifier + " not registered");
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// Not reached
	return null;
    }

    public Reply remoteRegisterResource(Request request) 
	throws HTTPException
    {
	// Check target resource class:
	Resource r = lookup(request);
	if ( ! ( r instanceof ContainerInterface) )
	    error(request, "can't add child in non-container");
	// Handle request:
	try {
	    DataInputStream in = getDataInputStream(request);
	    // Get the request parameters:
	    String id  = in.readUTF();
	    String cls = in.readUTF();
	    // Create the resource:
	    Resource child = null;
	    try {
		child = (Resource) Class.forName(cls).newInstance();
	    } catch (Exception ex) {
		error(request, "invalid resource class "+cls);
	    }
	    // Add it to the container:
	    ((ContainerInterface) r).registerResource(id, child, null);
	    // Write back the new resource:
	    ByteArrayOutputStream bout = new ByteArrayOutputStream();
	    DataOutputStream      out  = new DataOutputStream(bout);
	    try {
		writer.writeResource(out, child);
		out.close();
	    } catch (IOException ex) {
		error(request, "bad request");
	    }
	    return okReply(request, bout.toByteArray());
	} catch (IOException ex) {
	    error(request, "bad request");
	}
	// not reached:
	return null;
    }

    public Reply remoteDeleteResource(Request request) 
	throws HTTPException
    {
	// Check target resource class:
	Resource r = lookup(request);
	// Handle request:
	if(r != null) {
	    r.delete();
	    return okReply(request);
	} else {
	    error(request, "Bad request");
	}
	// not reached:
	return null;
    }

    public Reply extended(Request request) 
	throws HTTPException, ClientException
    {
	String mth = request.getMethod();
	if ( mth.equals("SET-VALUES") ) {
	    checkContentType(request);
	    return remoteSetValues(request);
	} else if (mth.equals("GET-VALUES") ) {
	    checkContentType(request);
	    return remoteGetValues(request);
	} else if (mth.equals("GET-ATTRIBUTES")) {
	    checkContentType(request);
	    return remoteGetAttributes(request);
	} else if (mth.equals("ENUMERATE-IDENTIFIERS")) {
	    checkContentType(request);
	    return remoteEnumerateIdentifiers(request);
	} else if (mth.equals("LOAD-RESOURCE")) {
	    checkContentType(request);
	    return remoteLoadResource(request);
	} else if (mth.equals("REGISTER-RESOURCE") ) {
	    checkContentType(request);
	    return remoteRegisterResource(request);
	} else if (mth.equals("DELETE-RESOURCE") ) {
	    checkContentType(request);
	    return remoteDeleteResource(request);
	} else if (mth.equals("UNREGISTER-FILTER")) {
	    checkContentType(request);
	    return remoteUnregisterFilter(request);
	} else if (mth.equals("REGISTER-FILTER")) {
	    checkContentType(request);
	    return remoteRegisterFilter(request);
	} else {
	    return super.extended(request);
	}
    }

    /**
     * A real funny way to create resources..
     * @param shm The server handler manager instance to administer.
     * @param server The AdminServer instance.
     * @param writer The encoder for the Admin protocol.
     */

    public ResourceBroker(ServerHandlerManager shm
			  , AdminServer admin
			  , AdminWriter writer) {
	this.shm    = shm;
	this.admin  = admin;
	this.writer = writer;
    }
	
}
