// NegotiatedFrame.java
// $Id: NegotiatedFrame.java,v 1.44 2007/03/10 12:28:23 ylafon Exp $
// (c) COPYRIGHT MIT and INRIA, 1996.
// Please first read the full copyright statement in file COPYRIGHT.html
package org.w3c.jigsaw.frames;
import java.io.PrintStream;
import java.util.Vector;
import org.w3c.tools.resources.Attribute;
import org.w3c.tools.resources.AttributeHolder;
import org.w3c.tools.resources.AttributeRegistry;
import org.w3c.tools.resources.BooleanAttribute;
import org.w3c.tools.resources.DirectoryResource;
import org.w3c.tools.resources.FramedResource;
import org.w3c.tools.resources.InvalidResourceException;
import org.w3c.tools.resources.LookupState;
import org.w3c.tools.resources.LookupResult;
import org.w3c.tools.resources.MultipleLockException;
import org.w3c.tools.resources.ProtocolException;
import org.w3c.tools.resources.ReplyInterface;
import org.w3c.tools.resources.RequestInterface;
import org.w3c.tools.resources.Resource;
import org.w3c.tools.resources.ResourceException;
import org.w3c.tools.resources.ResourceFrame;
import org.w3c.tools.resources.ResourceReference;
import org.w3c.tools.resources.ServerInterface;
import org.w3c.tools.resources.StringArrayAttribute;
import org.w3c.jigsaw.http.HTTPException;
import org.w3c.jigsaw.http.Reply;
import org.w3c.jigsaw.http.Request;
import org.w3c.jigsaw.html.HtmlGenerator;
import org.w3c.jigsaw.html.HtmlGenerator ;
import org.w3c.www.mime.LanguageTag;
import org.w3c.www.mime.MimeType;
import org.w3c.www.http.HTTP;
import org.w3c.www.http.HttpAccept;
import org.w3c.www.http.HttpAcceptCharset;
import org.w3c.www.http.HttpAcceptEncoding;
import org.w3c.www.http.HttpAcceptLanguage;
import org.w3c.www.http.HttpEntityMessage;
import org.w3c.www.http.HttpEntityTag;
import org.w3c.www.http.HttpFactory;
import org.w3c.www.http.HttpInvalidValueException;
import org.w3c.www.http.HttpMessage;
import org.w3c.www.http.HttpReplyMessage;
import org.w3c.www.http.HttpRequestMessage;
import org.w3c.www.http.HttpTokenList;
import org.w3c.tools.resources.ProtocolException;
import org.w3c.tools.resources.ResourceException;
/**
* Content negotiation.
*/
public class NegotiatedFrame extends HTTPFrame {
class VariantState {
ResourceReference variant = null ;
double qs = 0.0 ; // configured frame quality
double qe = 0.0 ; // content encoding quality
double qc = 0.0 ; // content charset quality
double ql = 0.0 ; // content language quality
double q = 0.0 ; // quality (mime type one)
double Q = 0.0 ; // the big Q
public String toString() {
try {
Resource res = variant.unsafeLock();
String name = (String) res.getIdentifier() ;
if ( name == null )
name = "<noname>" ;
return "[" + name
+ " qc=" + qc
+ " qs=" + qs
+ " qe=" + qe
+ " ql=" + ql
+ " q =" + q
+ " Q =" + getQ()
+"]" ;
} catch (InvalidResourceException ex) {
return "invalid";
} finally {
variant.unlock();
}
}
void setCharsetQuality (double qc) {
this.qc = qc ;
}
double getCharsetQuality () {
return qc;
}
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 * qc;
}
VariantState (ResourceReference variant, double qs) {
this.qs = qs ;
this.variant = variant ;
}
}
private static Class httpFrameClass = null;
private static Class negotiatedFrameClass = null;
static {
try {
httpFrameClass = Class.forName("org.w3c.jigsaw.frames.HTTPFrame") ;
} catch (Exception ex) {
throw new RuntimeException("No HTTPFrame class found.");
}
try {
negotiatedFrameClass =
Class.forName("org.w3c.jigsaw.frames.NegotiatedFrame") ;
} catch (Exception ex) {
throw new RuntimeException("No NegotiatedFrame 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";
/**
* our state
*/
protected static String STATE_NEG = "org.w3c.jigsaw.frames.Negotiated";
/**
* 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
*/
protected HttpTokenList vary = null;
private boolean vary_done = false;
/**
* 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;
/**
* Attribute index - Should the variants to be strictly checked?
* the variants should NEVER contain negotiated resources!
*/
protected static int ATTR_PARANOID_VARIANT_CHECK = -1;
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) ;
a = new BooleanAttribute("strict_put"
, new Boolean(true)
, Attribute.EDITABLE);
ATTR_PUT_POLICY = AttributeRegistry.registerAttribute(cls, a) ;
a = new BooleanAttribute("paranoid_variant_check"
, new Boolean(false)
, Attribute.EDITABLE);
ATTR_PARANOID_VARIANT_CHECK =
AttributeRegistry.registerAttribute(cls, a) ;
}
private boolean b_charset = true;
private boolean b_type = true;
private boolean b_language = true;
private boolean b_encoding = true;
public synchronized void setValue(int idx, Object value) {
if ( idx == ATTR_VARIANTS ){
super.setValue(idx,checkVariants((String[])value));
} else {
super.setValue(idx,value);
}
}
private HttpTokenList getVary() {
if (vary_done) {
return vary;
}
// ok let's do it!
vary_done = true;
b_charset = false;
b_type = false;
b_language = false;
b_encoding = false;
ResourceReference variants[] = null;
try {
variants = getVariantResources() ;
} catch (ProtocolException ex) {};
// no vary for so few variants!
if ((variants == null) || (variants.length < 2)) {
return null;
}
synchronized (this) {
int num = 0;
HTTPFrame refframe = null;
FramedResource resource = null;
do {
try {
resource = (FramedResource)variants[num].unsafeLock();
refframe = (HTTPFrame) resource.getFrame(httpFrameClass);
} catch (InvalidResourceException ex) {
//ex.printStackTrace();
//FIXME
} catch (Exception fex) {
fex.printStackTrace();
} finally {
variants[num].unlock();
}
num++;
} while ((refframe == null) && (num < variants.length));
// not enough variants, abort
if (variants.length - num < 1)
return null;
String language = refframe.getContentLanguage();
String encoding = refframe.getContentEncoding();
String charset = refframe.getCharset();
MimeType mtype = refframe.getContentType();
HTTPFrame itsframe = null;
for (int i = num; i < variants.length; i++) {
try {
resource = (FramedResource)variants[i].unsafeLock();
itsframe = (HTTPFrame)
resource.unsafeGetFrame(httpFrameClass);
if (language != null) {
if (!language.equals(itsframe.getContentLanguage())) {
b_language = true;
}
} else {
if (itsframe.getContentLanguage() != null) {
b_language = true;
}
}
if (encoding != null) {
if (!encoding.equals(itsframe.getContentEncoding())) {
b_encoding = true;
}
} else {
if (itsframe.getContentEncoding() != null) {
b_encoding = true;
}
}
if (charset != null) {
if (!charset.equals(itsframe.getCharset())) {
b_charset = true;
}
} else {
if (itsframe.getCharset() != null) {
b_charset = true;
}
}
if (mtype != null) {
MimeType o_type = itsframe.getContentType();
if ((o_type != null) && (mtype.match(o_type) !=
MimeType.MATCH_SPECIFIC_SUBTYPE)) {
b_type = true;
}
} else {
if (itsframe.getContentType() != null) {
b_type = true;
}
}
} catch (InvalidResourceException ex) {
//ex.printStackTrace();
//FIXME
} finally {
variants[i].unlock();
}
}
int vary_size = 0;
if (b_language) {
vary_size++;
}
if (b_charset) {
vary_size++;
}
if (b_encoding) {
++vary_size;
}
if (b_type) {
++vary_size;
}
if (vary_size == 0) {
return null;
}
String[] s_vary = new String[vary_size];
num = 0;
if (b_type) {
s_vary[num++] = "Accept";
}
if (b_encoding) {
s_vary[num++] = "Accept-Encoding";
}
if (b_language) {
s_vary[num++] = "Accept-Language";
}
if (b_charset) {
s_vary[num] = "Accept-Charset";
}
vary = HttpFactory.makeStringList(s_vary);
}
return vary;
}
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) ;
}
protected String[] checkVariants(String variants[]) {
Vector checked = new Vector(variants.length) ;
ResourceReference tmpres = null;
ResourceReference r_parent = resource.getParent() ;
try {
DirectoryResource parent= (DirectoryResource)r_parent.unsafeLock();
for (int i = 0 ; i < variants.length ; i++) {
tmpres = parent.lookup(variants[i]) ;
if (tmpres != null) {
try {
FramedResource resource =
(FramedResource)tmpres.unsafeLock() ;
NegotiatedFrame itsframe = (NegotiatedFrame)
resource.getFrame(negotiatedFrameClass);
if (itsframe == null) {
checked.addElement (variants[i]);
}
} catch (InvalidResourceException ex) {
} finally {
tmpres.unlock();
}
} else {
checked.addElement (variants[i]);
}
}
} catch (InvalidResourceException ex) {
// throw new HTTPException("invalid parent for negotiation");
} finally {
r_parent.unlock();
}
String[] variants_ok = new String[checked.size()];
checked.copyInto(variants_ok);
return variants_ok ;
}
public void setVariants(String variants[]) {
setValue(ATTR_VARIANTS, variants);
// invalidate the Vary header
vary_done = false;
}
/**
* 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();
}
public void setPutPolicy(Boolean strict) {
setValue(ATTR_PUT_POLICY, strict);
}
public void setPutPolicy(boolean strict) {
setValue(ATTR_PUT_POLICY, new Boolean(strict));
}
/**
* get the variant checking policy
*/
public boolean getParanoidVariantCheck() {
Boolean val = (Boolean) getValue(ATTR_PARANOID_VARIANT_CHECK,
Boolean.FALSE);
return val.booleanValue();
}
public void setParanoidVariantCheck(boolean strict) {
setValue(ATTR_PARANOID_VARIANT_CHECK, new Boolean(strict));
}
/**
* 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 ;
int oldlength = names.length ;
if (getParanoidVariantCheck()) {
names = checkVariants(names) ;
}
// Look them up in our parent directory:
ResourceReference variants[] = new ResourceReference[names.length] ;
ResourceReference r_parent = resource.getParent() ;
try {
DirectoryResource parent= (DirectoryResource)r_parent.unsafeLock();
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();
} else if (oldlength > names.length){
setVariants(names);
}
} 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() || !b_encoding ) {
// 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.unsafeLock() ;
HTTPFrame itsframe =
(HTTPFrame) resource.unsafeGetFrame(httpFrameClass);
if (itsframe != null) {
String ve;
ve = (String) itsframe.unsafeGetValue(
ATTR_CONTENT_ENCODING, null);
if ( ve == null ) {
ve = "identity"; // default encoding
state.setContentEncodingQuality (1.0) ;
} else {
state.setContentEncodingQuality (0.01) ;
}
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].getQuality() - jidx * 0.001) ;
}
} catch (InvalidResourceException ex) {
} finally {
rr.unlock();
}
}
// FIXME We should check here against unlegible variants as now
}
return false ;
}
/**
* Negotiate on charsets.
* The algorithm is described in RFC2616 (Obsoletes RFC2068)
* @param states The current states of negotiation.
* @param request The request to handle.
*/
protected boolean negotiateCharsetQuality (Vector states
, Request request) {
if ( !request.hasAcceptCharset() || !b_charset ) {
// All variants get a quality of 1.0
for (int i = 0 ; i < states.size() ; i++) {
VariantState state = (VariantState) states.elementAt(i) ;
state.setCharsetQuality (1.0) ;
}
} else {
// The browser has given some preferences:
HttpAcceptCharset charsets[] = request.getAcceptCharset() ;
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.unsafeLock() ;
HTTPFrame itsframe =
(HTTPFrame) resource.unsafeGetFrame(httpFrameClass);
if (itsframe != null) {
MimeType vt;
vt = (MimeType) itsframe.unsafeGetValue(
ATTR_CONTENT_TYPE, null);
String fcharset = vt.getParameterValue("charset");
// if not defined in the frame, it must be the default
if (fcharset == null) {
fcharset = "ISO-8859-1";
}
double qual = 0.0 ;
boolean default_done = false;
String charset;
for (int j = 0 ; j < charsets.length ; j++) {
charset = charsets[j].getCharset();
if (charset.equals("*")) {
default_done = true;
if (qual == 0) {
qual = charsets[j].getQuality()-0.001*j;
}
} else {
if ( charset.equals("ISO-8859-1"))
default_done = true;
if ( charset.equals(fcharset))
if (charsets[j].getQuality() > qual) {
qual =charsets[j].getQuality()-0.001*j;
}
}
}
if (!default_done && fcharset.equals("ISO-8859-1"))
qual = 1.0 - 0.001*charsets.length;
state.setCharsetQuality(qual) ;
}
} catch (InvalidResourceException ex) {
//FIXME
} finally {
rr.unlock();
}
}
}
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() || !b_language ) {
for (int i = 0 ; i < states.size() ; i++) {
VariantState state = (VariantState) states.elementAt(i) ;
state.setLanguageQuality (1.0) ;
}
} else {
HttpAcceptLanguage languages[] = request.getAcceptLanguage() ;
LanguageTag req_lang[] = new LanguageTag[languages.length];
for (int i = 0 ; i < languages.length ; i++) {
req_lang[i] = new LanguageTag(languages[i].getLanguage());
}
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.unsafeLock() ;
HTTPFrame itsframe =
(HTTPFrame) resource.getFrame(httpFrameClass);
if (itsframe != null) {
String lang;
lang = (String) itsframe.unsafeGetValue(
ATTR_CONTENT_LANGUAGE, null);
if ( lang == null ) {
state.setLanguageQuality (-1.0) ;
} else {
varyLang = true ;
LanguageTag ftag = new LanguageTag(lang);
int jmatch = -1 ;
int jidx = -1 ;
for (int j = 0 ; j < languages.length ; j++) {
int match = ftag.match(req_lang[j]);
if ( match > jmatch ) {
jmatch = match ;
jidx = j ;
}
}
if ( jidx < 0 )
state.setLanguageQuality(0.01) ;
else {
// little hack for first
state.setLanguageQuality (
languages[jidx].getQuality()-jidx*0.001) ;
}
}
}
} 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 ;
}
/**
* 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() || !b_type ) {
// 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:
HttpAccept accepts[] = request.getAccept() ;
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.unsafeLock() ;
HTTPFrame itsframe =
(HTTPFrame) resource.unsafeGetFrame(httpFrameClass);
if (itsframe != null) {
MimeType vt;
vt = (MimeType) itsframe.unsafeGetValue(
ATTR_CONTENT_TYPE, null);
int jmatch = -1 ;
int jidx = -1 ;
for (int j = 0 ; j < accepts.length ; j++) {
try {
int match = vt.match(accepts[j].getMimeType());
if ( match > jmatch ) {
jmatch = match ;
jidx = j ;
}
} catch (HttpInvalidValueException ivex) {
// There is a bad acept header here
// let's be cool and ignore it
// FIXME we should answer with a Bad Request
}
}
if ( jidx < 0 )
state.setQuality (0.0) ;
else
state.setQuality(accepts[jidx].getQuality()
-jidx*0.001) ;
}
} 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].unsafeLock() ;
HTTPFrame itsframe =
(HTTPFrame) resource.unsafeGetFrame(httpFrameClass);
if (itsframe != null) {
if ( itsframe.unsafeDefinesAttribute (ATTR_QUALITY) )
qs = itsframe.unsafeGetQuality() ;
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 {
// 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()) ;
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.unsafeLock().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, getVary());
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].unsafeLock() ;
itsframe = (HTTPFrame)resource.getFrame(httpFrameClass);
if (itsframe != null) {
frametag = itsframe.getETag();
if (frametag == null) {
continue;
}
// Do we have a winner?
try {
for (int j=0; j<etags.length; j++)
if(frametag.getTag().equals(etags[j].getTag()))
return variants[i];
} catch (NullPointerException ex) {
// if the list of etag contains a null
// it should never happen and the try doesn't cost
}
}
} 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.unsafeLock() ;
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()) ;
g.append ("<ul>") ;
for (int i = 0 ; i < variants.length ; i++) {
if (variants[i] != null) {
try {
name = variants[i].unsafeLock().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, getVary());
throw new HTTPException (reply) ;
}
public void registerResource(FramedResource resource) {
super.registerOtherResource(resource);
}
/**
* Lookup the target resource.
* @param ls The current lookup state
* @param lr The result
* @return true if lookup is done.
* @exception ProtocolException If an error relative to the protocol occurs
*/
public boolean lookup(LookupState ls, LookupResult lr)
throws ProtocolException
{
if ( ls.isDirectory() ) { // we are not a directory, bail out
Request req = (Request)ls.getRequest();
String locstate = (String)req.getState(STATE_CONTENT_LOCATION);
if (locstate == null) {
lr.setTarget(null);
return true;
}
}
ResourceFrame frames[] = getFrames();
if (frames != null) {
for (int i = 0 ; i < frames.length ; i++) {
if (frames[i] == null) {
continue;
}
if (frames[i].lookup(ls,lr)) {
return true;
}
}
}
if ( ls.hasMoreComponents() ) {
// We are not a container resource, and we don't have children:
lr.setTarget(null);
return false;
} else {
//we are done! try to find the negotiated one...
RequestInterface reqi = ls.getRequest();
ResourceReference selected;
Request request = (Request) reqi;
// 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 ();
try {
if (method.equals("PUT")) {
selected = negotiatePut(request);
} else {
selected = negotiate(request);
}
} catch (ResourceException ex) {
// the failure will be processed in perform
return false;
}
if (selected != null) {
try {
FramedResource resource =
(FramedResource) selected.unsafeLock();
resource.lookup(ls, lr);
} catch (InvalidResourceException ex) {
// the failure will be processed in perform
} finally {
selected.unlock();
}
}
request.setState(STATE_NEG, selected);
// fake now, we handle the process to have a two-level processing
// just to add the Vary: header
lr.setTarget(getResourceReference());
return true;
}
}
/**
* 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;
// get the right resources
if (request.hasState(STATE_NEG)) {
selected = (ResourceReference) request.getState(STATE_NEG);
} else {
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) ;
}
try {
FramedResource resource = (FramedResource) selected.unsafeLock();
Reply reply = (Reply)resource.perform(request) ;
reply.setHeaderValue(reply.H_VARY, getVary());
HTTPFrame itsframe =
(HTTPFrame) resource.unsafeGetFrame(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();
}
}
}