jigsaw bugs and improvements...

Hi !

----

Here comes a list of possible improvements/bug reports:

- org.w3c.www.mime.MimeType:
    - should be cloneable (implementing interface java.lang.Cloneable)
    - should contain static MimeType TEXT_XML (text/xml)
    - should contain static MimeType APPLICATION_OCTET_STREAM
(application/octet-stream)
    - MATCH_* identifers should get some docs about how to compare them
with
      `<` or `>` operators (or simply add their real int-values in the
javadoc entries)

- org.w3c.jigsaw.frames.Redirecterframe
  - does not redirect subpaths, e.g. /redirect_me redirects to
/hello_world, then
      /redirect/foo_bar/beep_beep is redirected to /hello_world; IMHO it
should be redirected to
      /hello_world/foo_bar/beep_beep
      (If the subpath is redirected, the RedirecterFrame may act like an
alias resource...)

- org.w3c.jigsaw.frames.NegotiatedFrame has been improved a little bit:
    I've added a flag "first_match" which returns the first resource
matching a request, instead of
    sening the "multiple choice" page (which may confuse a browser which
has requested an image,
    and got a text/html page...).
    I didn't test it, but IMHO it should work.
    TODO: - This is currently not implemented for PUT
                 - Not tested yet. Seems that the "first_match" flag is
recognized by the GUI, the rest
                     is theory...
    I've added the source of NegotiatedFrame.java V1.23 as an
attachment...
    In the source is a small note about a (possible) bug, see
HttpAcceptVector class...

    An exception within negotiateContent() is hidden (e.g. no
printStackTrace() output is promted to
    console) - in this case, I need half an hour to dig-out a
NullPointerException...

    The multiple choice pages now using Style-Sheets like the direcory
output, but I didn't implement
    the nice icons for each resource yet.

----

Bye,
Roland

--
  __ .  . __
 (o.\ \/ /.o)  Roland Mainz                               C programmer
  \__\/\/__/   Roland.Mainz@informatik.med.uni-giessen.de MPEG specialist
  /O /==\ O\   gisburn@w-specht.rhein-ruhr.de             Sun&&Amiga programmer
 (;O/ \/ \O;)  TEL +49 (0) 2426901568  FAX +49 (0) 2426901569
// NegotiatedFrame.java
// $Id: NegotiatedFrame.java,v 1.23 1999/04/21 22:08:11 gisburn Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html

// new for V1.23
// - added "first_match" attribute
//   FIXME: This has not been implemented for "PUT" !!!
// - added HttpAcceptVector class for simple management of the HttpAccept values
//   - found a possible bug, see HttpAcceptVector
// - question: why has negotiateContentType() everytimes a return value of false ?

// new for V1.22:
// - */* in "Accept"-header is now replaced by text/html to solve a problem...
// - multiple choice page now gets a style sheed added;
// 
// note to Yves/Benoit: I'm too tired (0:20h Thursday (late again :-(  )) to to the rest, but it would be nice
// - if the multiple-choice page looks like the "normal" directory listing
// - "first_match" works for "PUT", too

package org.w3c.jigsaw.frames;

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

import org.w3c.tools.resources.*;
import org.w3c.jigsaw.http.* ;
import org.w3c.jigsaw.html.*;
import org.w3c.jigsaw.html.HtmlGenerator ;
import org.w3c.www.mime.* ;
import org.w3c.www.http.*;

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

/**
 * Content negotiation frame.
 * Selects between variants of the same "thing" in different formats based
 * on information from the requesting client and variant attributes like language,
 * content-type or content-encoding.
 *  @author Yves Lafon <ylafon@w3.org>
 *  @author Benoît Mahé <bmahe@w3.org>
 *  @author Roland Mainz <Roland.Mainz@informatik.med.uni-giessen.de>
 */
public class NegotiatedFrame extends HTTPFrame {
  
    class VariantState {
	ResourceReference variant = null ;
	double qs      = 0.0 ;
	double qe      = 0.0 ;
	double qc      = 0.0 ;
	double ql      = 0.0 ;
	double q       = 0.0 ;	// quality (mime type one)
	double Q       = 0.0 ;	// the big Q

	public String toString() {
	    try {
		Resource res = variant.lock();
		String name = (String) res.getIdentifier() ;
		if ( name == null )
		    name = "<noname>" ;
		return "[" + name 
		    + " qs=" + qs 
		    + " qe=" + qe
		    + " ql=" + ql
		    + " q =" + q
		    + " Q =" + getQ() 
		    +"]" ;
	    } catch (InvalidResourceException ex) {
		return "invalid";
	    } finally {
		variant.unlock();
	    }
	}

