// BrokerFrame.java
// $Id: BrokerFrame.java,v 1.3 1998/01/26 13:20:52 bmahe Exp $
// (c) COPYRIGHT MIT and INRIA, 1997.
// Please first read the full copyright statement in file COPYRIGHT.html

package org.w3c.jigsaw.admin;

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

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

import org.w3c.tools.resources.ProtocolException;
import org.w3c.tools.resources.NotAProtocolException;

public class BrokerFrame extends HTTPFrame {

  class LookupFrameState {
    private int     index ;
    private String  components[] ;

    void parseQuery(String query) {
      StringTokenizer st = new StringTokenizer(query, "?");
      int nbTokens = st.countTokens();
      components = new String[nbTokens];
      for (int i = 0 ; i < nbTokens ; i++)
	components[i] = st.nextToken();
      index = 0;
    }

    public boolean hasMoreComponents() {
      return index < components.length ;
    }

    public final String getNextComponent() {
      return components[index++] ;
    }

    LookupFrameState (String query) {
      parseQuery(query);
    }
  }


  protected ResourceBroker broker = null;

  public void registerResource(FramedResource resource) {
    super.registerResource(resource);
    if (resource instanceof ResourceBroker)
      broker = (ResourceBroker) resource;
  }

  
  /**
   * 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 ProtocolException Always thrown.
   */

