- From: Roland Mainz <Roland.Mainz@informatik.med.uni-giessen.de>
- Date: Wed, 21 Apr 1999 01:38:32 +0200
- To: W3 Jigsaw Mailinglist <www-jigsaw@w3.org>, "jigsaw@w3.org" <jigsaw@w3.org>
- Message-Id: <371D0FF7.69AE16EF@informatik.med.uni-giessen.de>
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