	void setContentEncodingQuality (double qe) {
	    this.qe = qe ;
	}

	void setContentEncodingQuality (HttpAcceptEncoding e) {
	    this.qe = e.getQuality();
	}

	double getContentEncodingQuality () {
	    return qe ;
	}

	void setQuality (double q) {
	    this.q = q ;
	}

	void setQuality (HttpAccept a) {
	    q = a.getQuality() ;
	}

	void setLanguageQuality (double ql) {
	    this.ql = ql ;
	}

	void setLanguageQuality (HttpAcceptLanguage l) {
	    this.ql = l.getQuality() ;
	}

	double getLanguageQuality () {
	    return ql ;
	}

	ResourceReference getResource () {
	    return variant ;
	}

	double getQ() {
	    return qe * q * qs * ql ;
	}

	VariantState (ResourceReference variant, double qs) {
	    this.qs      = qs ;
	    this.variant = variant ;
	}
    }

    private static Class httpFrameClass = null;

    static {
	try {
	    httpFrameClass = Class.forName("org.w3c.jigsaw.frames.HTTPFrame") ;
	} catch (Exception ex) {
	    throw new RuntimeException("No HTTPFrame class found.");
	}
    }

    /**
     * Our Icon property.
     */
    public static String NEGOTIATED_ICON_P = 
	"org.w3c.jigsaw.frames.negotiated.icon";
    /**
     * Our default Icon
     */
    public static String DEFAULT_NEGOTIATED_ICON = "generic.gif";

    /**
     * Turn debugging on/off.
     */
    private static final boolean debug = false;
    /**
     * Minimum quality for a resource to be considered further.
     */
    private static final double REQUIRED_QUALITY = 0.0001 ;
    /**
     * The Vary header field for this resource is always the same.
     */
    protected static HttpTokenList VARY = null;

    /**
     * Attribute index - The set of names of variants.
     */
    protected static int ATTR_VARIANTS = -1 ;
    /**
     * Attribute index - Should the PUT needs to be strictly checked?
     */
    protected static int ATTR_PUT_POLICY = -1;
    
    /**
     * Use first object matching our needs, or create a "multiple choice" page ?
     */
    protected static int ATTR_FIRST_MATCH = -1;    

    static {
	// Compute and initialize the Vary header once and for all
	String vary[] = { "Accept",
			  "Accept-Charset",
			  "Accept-Language",
			  "Accept-Encoding" };
	VARY = HttpFactory.makeStringList(vary);
    }

    static {
	Attribute   a = null ;
	Class     cls = null ;
	try {
	    cls = Class.forName("org.w3c.jigsaw.frames.NegotiatedFrame") ;
	} catch (Exception ex) {
	    ex.printStackTrace() ;
	    System.exit(1) ;
	}
	
	// The names of the varint we negotiate
	a = new StringArrayAttribute("variants"
				     , null
				     , Attribute.EDITABLE) ;
	ATTR_VARIANTS = AttributeRegistry.registerAttribute(cls, a);
	
	// "PUT" policy
	a = new BooleanAttribute("strict_put"
				 , new Boolean(true)
				 , Attribute.EDITABLE);
	ATTR_PUT_POLICY = AttributeRegistry.registerAttribute(cls, a);
	
	// no "multiple choice" page ?
	a = new BooleanAttribute("first_match"
				 , new Boolean(false)
				 , Attribute.EDITABLE);
	ATTR_FIRST_MATCH = AttributeRegistry.registerAttribute(cls, a);
    }

    public String getIcon() {
	String icon = super.getIcon();
	if (icon == null) {
	    icon = 
		getServer().getProperties().getString(NEGOTIATED_ICON_P,
						      DEFAULT_NEGOTIATED_ICON);
	    setValue(ATTR_ICON, icon);
	}
	return icon;
    }

    /**
     * Get the variant names.
     */
    public String[] getVariantNames() {
	return (String[]) getValue(ATTR_VARIANTS, null) ;
    }

    /**
     * Set the variant names.
     */
    public void setVariants(String variants[]) {
	setValue(ATTR_VARIANTS, variants);
    }

    /**
     * Get the "strictness" of the PUT checking.
     */
    public boolean getPutPolicy() {
	Boolean val = (Boolean) getValue(ATTR_PUT_POLICY, null);
	if (val == null) // strict by default
	    return true;
	return val.booleanValue();
    }

    /**
     * Set the "strictness" of the PUT checking.
     */    
    public void setPutPolicy(Boolean strict) {
	setValue(ATTR_PUT_POLICY, strict);
    }
    
