0001: // NegotiatedFrame.java
0002: // $Id: NegotiatedFrame.java,v 1.44 2007/03/10 12:28:23 ylafon Exp $
0003: // (c) COPYRIGHT MIT and INRIA, 1996.
0004: // Please first read the full copyright statement in file COPYRIGHT.html
0005:
0006: package org.w3c.jigsaw.frames;
0007:
0008: import java.io.PrintStream;
0009:
0010: import java.util.Vector;
0011:
0012: import org.w3c.tools.resources.Attribute;
0013: import org.w3c.tools.resources.AttributeHolder;
0014: import org.w3c.tools.resources.AttributeRegistry;
0015: import org.w3c.tools.resources.BooleanAttribute;
0016: import org.w3c.tools.resources.DirectoryResource;
0017: import org.w3c.tools.resources.FramedResource;
0018: import org.w3c.tools.resources.InvalidResourceException;
0019: import org.w3c.tools.resources.LookupState;
0020: import org.w3c.tools.resources.LookupResult;
0021: import org.w3c.tools.resources.MultipleLockException;
0022: import org.w3c.tools.resources.ProtocolException;
0023: import org.w3c.tools.resources.ReplyInterface;
0024: import org.w3c.tools.resources.RequestInterface;
0025: import org.w3c.tools.resources.Resource;
0026: import org.w3c.tools.resources.ResourceException;
0027: import org.w3c.tools.resources.ResourceFrame;
0028: import org.w3c.tools.resources.ResourceReference;
0029: import org.w3c.tools.resources.ServerInterface;
0030: import org.w3c.tools.resources.StringArrayAttribute;
0031:
0032: import org.w3c.jigsaw.http.HTTPException;
0033: import org.w3c.jigsaw.http.Reply;
0034: import org.w3c.jigsaw.http.Request;
0035:
0036: import org.w3c.jigsaw.html.HtmlGenerator;
0037:
0038: import org.w3c.jigsaw.html.HtmlGenerator;
0039:
0040: import org.w3c.www.mime.LanguageTag;
0041: import org.w3c.www.mime.MimeType;
0042:
0043: import org.w3c.www.http.HTTP;
0044: import org.w3c.www.http.HttpAccept;
0045: import org.w3c.www.http.HttpAcceptCharset;
0046: import org.w3c.www.http.HttpAcceptEncoding;
0047: import org.w3c.www.http.HttpAcceptLanguage;
0048: import org.w3c.www.http.HttpEntityMessage;
0049: import org.w3c.www.http.HttpEntityTag;
0050: import org.w3c.www.http.HttpFactory;
0051: import org.w3c.www.http.HttpInvalidValueException;
0052: import org.w3c.www.http.HttpMessage;
0053: import org.w3c.www.http.HttpReplyMessage;
0054: import org.w3c.www.http.HttpRequestMessage;
0055: import org.w3c.www.http.HttpTokenList;
0056:
0057: import org.w3c.tools.resources.ProtocolException;
0058: import org.w3c.tools.resources.ResourceException;
0059:
0060: /**
0061: * Content negotiation.
0062: */
0063: public class NegotiatedFrame extends HTTPFrame {
0064:
0065: class VariantState {
0066: ResourceReference variant = null;
0067: double qs = 0.0; // configured frame quality
0068: double qe = 0.0; // content encoding quality
0069: double qc = 0.0; // content charset quality
0070: double ql = 0.0; // content language quality
0071: double q = 0.0; // quality (mime type one)
0072: double Q = 0.0; // the big Q
0073:
0074: public String toString() {
0075: try {
0076: Resource res = variant.unsafeLock();
0077: String name = (String) res.getIdentifier();
0078: if (name == null)
0079: name = "<noname>";
0080: return "[" + name + " qc=" + qc + " qs=" + qs + " qe="
0081: + qe + " ql=" + ql + " q =" + q + " Q ="
0082: + getQ() + "]";
0083: } catch (InvalidResourceException ex) {
0084: return "invalid";
0085: } finally {
0086: variant.unlock();
0087: }
0088: }
0089:
0090: void setCharsetQuality(double qc) {
0091: this .qc = qc;
0092: }
0093:
0094: double getCharsetQuality() {
0095: return qc;
0096: }
0097:
0098: void setContentEncodingQuality(double qe) {
0099: this .qe = qe;
0100: }
0101:
0102: void setContentEncodingQuality(HttpAcceptEncoding e) {
0103: this .qe = e.getQuality();
0104: }
0105:
0106: double getContentEncodingQuality() {
0107: return qe;
0108: }
0109:
0110: void setQuality(double q) {
0111: this .q = q;
0112: }
0113:
0114: void setQuality(HttpAccept a) {
0115: q = a.getQuality();
0116: }
0117:
0118: void setLanguageQuality(double ql) {
0119: this .ql = ql;
0120: }
0121:
0122: void setLanguageQuality(HttpAcceptLanguage l) {
0123: this .ql = l.getQuality();
0124: }
0125:
0126: double getLanguageQuality() {
0127: return ql;
0128: }
0129:
0130: ResourceReference getResource() {
0131: return variant;
0132: }
0133:
0134: double getQ() {
0135: return qe * q * qs * ql * qc;
0136: }
0137:
0138: VariantState(ResourceReference variant, double qs) {
0139: this .qs = qs;
0140: this .variant = variant;
0141: }
0142: }
0143:
0144: private static Class httpFrameClass = null;
0145: private static Class negotiatedFrameClass = null;
0146:
0147: static {
0148: try {
0149: httpFrameClass = Class
0150: .forName("org.w3c.jigsaw.frames.HTTPFrame");
0151: } catch (Exception ex) {
0152: throw new RuntimeException("No HTTPFrame class found.");
0153: }
0154: try {
0155: negotiatedFrameClass = Class
0156: .forName("org.w3c.jigsaw.frames.NegotiatedFrame");
0157: } catch (Exception ex) {
0158: throw new RuntimeException(
0159: "No NegotiatedFrame class found.");
0160: }
0161: }
0162:
0163: /**
0164: * Our Icon property.
0165: */
0166: public static String NEGOTIATED_ICON_P = "org.w3c.jigsaw.frames.negotiated.icon";
0167: /**
0168: * Our default Icon
0169: */
0170: public static String DEFAULT_NEGOTIATED_ICON = "generic.gif";
0171:
0172: /**
0173: * our state
0174: */
0175: protected static String STATE_NEG = "org.w3c.jigsaw.frames.Negotiated";
0176:
0177: /**
0178: * Turn debugging on/off.
0179: */
0180: private static final boolean debug = false;
0181: /**
0182: * Minimum quality for a resource to be considered further.
0183: */
0184: private static final double REQUIRED_QUALITY = 0.0001;
0185: /**
0186: * The Vary header field for this resource
0187: */
0188: protected HttpTokenList vary = null;
0189: private boolean vary_done = false;
0190: /**
0191: * Attribute index - The set of names of variants.
0192: */
0193: protected static int ATTR_VARIANTS = -1;
0194: /**
0195: * Attribute index - Should the PUT needs to be strictly checked?
0196: */
0197: protected static int ATTR_PUT_POLICY = -1;
0198: /**
0199: * Attribute index - Should the variants to be strictly checked?
0200: * the variants should NEVER contain negotiated resources!
0201: */
0202: protected static int ATTR_PARANOID_VARIANT_CHECK = -1;
0203:
0204: static {
0205: Attribute a = null;
0206: Class cls = null;
0207: try {
0208: cls = Class
0209: .forName("org.w3c.jigsaw.frames.NegotiatedFrame");
0210: } catch (Exception ex) {
0211: ex.printStackTrace();
0212: System.exit(1);
0213: }
0214: // The names of the varint we negotiate
0215: a = new StringArrayAttribute("variants", null,
0216: Attribute.EDITABLE);
0217: ATTR_VARIANTS = AttributeRegistry.registerAttribute(cls, a);
0218: a = new BooleanAttribute("strict_put", new Boolean(true),
0219: Attribute.EDITABLE);
0220: ATTR_PUT_POLICY = AttributeRegistry.registerAttribute(cls, a);
0221: a = new BooleanAttribute("paranoid_variant_check", new Boolean(
0222: false), Attribute.EDITABLE);
0223: ATTR_PARANOID_VARIANT_CHECK = AttributeRegistry
0224: .registerAttribute(cls, a);
0225: }
0226:
0227: private boolean b_charset = true;
0228: private boolean b_type = true;
0229: private boolean b_language = true;
0230: private boolean b_encoding = true;
0231:
0232: public synchronized void setValue(int idx, Object value) {
0233: if (idx == ATTR_VARIANTS) {
0234: super .setValue(idx, checkVariants((String[]) value));
0235: } else {
0236: super .setValue(idx, value);
0237: }
0238: }
0239:
0240: private HttpTokenList getVary() {
0241: if (vary_done) {
0242: return vary;
0243: }
0244: // ok let's do it!
0245: vary_done = true;
0246: b_charset = false;
0247: b_type = false;
0248: b_language = false;
0249: b_encoding = false;
0250:
0251: ResourceReference variants[] = null;
0252: try {
0253: variants = getVariantResources();
0254: } catch (ProtocolException ex) {
0255: }
0256: ;
0257: // no vary for so few variants!
0258:
0259: if ((variants == null) || (variants.length < 2)) {
0260: return null;
0261: }
0262: synchronized (this ) {
0263: int num = 0;
0264: HTTPFrame refframe = null;
0265: FramedResource resource = null;
0266: do {
0267: try {
0268: resource = (FramedResource) variants[num]
0269: .unsafeLock();
0270: refframe = (HTTPFrame) resource
0271: .getFrame(httpFrameClass);
0272: } catch (InvalidResourceException ex) {
0273: //ex.printStackTrace();
0274: //FIXME
0275: } catch (Exception fex) {
0276: fex.printStackTrace();
0277: } finally {
0278: variants[num].unlock();
0279: }
0280: num++;
0281: } while ((refframe == null) && (num < variants.length));
0282: // not enough variants, abort
0283: if (variants.length - num < 1)
0284: return null;
0285:
0286: String language = refframe.getContentLanguage();
0287: String encoding = refframe.getContentEncoding();
0288: String charset = refframe.getCharset();
0289: MimeType mtype = refframe.getContentType();
0290:
0291: HTTPFrame itsframe = null;
0292: for (int i = num; i < variants.length; i++) {
0293: try {
0294: resource = (FramedResource) variants[i]
0295: .unsafeLock();
0296: itsframe = (HTTPFrame) resource
0297: .unsafeGetFrame(httpFrameClass);
0298: if (language != null) {
0299: if (!language.equals(itsframe
0300: .getContentLanguage())) {
0301: b_language = true;
0302: }
0303: } else {
0304: if (itsframe.getContentLanguage() != null) {
0305: b_language = true;
0306: }
0307: }
0308: if (encoding != null) {
0309: if (!encoding.equals(itsframe
0310: .getContentEncoding())) {
0311: b_encoding = true;
0312: }
0313: } else {
0314: if (itsframe.getContentEncoding() != null) {
0315: b_encoding = true;
0316: }
0317: }
0318: if (charset != null) {
0319: if (!charset.equals(itsframe.getCharset())) {
0320: b_charset = true;
0321: }
0322: } else {
0323: if (itsframe.getCharset() != null) {
0324: b_charset = true;
0325: }
0326: }
0327: if (mtype != null) {
0328: MimeType o_type = itsframe.getContentType();
0329: if ((o_type != null)
0330: && (mtype.match(o_type) != MimeType.MATCH_SPECIFIC_SUBTYPE)) {
0331: b_type = true;
0332: }
0333: } else {
0334: if (itsframe.getContentType() != null) {
0335: b_type = true;
0336: }
0337: }
0338: } catch (InvalidResourceException ex) {
0339: //ex.printStackTrace();
0340: //FIXME
0341: } finally {
0342: variants[i].unlock();
0343: }
0344: }
0345: int vary_size = 0;
0346: if (b_language) {
0347: vary_size++;
0348: }
0349: if (b_charset) {
0350: vary_size++;
0351: }
0352: if (b_encoding) {
0353: ++vary_size;
0354: }
0355: if (b_type) {
0356: ++vary_size;
0357: }
0358:
0359: if (vary_size == 0) {
0360: return null;
0361: }
0362: String[] s_vary = new String[vary_size];
0363: num = 0;
0364: if (b_type) {
0365: s_vary[num++] = "Accept";
0366: }
0367: if (b_encoding) {
0368: s_vary[num++] = "Accept-Encoding";
0369: }
0370: if (b_language) {
0371: s_vary[num++] = "Accept-Language";
0372: }
0373: if (b_charset) {
0374: s_vary[num] = "Accept-Charset";
0375: }
0376: vary = HttpFactory.makeStringList(s_vary);
0377: }
0378: return vary;
0379: }
0380:
0381: public String getIcon() {
0382: String icon = super .getIcon();
0383: if (icon == null) {
0384: icon = getServer().getProperties().getString(
0385: NEGOTIATED_ICON_P, DEFAULT_NEGOTIATED_ICON);
0386: setValue(ATTR_ICON, icon);
0387: }
0388: return icon;
0389: }
0390:
0391: /**
0392: * Get the variant names.
0393: */
0394: public String[] getVariantNames() {
0395: return (String[]) getValue(ATTR_VARIANTS, null);
0396: }
0397:
0398: protected String[] checkVariants(String variants[]) {
0399:
0400: Vector checked = new Vector(variants.length);
0401: ResourceReference tmpres = null;
0402: ResourceReference r_parent = resource.getParent();
0403: try {
0404: DirectoryResource parent = (DirectoryResource) r_parent
0405: .unsafeLock();
0406: for (int i = 0; i < variants.length; i++) {
0407: tmpres = parent.lookup(variants[i]);
0408: if (tmpres != null) {
0409: try {
0410: FramedResource resource = (FramedResource) tmpres
0411: .unsafeLock();
0412: NegotiatedFrame itsframe = (NegotiatedFrame) resource
0413: .getFrame(negotiatedFrameClass);
0414: if (itsframe == null) {
0415: checked.addElement(variants[i]);
0416: }
0417: } catch (InvalidResourceException ex) {
0418: } finally {
0419: tmpres.unlock();
0420: }
0421: } else {
0422: checked.addElement(variants[i]);
0423: }
0424: }
0425: } catch (InvalidResourceException ex) {
0426: // throw new HTTPException("invalid parent for negotiation");
0427: } finally {
0428: r_parent.unlock();
0429: }
0430:
0431: String[] variants_ok = new String[checked.size()];
0432: checked.copyInto(variants_ok);
0433:
0434: return variants_ok;
0435: }
0436:
0437: public void setVariants(String variants[]) {
0438: setValue(ATTR_VARIANTS, variants);
0439:
0440: // invalidate the Vary header
0441: vary_done = false;
0442: }
0443:
0444: /**
0445: * get the "strictness" of the PUT checking
0446: */
0447: public boolean getPutPolicy() {
0448: Boolean val = (Boolean) getValue(ATTR_PUT_POLICY, null);
0449: if (val == null) // strict by default
0450: return true;
0451: return val.booleanValue();
0452: }
0453:
0454: public void setPutPolicy(Boolean strict) {
0455: setValue(ATTR_PUT_POLICY, strict);
0456: }
0457:
0458: public void setPutPolicy(boolean strict) {
0459: setValue(ATTR_PUT_POLICY, new Boolean(strict));
0460: }
0461:
0462: /**
0463: * get the variant checking policy
0464: */
0465: public boolean getParanoidVariantCheck() {
0466: Boolean val = (Boolean) getValue(ATTR_PARANOID_VARIANT_CHECK,
0467: Boolean.FALSE);
0468: return val.booleanValue();
0469: }
0470:
0471: public void setParanoidVariantCheck(boolean strict) {
0472: setValue(ATTR_PARANOID_VARIANT_CHECK, new Boolean(strict));
0473: }
0474:
0475: /**
0476: * Get the variant resources.
0477: * This is somehow broken, it shouldn't allocate the array of variants
0478: * on each call. However, don't forget that the list of variants can be
0479: * dynamically edited, this means that if we are to keep a cache of it
0480: * (as would be the case if we kept the array of variants as instance var)
0481: * we should also take care of editing of attributes (possible, but I
0482: * just don't have enough lifes).
0483: * @return An array of ResourceReference, or <strong>null</strong>.
0484: * @exception ProtocolException If one of the variants doesn't exist.
0485: */
0486:
0487: public ResourceReference[] getVariantResources()
0488: throws ProtocolException {
0489:
0490: // Get the variant names:
0491: String names[] = getVariantNames();
0492: if (names == null)
0493: return null;
0494:
0495: int oldlength = names.length;
0496:
0497: if (getParanoidVariantCheck()) {
0498: names = checkVariants(names);
0499: }
0500:
0501: // Look them up in our parent directory:
0502: ResourceReference variants[] = new ResourceReference[names.length];
0503: ResourceReference r_parent = resource.getParent();
0504: try {
0505: DirectoryResource parent = (DirectoryResource) r_parent
0506: .unsafeLock();
0507: int missing = 0;
0508: for (int i = 0; i < names.length; i++) {
0509: variants[i] = parent.lookup(names[i]);
0510: if (variants[i] == null)
0511: missing++;
0512: }
0513: if (missing > 0) {
0514: int kept = names.length - missing;
0515: if (kept < 1)
0516: return null;
0517: String newNames[] = new String[kept];
0518: int j = 0;
0519: int i = 0;
0520: while (i < variants.length) {
0521: if (variants[i] != null) {
0522: newNames[j++] = names[i++];
0523: } else {
0524: i++;
0525: }
0526: }
0527: setVariants(newNames);
0528: //recompute Variant Resources
0529: return getVariantResources();
0530: } else if (oldlength > names.length) {
0531: setVariants(names);
0532: }
0533:
0534: } catch (InvalidResourceException ex) {
0535: throw new HTTPException("invalid parent for negotiation");
0536: } finally {
0537: r_parent.unlock();
0538: }
0539: return variants;
0540: }
0541:
0542: /**
0543: * Print the current negotiation state.
0544: * @param header The header to print first.
0545: * @param states The current negotiation states.
0546: */
0547:
0548: protected void printNegotiationState(String header, Vector states) {
0549: if (debug) {
0550: System.out.println("------" + header);
0551: for (int i = 0; i < states.size(); i++) {
0552: VariantState state = (VariantState) states.elementAt(i);
0553: System.out.println(state);
0554: }
0555: System.out.println("-----");
0556: }
0557: }
0558:
0559: /**
0560: * Negotiate among content encodings.
0561: * <p>BUG: This will work only for single encoded variants.
0562: * @param states The current negotiation states.
0563: * @param request The request to handle.
0564: * @return a boolean.
0565: * @exception ProtocolException If one of the variants doesn't exist.
0566: */
0567:
0568: protected boolean negotiateContentEncoding(Vector states,
0569: Request request) throws ProtocolException {
0570: if (!request.hasAcceptEncoding() || !b_encoding) {
0571: // All encodings accepted:
0572: for (int i = 0; i < states.size(); i++) {
0573: VariantState state = (VariantState) states.elementAt(i);
0574: state.setContentEncodingQuality(1.0);
0575: }
0576: } else {
0577: HttpAcceptEncoding encodings[] = request
0578: .getAcceptEncoding();
0579: for (int i = 0; i < states.size(); i++) {
0580: VariantState state = (VariantState) states.elementAt(i);
0581: ResourceReference rr = state.getResource();
0582: try {
0583: FramedResource resource = (FramedResource) rr
0584: .unsafeLock();
0585: HTTPFrame itsframe = (HTTPFrame) resource
0586: .unsafeGetFrame(httpFrameClass);
0587: if (itsframe != null) {
0588: String ve;
0589: ve = (String) itsframe.unsafeGetValue(
0590: ATTR_CONTENT_ENCODING, null);
0591: if (ve == null) {
0592: ve = "identity"; // default encoding
0593: state.setContentEncodingQuality(1.0);
0594: } else {
0595: state.setContentEncodingQuality(0.01);
0596: }
0597: int jidx = -1;
0598: for (int j = 0; j < encodings.length; j++) {
0599: if (encodings[j].getEncoding().equals(ve)) {
0600: jidx = j;
0601: break;
0602: }
0603: if (encodings[j].getEncoding().equals("*"))
0604: jidx = j; // default '*' if no better match
0605: }
0606: if (jidx >= 0)
0607: state
0608: .setContentEncodingQuality(encodings[jidx]
0609: .getQuality()
0610: - jidx * 0.001);
0611: }
0612: } catch (InvalidResourceException ex) {
0613:
0614: } finally {
0615: rr.unlock();
0616: }
0617: }
0618: // FIXME We should check here against unlegible variants as now
0619: }
0620: return false;
0621: }
0622:
0623: /**
0624: * Negotiate on charsets.
0625: * The algorithm is described in RFC2616 (Obsoletes RFC2068)
0626: * @param states The current states of negotiation.
0627: * @param request The request to handle.
0628: */
0629:
0630: protected boolean negotiateCharsetQuality(Vector states,
0631: Request request) {
0632: if (!request.hasAcceptCharset() || !b_charset) {
0633: // All variants get a quality of 1.0
0634: for (int i = 0; i < states.size(); i++) {
0635: VariantState state = (VariantState) states.elementAt(i);
0636: state.setCharsetQuality(1.0);
0637: }
0638: } else {
0639: // The browser has given some preferences:
0640: HttpAcceptCharset charsets[] = request.getAcceptCharset();
0641:
0642: for (int i = 0; i < states.size(); i++) {
0643: VariantState state = (VariantState) states.elementAt(i);
0644: // Get the most specific match for this variant:
0645: ResourceReference rr = state.getResource();
0646: try {
0647: FramedResource resource = (FramedResource) rr
0648: .unsafeLock();
0649: HTTPFrame itsframe = (HTTPFrame) resource
0650: .unsafeGetFrame(httpFrameClass);
0651: if (itsframe != null) {
0652: MimeType vt;
0653: vt = (MimeType) itsframe.unsafeGetValue(
0654: ATTR_CONTENT_TYPE, null);
0655: String fcharset = vt
0656: .getParameterValue("charset");
0657: // if not defined in the frame, it must be the default
0658: if (fcharset == null) {
0659: fcharset = "ISO-8859-1";
0660: }
0661: double qual = 0.0;
0662: boolean default_done = false;
0663: String charset;
0664: for (int j = 0; j < charsets.length; j++) {
0665: charset = charsets[j].getCharset();
0666: if (charset.equals("*")) {
0667: default_done = true;
0668: if (qual == 0) {
0669: qual = charsets[j].getQuality()
0670: - 0.001 * j;
0671: }
0672: } else {
0673: if (charset.equals("ISO-8859-1"))
0674: default_done = true;
0675: if (charset.equals(fcharset))
0676: if (charsets[j].getQuality() > qual) {
0677: qual = charsets[j].getQuality()
0678: - 0.001 * j;
0679: }
0680: }
0681: }
0682: if (!default_done
0683: && fcharset.equals("ISO-8859-1"))
0684: qual = 1.0 - 0.001 * charsets.length;
0685: state.setCharsetQuality(qual);
0686: }
0687: } catch (InvalidResourceException ex) {
0688: //FIXME
0689: } finally {
0690: rr.unlock();
0691: }
0692: }
0693: }
0694: return false;
0695: }
0696:
0697: /**
0698: * Negotiate among language qualities.
0699: * <p>BUG: This will only work for variants that have one language tag.
0700: * @param states The current states of negotiation.
0701: * @param request The request to handle.
0702: * @return a boolean.
0703: * @exception ProtocolException If one of the variants doesn't exist.
0704: */
0705:
0706: protected boolean negotiateLanguageQuality(Vector states,
0707: Request request) throws ProtocolException {
0708: if (!request.hasAcceptLanguage() || !b_language) {
0709: for (int i = 0; i < states.size(); i++) {
0710: VariantState state = (VariantState) states.elementAt(i);
0711: state.setLanguageQuality(1.0);
0712: }
0713: } else {
0714: HttpAcceptLanguage languages[] = request
0715: .getAcceptLanguage();
0716: LanguageTag req_lang[] = new LanguageTag[languages.length];
0717: for (int i = 0; i < languages.length; i++) {
0718: req_lang[i] = new LanguageTag(languages[i]
0719: .getLanguage());
0720: }
0721: boolean varyLang = false;
0722: for (int i = 0; i < states.size(); i++) {
0723: VariantState state = (VariantState) states.elementAt(i);
0724: ResourceReference rr = state.getResource();
0725: try {
0726: FramedResource resource = (FramedResource) rr
0727: .unsafeLock();
0728: HTTPFrame itsframe = (HTTPFrame) resource
0729: .getFrame(httpFrameClass);
0730: if (itsframe != null) {
0731: String lang;
0732: lang = (String) itsframe.unsafeGetValue(
0733: ATTR_CONTENT_LANGUAGE, null);
0734: if (lang == null) {
0735: state.setLanguageQuality(-1.0);
0736: } else {
0737: varyLang = true;
0738: LanguageTag ftag = new LanguageTag(lang);
0739: int jmatch = -1;
0740: int jidx = -1;
0741: for (int j = 0; j < languages.length; j++) {
0742: int match = ftag.match(req_lang[j]);
0743: if (match > jmatch) {
0744: jmatch = match;
0745: jidx = j;
0746: }
0747: }
0748: if (jidx < 0)
0749: state.setLanguageQuality(0.01);
0750: else {
0751: // little hack for first
0752: state
0753: .setLanguageQuality(languages[jidx]
0754: .getQuality()
0755: - jidx * 0.001);
0756: }
0757: }
0758: }
0759: } catch (InvalidResourceException ex) {
0760: //FIXME
0761: } finally {
0762: rr.unlock();
0763: }
0764: }
0765: if (varyLang) {
0766: for (int i = 0; i < states.size(); i++) {
0767: VariantState s = (VariantState) states.elementAt(i);
0768: if (s.getLanguageQuality() < 0)
0769: s.setLanguageQuality(0.5);
0770: }
0771: } else {
0772: for (int i = 0; i < states.size(); i++) {
0773: VariantState s = (VariantState) states.elementAt(i);
0774: s.setLanguageQuality(1.0);
0775: }
0776: }
0777: }
0778: return false;
0779: }
0780:
0781: /**
0782: * Negotiate among content types.
0783: * @param states The current states of negotiation.
0784: * @param request The request to handle.
0785: * @return a boolean.
0786: * @exception ProtocolException If one of the variants doesn't exist.
0787: */
0788:
0789: protected boolean negotiateContentType(Vector states,
0790: Request request) throws ProtocolException {
0791: if (!request.hasAccept() || !b_type) {
0792: // All variants get a quality of 1.0
0793: for (int i = 0; i < states.size(); i++) {
0794: VariantState state = (VariantState) states.elementAt(i);
0795: state.setQuality(1.0);
0796: }
0797: } else {
0798: // The browser has given some preferences:
0799: HttpAccept accepts[] = request.getAccept();
0800:
0801: for (int i = 0; i < states.size(); i++) {
0802: VariantState state = (VariantState) states.elementAt(i);
0803: // Get the most specific match for this variant:
0804: ResourceReference rr = state.getResource();
0805: try {
0806: FramedResource resource = (FramedResource) rr
0807: .unsafeLock();
0808: HTTPFrame itsframe = (HTTPFrame) resource
0809: .unsafeGetFrame(httpFrameClass);
0810: if (itsframe != null) {
0811: MimeType vt;
0812: vt = (MimeType) itsframe.unsafeGetValue(
0813: ATTR_CONTENT_TYPE, null);
0814: int jmatch = -1;
0815: int jidx = -1;
0816: for (int j = 0; j < accepts.length; j++) {
0817: try {
0818: int match = vt.match(accepts[j]
0819: .getMimeType());
0820: if (match > jmatch) {
0821: jmatch = match;
0822: jidx = j;
0823: }
0824: } catch (HttpInvalidValueException ivex) {
0825: // There is a bad acept header here
0826: // let's be cool and ignore it
0827: // FIXME we should answer with a Bad Request
0828: }
0829: }
0830: if (jidx < 0)
0831: state.setQuality(0.0);
0832: else
0833: state.setQuality(accepts[jidx].getQuality()
0834: - jidx * 0.001);
0835: }
0836: } catch (InvalidResourceException ex) {
0837: //FIXME
0838: } finally {
0839: rr.unlock();
0840: }
0841: }
0842: }
0843: return false;
0844: }
0845:
0846: /**
0847: * Negotiate among the various variants for the Resource.
0848: * We made our best efforts to be as compliant as possible to the HTTP/1.0
0849: * content negotiation algorithm.
0850: * @param request the incomming request.
0851: * @return a RefourceReference instance.
0852: * @exception ProtocolException If one of the variants doesn't exist.
0853: */
0854: protected ResourceReference negotiate(Request request)
0855: throws ProtocolException {
0856: // Check for zero or one variant:
0857: ResourceReference variants[] = getVariantResources();
0858: if (variants == null) {
0859: try {
0860: getResource().delete();
0861: } catch (MultipleLockException ex) {
0862: //will be deleted later...
0863: } finally {
0864: Reply reply = request.makeReply(HTTP.NOT_FOUND);
0865: reply.setContent("<h1>Document not found</h1>"
0866: + "<p>The document " + request.getURL()
0867: + " has no acceptable variants "
0868: + "(probably deleted).");
0869: throw new HTTPException(reply);
0870: }
0871: }
0872: if (variants.length < 2) {
0873: if (variants.length == 0) {
0874: try {
0875: getResource().delete();
0876: } catch (MultipleLockException ex) {
0877: //will be deleted later...
0878: } finally {
0879: Reply reply = request.makeReply(HTTP.NOT_FOUND);
0880: reply.setContent("<h1>Document not found</h1>"
0881: + "<p>The document " + request.getURL()
0882: + " has no acceptable variants "
0883: + "(probably deleted).");
0884: throw new HTTPException(reply);
0885: }
0886: } else {
0887: return variants[0];
0888: }
0889: }
0890: // Build a vector of variant negociation states, one per variants:
0891: Vector states = new Vector(variants.length);
0892: for (int i = 0; i < variants.length; i++) {
0893: double qs = 1.0;
0894: try {
0895: FramedResource resource = (FramedResource) variants[i]
0896: .unsafeLock();
0897: HTTPFrame itsframe = (HTTPFrame) resource
0898: .unsafeGetFrame(httpFrameClass);
0899: if (itsframe != null) {
0900: if (itsframe.unsafeDefinesAttribute(ATTR_QUALITY))
0901: qs = itsframe.unsafeGetQuality();
0902: if (qs > REQUIRED_QUALITY)
0903: states.addElement(new VariantState(variants[i],
0904: qs));
0905: }
0906: } catch (InvalidResourceException ex) {
0907: //FIXME
0908: } finally {
0909: variants[i].unlock();
0910: }
0911: }
0912: // Content-encoding negociation:
0913: if (debug) {
0914: printNegotiationState("init:", states);
0915: }
0916: if (negotiateContentEncoding(states, request)) {
0917: // Remains a single acceptable variant:
0918: return ((VariantState) states.elementAt(0)).getResource();
0919: }
0920: if (debug) {
0921: printNegotiationState("encoding:", states);
0922: }
0923: // Charset quality negociation:
0924: if (negotiateCharsetQuality(states, request)) {
0925: // Remains a single acceptable variant:
0926: return ((VariantState) states.elementAt(0)).getResource();
0927: }
0928: if (debug) {
0929: printNegotiationState("charset:", states);
0930: }
0931: // Language quality negociation:
0932: if (negotiateLanguageQuality(states, request)) {
0933: // Remains a single acceptable variant:
0934: return ((VariantState) states.elementAt(0)).getResource();
0935: }
0936: if (debug) {
0937: printNegotiationState("language:", states);
0938: }
0939: // Content-type negociation:
0940: if (negotiateContentType(states, request)) {
0941: // Remains a single acceptable variant:
0942: return ((VariantState) states.elementAt(0)).getResource();
0943: }
0944: if (debug) {
0945: printNegotiationState("type:", states);
0946: }
0947: // If we reached this point, this means that multiple variants are
0948: // acceptable at this point. Keep the ones that have the best quality.
0949: if (debug) {
0950: printNegotiationState("before Q selection:", states);
0951: }
0952: double qmax = REQUIRED_QUALITY;
0953:
0954: for (int i = 0; i < states.size();) {
0955: VariantState state = (VariantState) states.elementAt(i);
0956: if (state.getQ() > qmax) {
0957: for (int j = i; j > 0; j--)
0958: states.removeElementAt(0);
0959: qmax = state.getQ();
0960: i = 1;
0961: } else {
0962: if (state.getQ() < qmax)
0963: states.removeElementAt(i);
0964: else
0965: i++;
0966: }
0967: }
0968: if (debug)
0969: printNegotiationState("After Q selection:", states);
0970: if (qmax == REQUIRED_QUALITY) {
0971:
0972: Reply reply = request.makeReply(HTTP.NOT_ACCEPTABLE);
0973: HtmlGenerator g = new HtmlGenerator("No acceptable");
0974: g
0975: .append("<P>The resource cannot be served according to the "
0976: + "headers sent</P>");
0977: reply.setStream(g);
0978: throw new HTTPException(reply);
0979: } else if (states.size() == 1) {
0980: return ((VariantState) states.elementAt(0)).getResource();
0981: } else {
0982: // Respond with multiple choice (for the time being, there should
0983: // be a parameter to decide what to do.
0984: Reply reply = request.makeReply(HTTP.MULTIPLE_CHOICE);
0985: HtmlGenerator g = new HtmlGenerator("Multiple choice for "
0986: + resource.getIdentifier());
0987: g.append("<ul>");
0988: for (int i = 0; i < states.size(); i++) {
0989: VariantState state = (VariantState) states.elementAt(i);
0990: String name = null;
0991: ResourceReference rr = state.getResource();
0992: try {
0993: name = rr.unsafeLock().getIdentifier();
0994: g.append("<li>" + "<a href=\"" + name + "\">"
0995: + name + "</a>" + " Q= " + state.getQ());
0996: } catch (InvalidResourceException ex) {
0997: //FIXME
0998: } finally {
0999: rr.unlock();
1000: }
1001: }
1002: reply.setStream(g);
1003: reply.setHeaderValue(reply.H_VARY, getVary());
1004: throw new HTTPException(reply);
1005: }
1006: }
1007:
1008: /**
1009: * "negotiate" for a PUT, the negotiation of a PUT should be
1010: * different as we just want to match the desciption of the entity
1011: * and the available variants
1012: * @param request, the request to handle
1013: * @return a ResourceReference instance
1014: * @exception ProtocolException If negotiating among the resource variants
1015: * failed.
1016: * @exception ResourceException If the resource got a fatal error.
1017: */
1018:
1019: protected ResourceReference negotiatePut(Request request)
1020: throws ProtocolException, ResourceException {
1021: // Check for zero or one variant:
1022: ResourceReference variants[] = getVariantResources();
1023: HTTPFrame itsframe;
1024: int nb_v;
1025: // zero, don't PUT on a negotiable resource!
1026: if (variants == null || variants.length == 0) {
1027: try {
1028: getResource().delete();
1029: } catch (MultipleLockException ex) {
1030: //will be deleted later...
1031: } finally {
1032: Reply reply = request.makeReply(HTTP.NOT_FOUND);
1033: reply.setContent("<h1>Document not found</h1>"
1034: + "<p>The document " + request.getURL()
1035: + " has no acceptable variants "
1036: + "(probably deleted).");
1037: throw new HTTPException(reply);
1038: }
1039: }
1040: // negotiate etag
1041: HttpEntityTag etag = request.getETag();
1042: HttpEntityTag etags[] = request.getIfMatch();
1043: // gather the etags
1044: if (etags == null && etag != null) {
1045: etags = new HttpEntityTag[1];
1046: etags[0] = etag;
1047: } else if (etag != null) {
1048: HttpEntityTag t_etags[] = new HttpEntityTag[etags.length + 1];
1049: System.arraycopy(etags, 0, t_etags, 0, etags.length);
1050: t_etags[etags.length] = etag;
1051: etags = t_etags;
1052: }
1053:
1054: if (etags != null) {
1055: // yeah go for it!
1056: FramedResource resource;
1057: HttpEntityTag frametag;
1058: for (int i = 0; i < variants.length; i++) {
1059: try {
1060: resource = (FramedResource) variants[i]
1061: .unsafeLock();
1062: itsframe = (HTTPFrame) resource
1063: .getFrame(httpFrameClass);
1064: if (itsframe != null) {
1065: frametag = itsframe.getETag();
1066: if (frametag == null) {
1067: continue;
1068: }
1069: // Do we have a winner?
1070: try {
1071: for (int j = 0; j < etags.length; j++)
1072: if (frametag.getTag().equals(
1073: etags[j].getTag()))
1074: return variants[i];
1075: } catch (NullPointerException ex) {
1076: // if the list of etag contains a null
1077: // it should never happen and the try doesn't cost
1078: }
1079: }
1080: } catch (InvalidResourceException ex) {
1081: //FIXME
1082: } finally {
1083: variants[i].unlock();
1084: }
1085: }
1086: // no matching variants...
1087: Reply reply = request.makeReply(HTTP.NOT_FOUND);
1088: reply.setContent("<h1>Document not found</h1>"
1089: + "<p>The document " + request.getURL()
1090: + " has no acceptable variants "
1091: + "according to the ETag sent");
1092: throw new HTTPException(reply);
1093: }
1094: // if we are strict, don't go any further, etags
1095: // is the mandatory thing, otherwise PUT on the direct version
1096: if (getPutPolicy()) {
1097: Reply reply = request.makeReply(HTTP.NOT_FOUND);
1098: reply.setContent("<h1>Document not found</h1>"
1099: + "<p>The document " + request.getURL()
1100: + " has no acceptable variants "
1101: + " for a PUT, as no ETags were sent");
1102: throw new HTTPException(reply);
1103: }
1104: // now filter out variants
1105: nb_v = variants.length;
1106: MimeType type = request.getContentType();
1107: String encodings[] = request.getContentEncoding();
1108: String languages[] = request.getContentLanguage();
1109: ResourceReference rr;
1110:
1111: if (type != null || encodings != null || languages != null) {
1112: // the request is not too bad ;)
1113: for (int i = 0; i < variants.length; i++) {
1114: if (variants[i] == null)
1115: continue;
1116: rr = variants[i];
1117: try {
1118: resource = (FramedResource) rr.unsafeLock();
1119: itsframe = (HTTPFrame) resource
1120: .getFrame(httpFrameClass);
1121: if (itsframe == null) {
1122: nb_v--;
1123: variants[i] = null;
1124: continue;
1125: }
1126: // remove the non matching mime types
1127: if (type != null) {
1128: MimeType fmt = itsframe.getContentType();
1129: if (fmt == null
1130: || (fmt.match(type) != MimeType.MATCH_SPECIFIC_SUBTYPE)) {
1131: nb_v--;
1132: variants[i] = null;
1133: continue;
1134: }
1135: }
1136: // remove the non matching languages
1137: if (languages != null) {
1138: String language = itsframe.getContentLanguage();
1139: nb_v--;
1140: variants[i] = null;
1141: if (language == null) {
1142: continue;
1143: }
1144: for (int j = 0; j < languages.length; j++) {
1145: if (language.equals(languages[j])) {
1146: nb_v++;
1147: variants[i] = rr;
1148: break;
1149: }
1150: }
1151: }
1152: // remove the non matching encodings
1153: if (encodings != null) {
1154: String encoding = itsframe.getContentEncoding();
1155: nb_v--;
1156: variants[i] = null;
1157: if (encoding == null) {
1158: continue;
1159: }
1160: for (int j = 0; j < encodings.length; j++) {
1161: if (encoding.equals(languages[j])) {
1162: nb_v++;
1163: variants[i] = rr;
1164: break;
1165: }
1166: }
1167: }
1168: } catch (InvalidResourceException ex) {
1169: //FIXME
1170: } finally {
1171: rr.unlock();
1172: }
1173: }
1174: // a winner!
1175: if (nb_v == 1) {
1176: for (int i = 0; i < variants.length; i++) {
1177: if (variants[i] != null)
1178: return variants[i];
1179: }
1180: }
1181: // no document matching
1182: if (nb_v <= 0) {
1183: Reply reply = request.makeReply(HTTP.NOT_FOUND);
1184: reply
1185: .setContent("<h1>Document not found</h1>"
1186: + "<p>The document " + request.getURL()
1187: + " has no acceptable variants "
1188: + " for a PUT");
1189: throw new HTTPException(reply);
1190: }
1191: }
1192: // now we have multiple choice :(
1193: String name;
1194: Reply reply = request.makeReply(HTTP.MULTIPLE_CHOICE);
1195: HtmlGenerator g = new HtmlGenerator("Multiple choice for "
1196: + resource.getIdentifier());
1197: g.append("<ul>");
1198: for (int i = 0; i < variants.length; i++) {
1199: if (variants[i] != null) {
1200: try {
1201: name = variants[i].unsafeLock().getIdentifier();
1202: g.append("<li>" + "<a href=\"" + name + "\">"
1203: + name + "</a>");
1204: } catch (InvalidResourceException ex) {
1205: //FIXME (this should NOT happen :) )
1206: } finally {
1207: variants[i].unlock();
1208: }
1209: }
1210: }
1211: reply.setStream(g);
1212: reply.setHeaderValue(reply.H_VARY, getVary());
1213: throw new HTTPException(reply);
1214: }
1215:
1216: public void registerResource(FramedResource resource) {
1217: super .registerOtherResource(resource);
1218: }
1219:
1220: /**
1221: * Lookup the target resource.
1222: * @param ls The current lookup state
1223: * @param lr The result
1224: * @return true if lookup is done.
1225: * @exception ProtocolException If an error relative to the protocol occurs
1226: */
1227: public boolean lookup(LookupState ls, LookupResult lr)
1228: throws ProtocolException {
1229: if (ls.isDirectory()) { // we are not a directory, bail out
1230: Request req = (Request) ls.getRequest();
1231: String locstate = (String) req
1232: .getState(STATE_CONTENT_LOCATION);
1233: if (locstate == null) {
1234: lr.setTarget(null);
1235: return true;
1236: }
1237: }
1238: ResourceFrame frames[] = getFrames();
1239: if (frames != null) {
1240: for (int i = 0; i < frames.length; i++) {
1241: if (frames[i] == null) {
1242: continue;
1243: }
1244: if (frames[i].lookup(ls, lr)) {
1245: return true;
1246: }
1247: }
1248: }
1249: if (ls.hasMoreComponents()) {
1250: // We are not a container resource, and we don't have children:
1251: lr.setTarget(null);
1252: return false;
1253: } else {
1254: //we are done! try to find the negotiated one...
1255: RequestInterface reqi = ls.getRequest();
1256: ResourceReference selected;
1257: Request request = (Request) reqi;
1258: // Run content negotiation now:
1259: // The PUT is special, we negotiate with ETag (if present) or
1260: // using the metainformation about the PUT entity.
1261: String method = request.getMethod();
1262: try {
1263: if (method.equals("PUT")) {
1264: selected = negotiatePut(request);
1265: } else {
1266: selected = negotiate(request);
1267: }
1268: } catch (ResourceException ex) {
1269: // the failure will be processed in perform
1270: return false;
1271: }
1272: if (selected != null) {
1273: try {
1274: FramedResource resource = (FramedResource) selected
1275: .unsafeLock();
1276: resource.lookup(ls, lr);
1277: } catch (InvalidResourceException ex) {
1278: // the failure will be processed in perform
1279: } finally {
1280: selected.unlock();
1281: }
1282: }
1283: request.setState(STATE_NEG, selected);
1284: // fake now, we handle the process to have a two-level processing
1285: // just to add the Vary: header
1286: lr.setTarget(getResourceReference());
1287: return true;
1288: }
1289: }
1290:
1291: /**
1292: * Perform an HTTP request.
1293: * Negotiate among the variants, the best variant according to the request
1294: * fields, and make this elected variant serve the request.
1295: * @param request The request to handle.
1296: * @exception ProtocolException If negotiating among the resource variants
1297: * failed.
1298: * @exception ResourceException If the resource got a fatal error.
1299: */
1300:
1301: public ReplyInterface perform(RequestInterface req)
1302: throws ProtocolException, ResourceException {
1303: ReplyInterface repi = performFrames(req);
1304: if (repi != null)
1305: return repi;
1306:
1307: if (!checkRequest(req))
1308: return null;
1309:
1310: Request request = (Request) req;
1311: ResourceReference selected;
1312: // get the right resources
1313: if (request.hasState(STATE_NEG)) {
1314: selected = (ResourceReference) request.getState(STATE_NEG);
1315: } else {
1316: String method = request.getMethod();
1317: if (method.equals("PUT")) {
1318: selected = negotiatePut(request);
1319: } else {
1320: selected = negotiate(request);
1321: }
1322: }
1323:
1324: // This should never happen: either the negotiation succeed, or the
1325: // negotiate method should return an error.
1326: if (selected == null) {
1327: Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
1328: error
1329: .setContent("Error negotiating among resource's variants.");
1330: throw new HTTPException(error);
1331: }
1332:
1333: try {
1334: FramedResource resource = (FramedResource) selected
1335: .unsafeLock();
1336: Reply reply = (Reply) resource.perform(request);
1337: reply.setHeaderValue(reply.H_VARY, getVary());
1338: HTTPFrame itsframe = (HTTPFrame) resource
1339: .unsafeGetFrame(httpFrameClass);
1340: if (itsframe != null) {
1341: reply.setContentLocation(itsframe.getURL(request)
1342: .toExternalForm());
1343: return reply;
1344: }
1345: Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
1346: error.setContent("Error negotiating : "
1347: + "selected resource has no HTTPFrame");
1348: throw new HTTPException(error);
1349: } catch (InvalidResourceException ex) {
1350: Reply error = request.makeReply(HTTP.INTERNAL_SERVER_ERROR);
1351: error
1352: .setContent("Error negotiating : Invalid selected resource");
1353: throw new HTTPException(error);
1354: } finally {
1355: selected.unlock();
1356: }
1357: }
1358: }
|