  protected void error(Request request, String msg) 
    throws ProtocolException
  {
    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(ResourceBroker.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 ProtocolException If the request type doesn't match admin.
   */

  protected void checkContentType(Request request) 
    throws ProtocolException
  {
    if ( request.getContentType().match(ResourceBroker.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 ProtocolException 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 ProtocolException
  {
    // 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 ProtocolException If the resource couldn't be located.
   */

  public ResourceReference lookup(Request request) 
    throws ProtocolException
  {
    // Create lookup state and get rid of root resource requests:
    LookupState   ls = null;
    try {
      ls = new LookupState(request);
    } catch (org.w3c.tools.resources.ProtocolException ex) {
      ex.printStackTrace();
      throw new HTTPException(ex);
    }
    LookupResult  lr = new LookupResult(null);
    ResourceReference rr = null; // cr
    ls.markInternal();
    if ( ! ls.hasMoreComponents() ) 
      return admin.getRootReference();
    // Lookup the target resource:
    String name = ls.getNextComponent();
    ServerHandler sh = shm.lookupServerHandler(name);
    if ( sh == null ) {
      if(name.equals("realms")) {
      	rr = admin.getRealmCatalogResource();
      } else if (name.equals("control")) {
	rr = admin.getControlResource();
      } else {
	error(request, "unknown server handler");
      }
    } else {
      // Lookup that resource, from the config resource of that server:
      rr = sh.getConfigResource();
    }
    if ( rr != null ) {
      ResourceReference rr_temp = null;
      while ( ls.hasMoreComponents() ) {
	try {
	  if (rr == null)
	    error(request, "url too long");
	  Resource r = rr.lock();
	  if ( ! ( r instanceof ContainerInterface) )
	    error(request, "url too long");
	  rr_temp = ((ContainerInterface) r).lookup(ls.getNextComponent());
	} catch (InvalidResourceException ex) {
	  error(request, "unable to restore resource");
	} finally {
	  rr.unlock();
	  rr = rr_temp;
	}
      }
      if ( rr == null )
	error(request, "unknown resource");
      String query = request.getQueryString();
      if ( query != null ) {
	try {
	  Resource r = rr.lock();
	  // Querying a frame !
	  if ( ! (r instanceof FramedResource) )
	    error(request, "not a framed resource");
	} catch (InvalidResourceException ex) {
	  error(request, "unable to restore resource");
	} finally {
	  rr.unlock();
	}

	//search the right frame
	LookupFrameState lfs = new LookupFrameState(query);
	
	String frameName = null;
	ResourceReference the_rrf   = rr; 
	ResourceReference[] rra = null;	  

	while (lfs.hasMoreComponents()) {

	  try {
	    rra = ((FramedResource) the_rrf.lock()).getFramesReference();
	    if (rra == null)
	      error(request, "unknown frame");
	  } catch (InvalidResourceException ex) {
	    error(request, ex.getMessage());
	  } finally {
	    the_rrf.unlock();
	  }

	  frameName = lfs.getNextComponent();
	  ResourceReference rrf   = null; 
	  ResourceFrame     frame = null;

	  for (int i = 0 ; i < rra.length ; i++) {
	    rrf = rra[i];
	    try {
	      frame = (ResourceFrame) rrf.lock();
	      if (frame.getIdentifier().equals(frameName)) {
		the_rrf = rrf;
		break;
	      }
	    } catch (InvalidResourceException ex) {
	      error(request, ex.getMessage());
	    } finally {
	      rrf.unlock();
	    }
	  }
	  if (the_rrf == null)
	    error(request,"unknown frame");
	}
	
	return the_rrf;

	// 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 rr;
      }
    }
    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 ProtocolException If some error occurs.
   */

  public Reply remoteSetValues(Request request) 
    throws ProtocolException
  {
    DataInputStream in = getDataInputStream(request);
    // Get the target resource to act on:
    ResourceReference rr = lookup(request);
    // Decode the new attribute values:
    try {
      Resource r = rr.lock();
      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");
      }
    } catch (InvalidResourceException ex) {
      ex.printStackTrace();
      error(request, "Invalid resource");
    } finally {
      rr.unlock();
    }
    // 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 ProtocolException If some error occurs.
   */

  public Reply remoteGetValues(Request request) 
    throws ProtocolException
  {
    DataInputStream in = getDataInputStream(request);
    // Get the target resource:
    ResourceReference     rr   = lookup(request);
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream      out  = new DataOutputStream(bout);
    try {
      Resource r = rr.lock();
      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) {
	ex.printStackTrace();
	error(request, "bad request");
      }
    } catch (InvalidResourceException ex) {
      ex.printStackTrace();
      error(request, "Invalid resource");
    } finally {
      rr.unlock();
    }
    // 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 ProtocolException If some error occurs.
   */

  public Reply remoteGetAttributes(Request request) 
    throws ProtocolException
  {
    ResourceReference rr = lookup(request);
    // This request has no content
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    DataOutputStream      out  = new DataOutputStream(bout);
    try {
      Resource r = rr.lock();
      writer.writeAttributes(out, r.getAttributes());
      out.close();
    } catch (IOException ex) {
      error(request, "bad request");
    } catch (InvalidResourceException ex) {
      error(request, "Invalid resource");
    } finally {
      rr.unlock();
    }
    // 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 ProtocolException If some error occurs.
   */

  public Reply remoteEnumerateIdentifiers(Request request) 
    throws ProtocolException
  {
    ResourceReference rr = lookup(request);
    try {
      Resource r = rr.lock();
      // 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());
    } catch (InvalidResourceException ex) {
      error(request, "Invalid resource");
      // not reached
      return null;
    } finally {
      rr.unlock();
    }
  }

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

  public Reply remoteLoadResource(Request request) 
    throws ProtocolException
  {
    ResourceReference rr = lookup(request);

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

  public Reply remoteRegisterFrame(Request request) 
    throws ProtocolException
  {
    ResourceReference rr = lookup(request);
    try {
      Resource r = rr.lock();
      if ( ! ( r instanceof FramedResource) )
	error(request, "can't add frame to non-framed resource");
      // Handle the request:
      try {
	DataInputStream in = getDataInputStream(request);
	// Get the request paraneters:
	String id  = in.readUTF();
	String cls = in.readUTF();
	// Create the frame:
	ResourceFrame frame = null;
	try {
	  frame = (ResourceFrame) Class.forName(cls).newInstance();
	} catch (Exception ex) {
	  error(request, "invalid frame class "+cls);
	}
	// Register the frame:
	Hashtable defs = new Hashtable(3);
	if ((id != null) && ! id.equals("") ) {
	  defs.put("identifier", id);
	}
	((FramedResource) r).registerFrame(frame, defs);
	// Send back the whole resource (inclding new frame):
	ByteArrayOutputStream bout = new ByteArrayOutputStream();
	DataOutputStream      out  = new DataOutputStream(bout);
	try {
	  writer.writeResource(out, frame);
	  out.close();
	} catch (IOException ex) {
	  error(request, "bad request");
	}
	return okReply(request, bout.toByteArray());
      } catch (IOException ex) {
	error(request, "bad request");
      }
    } catch (InvalidResourceException ex2) {
      error(request, "invalid resource");
    } finally {
      rr.unlock();
    }
    // not reached:
    return null;
  }

  public Reply remoteUnregisterFrame(Request request) 
    throws ProtocolException
  {
    ResourceReference rr = lookup(request);
    try{
      Resource r = rr.lock();
      if(!(r instanceof FramedResource)) {
	error(request, "Can't unregister frames from a non-framed" +
	      " resource");
      }
      try {
	DataInputStream in = getDataInputStream(request);
	String identifier = in.readUTF();
	// find the indentifier
	ResourceFrame f[] = ((FramedResource) r).getFrames();
	for (int i = 0 ; i < f.length ; i++)
	  if (f[i].getIdentifier().equals(identifier)) {
	    ((FramedResource) r).unregisterFrame(f[i]);
	    return okReply(request);
	  }
	error(request, "Frame " + identifier + " not registered");
      } catch (IOException ex) {
	error(request, "bad request");
      }
    } catch (InvalidResourceException ex2) {
      error(request, "invalid resource");
    } finally {
      rr.unlock();
    }
    // Not reached
    return null;
   }

  public Reply remoteRegisterResource(Request request) 
    throws ProtocolException
  {
    // Check target resource class:
    ResourceReference rr = lookup(request);
    try {
      Resource r = rr.lock();
      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:
	try {
	((ContainerInterface) r).registerResource(id, child, null);
	} catch (InvalidResourceException ex) {
	  error(request, ex.getMessage());
	}
	// 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");
      }
    } catch (InvalidResourceException ex) {
      error(request, "Invalid resource");
    } finally {
      rr.unlock();
    }
    // not reached:
    return null;
  }

  public Reply remoteReindexResource(Request request) 
    throws ProtocolException
  {
    // Check target resource class:
    ResourceReference rr = lookup(request);
    // Handle request:
    try {
      Resource r = rr.lock();
      if(r != null) {
	if (r instanceof org.w3c.tools.resources.DirectoryResource) {
	  org.w3c.tools.resources.DirectoryResource dir = 
	    (org.w3c.tools.resources.DirectoryResource) r;
	  dir.reindex();
	  return okReply(request);
	} else {
	  error(request, "Can't reindex this resource"+
		"(not a DirectoryResource)");
	}
      } else {
	error(request, "Bad request");
      }
    } catch (InvalidResourceException ex) {
      error(request, "Invalid resource");
    } finally {
      rr.unlock();
    }
    // not reached
    return null;
  }

  public Reply remoteDeleteResource(Request request) 
    throws ProtocolException
  {
    // Check target resource class:
    ResourceReference rr = lookup(request);
    // Handle request:
    try {
      Resource r = rr.lock();
      if(r != null) {
	try {
	  r.delete();
	} catch (MultipleLockException ex) {
	  error(request, ex.getMessage());
	}
	return okReply(request);
      } else {
	error(request, "Bad request");
      }
    } catch (InvalidResourceException ex) {
      error(request, "Invalid resource");
    } finally {
      rr.unlock();
    }
    // not reached
    return null;
  }

  public Reply extended(Request request) 
    throws ProtocolException, NotAProtocolException
  {
    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("REINDEX-RESOURCE") ) {
      checkContentType(request);
      return remoteReindexResource(request);
    } else if (mth.equals("UNREGISTER-FRAME")) {
      checkContentType(request);
      return remoteUnregisterFrame(request);
    } else if (mth.equals("REGISTER-FRAME")) {
      checkContentType(request);
      return remoteRegisterFrame(request);
    } else if (mth.equals("UNREGISTER-FILTER")) {
      //      checkContentType(request);
      // return remoteUnregisterFilter(request);
      error(request, "Not yet implemented");
      // not reached
      return null;
    } else if (mth.equals("REGISTER-FILTER")) {
      //      checkContentType(request);
      //      return remoteRegisterFilter(request);
      error(request, "Not yet implemented");
      // not reached
      return null;
    } else {
      return super.extended(request);
    }
  }

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

}