    /**
     * Set the "strictness" of the PUT checking.
     */ 
    public void setPutPolicy(boolean strict) {
	setValue(ATTR_PUT_POLICY, new Boolean(strict));
    }

    /**
     * Get the "use or use not multiple choice page" value.
     */
    public boolean getFirstMatch() {
	Boolean val = (Boolean) getValue(ATTR_FIRST_MATCH, null);
	if (val == null) // use multiple choice by default
	    return( false );
	return val.booleanValue();
    }

    /**
     * Set the "use or use not multiple choice page" value.
     */    
    public void setFirstMatch(Boolean useFirstMatch) {
	setValue(ATTR_FIRST_MATCH, useFirstMatch);
    }
    
    /**
     * Set the "use or use not multiple choice page" value.
     */ 
    public void setFirstMatch(boolean useFirstMatch) {
	setValue(ATTR_FIRST_MATCH, new Boolean(useFirstMatch));
    }
    
    
    /**
     * Get the variant resources.
     * This is somehow broken, it shouldn't allocate the array of variants
     * on each call. However, don't forget that the list of variants can be
     * dynamically edited, this means that if we are to keep a cache of it 
     * (as would be the case if we kept the array of variants as instance var)
     * we should also take care of editing of attributes (possible, but I
     * just don't have enough lifes).
     * @return An array of ResourceReference, or <strong>null</strong>.
     * @exception ProtocolException If one of the variants doesn't exist.
     */
    public ResourceReference[] getVariantResources() 
	throws ProtocolException
    {
	// Get the variant names:
	String names[] = getVariantNames() ;
	if ( names == null )
	    return null ;
	    
	// Look them up in our parent directory:
	ResourceReference variants[] = new ResourceReference[names.length] ;
	ResourceReference r_parent   = resource.getParent() ;
	try {
	    DirectoryResource parent = (DirectoryResource) r_parent.lock();
	    int missing = 0;
	    for (int i = 0 ; i < names.length ; i++) {
		variants[i] = parent.lookup(names[i]) ;
		if (variants[i] == null)
		    missing++;
	    }
	    if (missing > 0) {
		int kept = names.length - missing;
		if (kept < 1)
		    return null;
		String newNames[] = new String[kept];
		int j = 0; int i = 0;
		while (i < variants.length) {
		    if (variants[i] != null) {
			newNames[j++] = names[i++];
		    } else {
			i++;
		    }
		}
		setVariants(newNames);
		
		//recompute Variant Resources
		return getVariantResources();
	    }
	} catch (InvalidResourceException ex) {
	    throw new HTTPException("invalid parent for negotiation");
	} finally {
	    r_parent.unlock();
	}
	return variants ;
    }


    /**
     * Print the current negotiation state.
     * @param header The header to print first.
     * @param states The current negotiation states.
     */  
    protected void printNegotiationState (String header, Vector states) {
	if ( debug ) {
	    System.out.println ("------" + header) ;
	    for (int i = 0 ; i < states.size() ; i++) {
		VariantState state = (VariantState) states.elementAt(i) ;
		System.out.println (state) ;
	    }
	    System.out.println ("-----") ;
	}
    }
  
    /**
     * Negotiate among content encodings.
     * <p>BUG: This will work only for single encoded variants.
     * @param states The current negotiation states.
     * @param request The request to handle.
     * @return a boolean.
     * @exception ProtocolException If one of the variants doesn't exist.
     */
  
    protected boolean negotiateContentEncoding (Vector states,
						Request request) 
	throws ProtocolException
    {
	if ( ! request.hasAcceptEncoding() ) {
	    // All encodings accepted:
	    for (int i = 0 ; i < states.size() ; i++) {
		VariantState state = (VariantState) states.elementAt(i) ;
		state.setContentEncodingQuality(1.0) ;
	    }
	} else {
	    HttpAcceptEncoding encodings[] = request.getAcceptEncoding() ;
	    for (int i = 0 ; i < states.size() ; i++) {
		VariantState state    = (VariantState) states.elementAt(i) ;
		ResourceReference rr  = state.getResource();
		try {
		    FramedResource resource = (FramedResource)rr.lock() ;
		    HTTPFrame itsframe = 
			(HTTPFrame) resource.getFrame(httpFrameClass);
		    if (itsframe != null) {
			String ve;
			if ( !itsframe.definesAttribute("content-encoding") ) {
			    ve = "identity"; // default encoding
			    state.setContentEncodingQuality (1.0) ;
			} else {
			    ve = itsframe.getContentEncoding() ;
			    state.setContentEncodingQuality (0.001) ;
			}
			int jidx    = -1 ;
			for (int j = 0 ; j < encodings.length ; j++) {
			    if (encodings[j].getEncoding().equals(ve)) {
				jidx = j;
				break;
			    } 
			    if (encodings[j].getEncoding().equals("*"))
				jidx = j; // default '*' if no better match
			}
			if ( jidx >= 0 ) 
			    state.setContentEncodingQuality
				(encodings[jidx]) ;
		    }
		} catch (InvalidResourceException ex) {
		    
		} finally {
		    rr.unlock();
		}
	    }
	    // FIXME We should check here against unlegible variants as now
	}
	return false ;
    }

