- 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