    /**
     * Negotiate on charsets.
     * <p>BUG: Not implemented yet.
     * @param states The current states of negotiation.
     * @param request The request to handle.
     */

    protected boolean negotiateCharsetQuality (Vector states
					       , Request request) {
	return false ;
    }

    /**
     * Negotiate among language qualities.
     * <p>BUG: This will only work for variants that have one language tag.
     * @param states The current states of negotiation.
     * @param request The request to handle.
     * @return a boolean.
     * @exception ProtocolException If one of the variants doesn't exist.
     */

    protected boolean negotiateLanguageQuality (Vector states
						, Request request) 
	throws ProtocolException
    {
	if ( ! request.hasAcceptLanguage() ) {
	    for (int i = 0 ; i < states.size() ; i++) {
		VariantState state = (VariantState) states.elementAt(i) ;
		state.setLanguageQuality (1.0) ;
	    }
	} else {
	    HttpAcceptLanguage languages[] = request.getAcceptLanguage() ;
	    boolean  varyLang    = false ;
	    for (int i = 0 ; i < states.size() ; i++) {
		VariantState state    = (VariantState) states.elementAt(i) ;
		ResourceReference rr  = state.getResource();
		try {
		    FramedResource resource = (FramedResource)rr.lock() ;
		    HTTPFrame itsframe = 
			(HTTPFrame) resource.getFrame(httpFrameClass);
		    if (itsframe != null) {
			if ( !itsframe.definesAttribute("content-language") ) {
			    state.setLanguageQuality (-1.0) ;
			} else {
			    varyLang = true ;
			    String lang = itsframe.getContentLanguage() ;
			    int jidx    = -1 ;
			    for (int j = 0 ; j < languages.length ; j++) {
				if ( languages[j].getLanguage().equals(lang) )
				    jidx = j ;
			    }
			    if ( jidx < 0 ) 
				state.setLanguageQuality(0.001) ;
			    else 
				state.setLanguageQuality (languages[jidx]) ;
			}
		    }
		} catch (InvalidResourceException ex) {
		    //FIXME
		} finally {
		    rr.unlock();
		}
	    }
	    if ( varyLang ) {
		for (int i = 0 ; i < states.size() ; i++) {
		    VariantState s = (VariantState) states.elementAt(i);
		    if ( s.getLanguageQuality() < 0 )
			s.setLanguageQuality (0.5) ;
		}
	    } else {
		for (int i = 0 ; i < states.size() ; i++) {
		    VariantState s = (VariantState) states.elementAt(i) ;
		    s.setLanguageQuality (1.0) ;
		}
	    }
	}
	return false ;
    }

    
    // HttpAccept vector for simple modifications on the "accept" header contents
    // note to Yves/Benoit: What about moving this class to org.w3c.www.http package 
    // (after some additional extensions ?)
    class HttpAcceptVector extends Vector
    {
        HttpAcceptVector( HttpAccept a[] )
        {
            super( ((a == null)?(2):(a.length + 2)) ); // create Vector instance big 
                                     // enougth to embed all elements in the given array, 
                                     // additionally two more elements for further small 
                                     // vector operations (small/smart improvement for 
                                     // later addMimeType() usage)
                                     
            // add all array members into the vector                         
            if( a != null ) for( int i = 0 ; i < a.length ; i++ ) add( a[ i ] ); 
        }
        
        
        HttpAccept acceptAt( int index )
        {
            return( (HttpAccept)elementAt( index ) );
        }
        
        
        // remove given MimeType from Accept list
        // FIXME: Are more "conformant" way would be to pass here
        //        (MimeType match, int resolution) 
        //         - "match" is the Mimetype object to compare with, 
        //         - resolution is one of the values got from 
        //           MimeType.match, e.g. MimeType.MATCH_*
        //        )
        void removeMimeType( String match )
        {
 	    for( int z = 0 ; z < size() ; z++ )
	    {
	      MimeType m = acceptAt( z ).getMimeType();
	        
	      // remove "match" of equal
	      if( m.toString().indexOf( match ) != -1 )
	      {
	        removeElementAt( z );
	      }
	    }       
        }
        

        boolean hasMimeType( MimeType match )
        {
 	    for( int z = 0 ; z < size() ; z++ )
	    {
	      MimeType m = acceptAt( z ).getMimeType();
	        
	      // match excatly ?
	      if( m.match( match ) == MimeType.MATCH_SPECIFIC_SUBTYPE )
	      {
	        return( true );
	      }
	    }
	    
	    return( false );
        }       
        
        
        void addMimeType( MimeType m )
        {
            HttpAccept ha = new HttpAccept();
            ha.setString( m.toString() ); // FIXME/possible bug found: ha.setMimeType( m ) causes a
                                          // NullPointerException in a later getMimeType()...
            add( ha );    
        }
    }
    
    
    /**
     * Negotiate among content types.
     * @param states The current states of negotiation.
     * @param request The request to handle.
     * @return a boolean.
     * @exception ProtocolException If one of the variants doesn't exist.
     */
    protected boolean negotiateContentType (Vector states,
					    Request request) 
	throws ProtocolException
    {
	if ( ! request.hasAccept() ) {
	    // All variants get a quality of 1.0
	    for (int i = 0 ; i < states.size() ; i++) {
		VariantState state = (VariantState) states.elementAt(i) ;
		state.setQuality (1.0) ;
	    }
	} else {
	    // The browser has given some preferences:
	    HttpAcceptVector accepts = new HttpAcceptVector( request.getAccept() );
	    
	    // added by gisburn 16.4.1999:
	    // some browsers like Netscape 4.x adding */* in their accept header
	    // which confuses this negotiation algorithm :-(
	    // the */* is removed here; additionally a text/html is added if not present - assuming
	    // that all browsers supports text/html
	    // this fixed problems with browsers "accepting" both text/xml and text/html
	    //
	    // a clean solution would be to check whether the "accept" string contains
	    // text/html or not, if not, add it at the end; */* should 
            accepts.removeMimeType( "*/*" );
            
            if( !accepts.hasMimeType( MimeType.TEXT_HTML ) )
            {
              // don`t add the static type here, maybe someone modifies the mimetype object later...
              // a clone() method for MimeType would be usefull...
              // (this would allow here to remove the try/catch statement, for example...)
              try
              {
                accepts.addMimeType( new MimeType( "text/html" ) );
              } 
              catch( MimeTypeFormatException exc )
              {
                exc.printStackTrace();
              }
            }
            	    
	    for (int i = 0 ; i < states.size() ; i++ ) {
		VariantState state = (VariantState) states.elementAt(i) ;
		// Get the most specific match for this variant:
		ResourceReference rr = state.getResource();
		try {
		    FramedResource resource = (FramedResource)rr.lock() ;
		    HTTPFrame itsframe = 
			(HTTPFrame) resource.getFrame(httpFrameClass);
		    if (itsframe != null) {
			MimeType vt = itsframe.getContentType();
			int jmatch = -1 ;
			int jidx   = -1 ;
			for (int j = 0 ; j < accepts.size() ; j++) {
			    int match = vt.match (accepts.acceptAt(j).getMimeType()) ;
			    if ( match > jmatch ) {
				jmatch = match ;
				jidx   = j ;
			    }
			}
			if ( jidx < 0 )
			    state.setQuality (0.0) ;
			else 
			    state.setQuality(accepts.acceptAt(jidx)) ;
		    }
		} catch (InvalidResourceException ex) {
		    //FIXME
		} finally {
		    rr.unlock();
		}
	    }	    
	}
	
	return false ;
    }


    /**
     * Negotiate among the various variants for the Resource.
     * We made our best efforts to be as compliant as possible to the HTTP/1.0
     * content negotiation algorithm.
     * @param request the incomming request.
     * @return a RefourceReference instance.
     * @exception ProtocolException If one of the variants doesn't exist.
     */
    protected ResourceReference negotiate (Request request) 
	throws ProtocolException
    {
	// Check for zero or one variant:
	ResourceReference variants[] = getVariantResources() ;
	if (variants == null) {
	    try {
		getResource().delete();
	    } catch (MultipleLockException ex) {
		//will be deleted later...
	    } finally {
		Reply reply = request.makeReply(HTTP.NOT_FOUND);
		reply.setContent ("<h1>Document not found</h1>"+
				  "<p>The document "+request.getURL()+
				  " has no acceptable variants "+
				  "(probably deleted).");
		throw new HTTPException (reply);
	    }
	}
	if ( variants.length < 2 ) {
	    if ( variants.length == 0 ) {
		try {
		    getResource().delete();
		} catch (MultipleLockException ex) {
		    //will be deleted later...
		} finally {
		    Reply reply = request.makeReply(HTTP.NOT_FOUND);
		    reply.setContent ("<h1>Document not found</h1>"+
				      "<p>The document "+request.getURL()+
				      " has no acceptable variants "+
				      "(probably deleted).");
		    throw new HTTPException (reply);
		}
	    } else {
		return variants[0] ;
	    }
	}
	// Build a vector of variant negociation states, one per variants:
	Vector states = new Vector (variants.length) ;
	for (int i = 0 ; i < variants.length ; i++) {
	    double qs = 1.0 ;
	    try {
		FramedResource resource = (FramedResource)variants[i].lock() ;
		HTTPFrame itsframe = 
		    (HTTPFrame) resource.getFrame(httpFrameClass);
		if (itsframe != null) {
		    if ( itsframe.definesAttribute ("quality") )
			qs = itsframe.getQuality() ;
		    if ( qs > REQUIRED_QUALITY )
			states.addElement(new VariantState (variants[i], qs)) ;
		}
	    } catch (InvalidResourceException ex) {
		//FIXME
	    } finally {
		variants[i].unlock();
	    }
	}
	// Content-encoding negociation:
	if ( debug )
	    printNegotiationState ("init:", states) ;
	if ( negotiateContentEncoding (states, request) ) 
	    // Remains a single acceptable variant:
	    return ((VariantState) states.elementAt(0)).getResource() ;
	if ( debug )
	    printNegotiationState ("encoding:", states) ;
	// Charset quality negociation:
	if ( negotiateCharsetQuality (states, request) ) 
	    // Remains a single acceptable variant:
	    return ((VariantState) states.elementAt(0)).getResource() ;
	if ( debug )
	    printNegotiationState ("charset:", states) ;
	// Language quality negociation:
	if ( negotiateLanguageQuality (states, request) ) 
	    // Remains a single acceptable variant:
	    return ((VariantState) states.elementAt(0)).getResource() ;
	if ( debug )
	    printNegotiationState ("language:", states) ;
	    
	// Content-type negociation:
	if ( negotiateContentType (states, request) )
	    // Remains a single acceptable variant:
	    return ((VariantState) states.elementAt(0)).getResource() ;
	if ( debug )
	    printNegotiationState ("type:", states) ;
	    
	// If we reached this point, this means that multiple variants are 
	// acceptable at this point. Keep the ones that have the best quality.
	if ( debug )
	    printNegotiationState ("before Q selection:", states) ;
	double qmax = REQUIRED_QUALITY ;
	
	for (int i=0; i< states.size() ; ) {
	    VariantState state = (VariantState) states.elementAt(i) ;
	    if ( state.getQ() > qmax ) {
		for (int j = i ; j > 0 ; j--)
		    states.removeElementAt(0) ;
		qmax = state.getQ() ;
		i = 1 ;
	    } else {
		if ( state.getQ() < qmax)
		    states.removeElementAt(i) ;
		else
		    i++;
	    }
	}
	if ( debug )
	    printNegotiationState ("After Q selection:", states) ;
	if ( qmax == REQUIRED_QUALITY ) {
	    Reply reply = request.makeReply(HTTP.NOT_ACCEPTABLE) ;
	     HtmlGenerator g = new HtmlGenerator("No acceptable");
	     g.append("<P>The resource cannot be served according to the "
		      + "headers sent</P>");
	     reply.setStream (g) ;
	    throw new HTTPException (reply) ;
	} else if ( states.size() == 1 ) {
	    return ((VariantState) states.elementAt(0)).getResource() ;
        } else if ( getFirstMatch() ) {
            // return first match instead of a multiple choice page !!
	    return ((VariantState) states.elementAt(0)).getResource() ;    
	} else {
	    // Respond with multiple choice (for the time being, there should
	    // be a parameter to decide what to do.
	    Reply reply = request.makeReply(HTTP.MULTIPLE_CHOICE) ;
	    HtmlGenerator g = new HtmlGenerator ("Multiple choice for "+
						 resource.getIdentifier()) ;
						 
            addStyleSheet( g );				     

	    g.append ("<ul>") ;
	    for (int i = 0 ; i < states.size() ; i++) {
		VariantState state = (VariantState) states.elementAt(i) ;
		String name = null;
		ResourceReference rr = state.getResource();
		try {
		    name = rr.lock().getIdentifier();
		    g.append ("<li>" 
			      + "<a href=\"" + name + "\">" + name + "</a>"
			      + " Q= " + state.getQ()) ;
		} catch (InvalidResourceException ex) {
		    //FIXME
		} finally {
		    rr.unlock();
		}
	    }
	    reply.setStream (g) ;
	    reply.setHeaderValue(reply.H_VARY, VARY);
	    throw new HTTPException (reply) ;
	}
    }


   /**
     * "negotiate" for a PUT, the negotiation of a PUT should be 
     * different as we just want to match the desciption of the entity
     * and the available variants
     * @param request, the request to handle
     * @return a ResourceReference instance
     * @exception ProtocolException If negotiating among the resource variants 
     * failed.
     * @exception ResourceException If the resource got a fatal error.
     */
    protected ResourceReference negotiatePut(Request request) 
	throws ProtocolException, ResourceException
    {	
	// Check for zero or one variant:
	ResourceReference variants[] = getVariantResources() ;
	HTTPFrame itsframe;
	int nb_v;
	// zero, don't PUT on a negotiable resource! 
	if (variants == null ||  variants.length == 0) {
	    try {
		getResource().delete();
	    } catch (MultipleLockException ex) {
		//will be deleted later...
	    } finally {
		Reply reply = request.makeReply(HTTP.NOT_FOUND);
		reply.setContent ("<h1>Document not found</h1>"+
				  "<p>The document "+request.getURL()+
				  " has no acceptable variants "+
				  "(probably deleted).");
		throw new HTTPException (reply);
	    }
	}
	// negotiate etag
	HttpEntityTag etag = request.getETag();
	HttpEntityTag etags[] = request.getIfMatch();
	// gather the etags
	if (etags == null && etag != null) {
	    etags = new HttpEntityTag[1];
	    etags[0] = etag;
	} else if (etag != null) {
	    HttpEntityTag t_etags[] = new HttpEntityTag[etags.length+1];
	    System.arraycopy(etags, 0, t_etags, 0, etags.length);
	    t_etags[etags.length] = etag;
	    etags = t_etags;
	}

	if (etags != null) {
	    // yeah go for it!
	    FramedResource resource;
	    HttpEntityTag frametag;
	    for (int i = 0 ; i < variants.length ; i++) {
		try {
		    resource = (FramedResource)variants[i].lock() ;
		    itsframe = (HTTPFrame)resource.getFrame(httpFrameClass);
		    if (itsframe != null) {
			frametag = itsframe.getETag();
			// Do we have a winner?
			for (int j=0; j<etags.length; j++)
			    if (frametag.getTag().equals(etags[j].getTag()))
				return variants[i];
		    }
		} catch (InvalidResourceException ex) {
		    //FIXME
		} finally {
		    variants[i].unlock();
		}
	    }
	    // no matching variants...
	    Reply reply = request.makeReply(HTTP.NOT_FOUND);
	    reply.setContent ("<h1>Document not found</h1>"+
			      "<p>The document "+request.getURL()+
			      " has no acceptable variants "+
			      "according to the ETag sent");
	    throw new HTTPException (reply);
	}
	
	// if we are strict, don't go any further, etags
	// is the mandatory thing, otherwise PUT on the direct version
	if (getPutPolicy()) {
	    Reply reply = request.makeReply(HTTP.NOT_FOUND);
	    reply.setContent ("<h1>Document not found</h1>"+
			      "<p>The document "+request.getURL()+
			      " has no acceptable variants "+
			      " for a PUT, as no ETags were sent");
	    throw new HTTPException (reply);
	}
	
	// now filter out variants
	nb_v = variants.length;
	MimeType type = request.getContentType();
	String   encodings[] = request.getContentEncoding();
	String   languages[] = request.getContentLanguage();
	ResourceReference rr;

	if (type != null || encodings != null || languages != null) {
	    // the request is not too bad ;)
	    for (int i = 0 ; i < variants.length ; i++) {
		if (variants[i] == null)
		    continue;
		rr = variants[i];
		try {
		    resource = (FramedResource)rr.lock() ;
		    itsframe = (HTTPFrame)resource.getFrame(httpFrameClass);
		    if (itsframe == null) {
			nb_v--;
			variants[i] = null;
			continue;
		    }
		    // remove the non matching mime types
		    if (type != null) {
			MimeType fmt = itsframe.getContentType();
			if (fmt == null || (fmt.match(type) != 
					    MimeType.MATCH_SPECIFIC_SUBTYPE)) {
			    nb_v--;
			    variants[i] = null;
			    continue;
			}
		    }
		    // remove the non matching languages
		    if (languages != null) {
			String language = itsframe.getContentLanguage();
			nb_v--;
			variants[i] = null;
			if (language == null) {
			    continue;
			}
			for (int j=0; j<languages.length; j++) {
			    if (language.equals(languages[j])) {
				nb_v++;
				variants[i] = rr;
				break;
			    }
			}
		    }
		    // remove the non matching encodings		    
		    if (encodings != null) {
			String encoding = itsframe.getContentEncoding();
			nb_v--;
			variants[i] = null;
			if (encoding == null) {
			    continue;
			}
			for (int j=0; j<encodings.length; j++) {
			    if (encoding.equals(languages[j])) {
				nb_v++;
				variants[i] = rr;
				break;
			    }
			}
		    } 
		} catch (InvalidResourceException ex) {
		    //FIXME
		} finally {
		    rr.unlock();
		}
	    }
	    // a winner!
	    if (nb_v == 1) {
		for (int i=0; i< variants.length; i++) {
		    if (variants[i] != null)
			return variants[i];
		}
	    }
	    
	    // no document matching
	    if (nb_v <= 0 ) {
		Reply reply = request.makeReply(HTTP.NOT_FOUND);
		reply.setContent ("<h1>Document not found</h1>"+
				  "<p>The document "+request.getURL()+
				  " has no acceptable variants "+
				  " for a PUT");
		throw new HTTPException (reply);
	    }
	}
	
	// now we have multiple choice :(
	String name;
	Reply reply = request.makeReply(HTTP.MULTIPLE_CHOICE) ;
	HtmlGenerator g = new HtmlGenerator ("Multiple choice for "+
					     resource.getIdentifier()) ;
	
	addStyleSheet( g );				     
					     
	g.append ("<ul>") ;
	for (int i = 0 ; i < variants.length ; i++) {
	    if (variants[i] != null) {
		try {
		    name = variants[i].lock().getIdentifier();
		    g.append ("<li>" 
			      + "<a href=\"" + name + "\">" +name+ "</a>");
		} catch (InvalidResourceException ex) {
		    //FIXME (this should NOT happen :) )
		} finally {
		    variants[i].unlock();
		}
	    }
	}
	reply.setStream (g) ;
	reply.setHeaderValue(reply.H_VARY, VARY);
	throw new HTTPException (reply) ;
    }


    public void registerResource(FramedResource resource) {
	super.registerOtherResource(resource);
    }


    /**
     * Perform an HTTP request.
     * Negotiate among the variants, the best variant according to the request
     * fields, and make this elected variant serve the request.
     * @param request The request to handle.
     * @exception ProtocolException If negotiating among the resource variants 
     * failed.
     * @exception ResourceException If the resource got a fatal error.
     */
    public ReplyInterface perform(RequestInterface req)
	throws ProtocolException, ResourceException
    {
	ReplyInterface repi = performFrames(req);
	if (repi != null)
	    return repi;

	if (! checkRequest(req))
	    return null;

	Request           request = (Request) req;
	ResourceReference selected;
	
	// Run content negotiation now:
	// The PUT is special, we negotiate with ETag (if present) or
	// using the metainformation about the PUT entity.
	String method = request.getMethod ();
	if (method.equals("PUT")) 
	    selected = negotiatePut(request);
	else 
	    selected = negotiate(request);

	// This should never happen: either the negotiation succeed, or the
	// negotiate method should return an error.
	if ( selected == null ) {
	    Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ;
	    error.setContent("Error negotiating among resource's variants.");
	    throw new HTTPException(error) ;
	}
	
	// FIXME content neg should be done at lookup time
	// FIXME enhencing the reply should be done at outgoingfilter
	// Get the original variant reply, and add its location as a header:
	try {
	    FramedResource resource = (FramedResource) selected.lock();
	    Reply reply = (Reply)resource.perform(request) ;
	    reply.setHeaderValue(reply.H_VARY, VARY);
	    HTTPFrame itsframe = 
		(HTTPFrame) resource.getFrame(httpFrameClass);
	    if (itsframe != null) {
		reply.setContentLocation(
				  itsframe.getURL(request).toExternalForm()) ;
		return reply;
	    }
	    Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ;
	    error.setContent("Error negotiating : "+
			     "selected resource has no HTTPFrame");
	    throw new HTTPException(error) ;
	} catch (InvalidResourceException ex) {
	    Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR) ;
	    error.setContent("Error negotiating : Invalid selected resource");
	    throw new HTTPException(error) ;
	} finally {
	    selected.unlock();
	}
    }
}

Received on Tuesday, 20 April 1999 19:37:51 UTC