001: // Ssiframe.java
002: // $Id: SSIFrame.java,v 1.13 2000/08/16 21:37:46 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.ssi;
007:
008: import java.io.ByteArrayOutputStream;
009: import java.io.File;
010: import java.io.FileInputStream;
011: import java.io.IOException;
012: import java.io.PrintStream;
013: import java.io.RandomAccessFile;
014:
015: import java.util.Dictionary;
016: import java.util.Vector;
017:
018: import org.w3c.www.http.HTTP;
019: import org.w3c.www.http.HeaderValue;
020: import org.w3c.www.http.HttpDate;
021: import org.w3c.www.http.HttpEntityMessage;
022: import org.w3c.www.http.HttpInteger;
023: import org.w3c.www.http.HttpMessage;
024: import org.w3c.www.http.HttpReplyMessage;
025: import org.w3c.www.http.HttpRequestMessage;
026:
027: import org.w3c.tools.resources.Attribute;
028: import org.w3c.tools.resources.AttributeHolder;
029: import org.w3c.tools.resources.AttributeRegistry;
030: import org.w3c.tools.resources.BooleanAttribute;
031: import org.w3c.tools.resources.ClassAttribute;
032: import org.w3c.tools.resources.FileResource;
033: import org.w3c.tools.resources.IntegerAttribute;
034: import org.w3c.tools.resources.ProtocolException;
035: import org.w3c.tools.resources.ReplyInterface;
036: import org.w3c.tools.resources.RequestInterface;
037: import org.w3c.tools.resources.Resource;
038: import org.w3c.tools.resources.ResourceException;
039:
040: import org.w3c.tools.resources.event.AttributeChangedEvent;
041:
042: import org.w3c.jigsaw.http.HTTPException;
043: import org.w3c.jigsaw.http.Reply;
044: import org.w3c.jigsaw.http.Request;
045:
046: import org.w3c.jigsaw.frames.HTTPFrame;
047:
048: import org.w3c.util.ArrayDictionary;
049:
050: import org.w3c.jigsaw.ssi.commands.CommandRegistry;
051:
052: import org.w3c.tools.resources.ProtocolException;
053: import org.w3c.tools.resources.ResourceException;
054:
055: /**
056: * This resource implements server-side parsing of HTML documents.
057: * Any comment of the form <code><!--#commandName param1=val1
058: * ... paramn=valn --></code> will be interpreted as an include
059: * directive.
060: * <p> Commands are looked up in an instance of the class
061: * supplied in the registryClass attribute, which must be a subclass
062: * of <code>org.w3c.jigsaw.ssi.CommandRegistry</code>.
063: *
064: * @author Antonio Ramirez <anto@mit.edu>
065: * @author Benoit Mahe <bmahe@sophia.inria.fr>
066: * @see org.w3c.jigsaw.ssi.commands.CommandRegistry
067: * @see org.w3c.jigsaw.ssi.commands.Command
068: */
069:
070: public class SSIFrame extends HTTPFrame {
071:
072: public static final boolean debug = false;
073:
074: /** Attributes index - The segments */
075: private static int ATTR_SEGMENTS = -1;
076:
077: /** Attributes index - The total unparsed size */
078: private static int ATTR_UNPARSED_SIZE = -1;
079:
080: /** Attribute index - The class to use for making the CommandRegistry */
081: private static int ATTR_REGISTRY_CLASS = -1;
082:
083: /** Attributes index - The maximum recursive parsing depth */
084: private static int ATTR_MAX_DEPTH = -1;
085:
086: /** Attribute index - Whether or not to deny insecure commands */
087: private static int ATTR_SECURE = -1;
088:
089: /**
090: * The command registry used by the resource.
091: */
092: private CommandRegistry commReg = null;
093:
094: /**
095: * The most specific class of the current command registry.
096: */
097: private Class regClass = null;
098:
099: /**
100: * Here we keep track of created registries to avoid
101: * making more than one per registry class.
102: */
103: private static Dictionary regs = new ArrayDictionary(5);
104:
105: /** Will hold the increments for finding "<!--#" */
106: private static byte startIncrements[] = new byte[128];
107:
108: /** Same thing for "-->" */
109: private static byte endIncrements[] = new byte[128];
110:
111: /** The start pattern */
112: private static byte startPat[] = { (byte) '<', (byte) '!',
113: (byte) '-', (byte) '-', (byte) '#' };
114:
115: /** The end pattern */
116: private static byte endPat[] = { (byte) '-', (byte) '-', (byte) '>' };
117:
118: // For value-less parameters
119: private static final String emptyString = "";
120:
121: /**
122: * Our "very global" variables
123: */
124: protected Dictionary vars = null;
125:
126: /**
127: * Message state - the current recursion depth
128: */
129: public static final String STATE_DEPTH = "org.w3c.jigsaw.ssi.SSIResource.depth";
130:
131: /**
132: * Message state - the current variables
133: */
134: public static final String STATE_VARIABLES = "org.w3c.jigsaw.ssi.SSIResource.variables";
135:
136: private boolean cacheReplies = true;
137:
138: protected void doNotCacheReply() {
139: cacheReplies = false;
140: }
141:
142: protected boolean cacheReplies() {
143: return cacheReplies;
144: }
145:
146: /**
147: * Listen its resource.
148: */
149: public void attributeChanged(AttributeChangedEvent evt) {
150: super .attributeChanged(evt);
151: String name = evt.getAttribute().getName();
152: if ((name.equals("file-stamp")) || (name.equals("file-length"))) {
153: setValue(ATTR_SEGMENTS, (Segment[]) null);
154: }
155: }
156:
157: private final int getUnparsedSize() {
158: return getInt(ATTR_UNPARSED_SIZE, -1);
159: }
160:
161: private final void setUnparsedSize(int unparsedSize) {
162: setValue(ATTR_UNPARSED_SIZE, new Integer(unparsedSize));
163: }
164:
165: /**
166: * Makes sure that checkContent() is called on _any_ HTTP method,
167: * so that the internal representation of commands is always consistent.
168: * @param request The HTTPRequest
169: * @param filters The filters to apply
170: * @return a ReplyInterface instance
171: * @exception ProtocolException If processing the request failed.
172: * @exception ResourceException If this resource got a fatal error.
173: */
174:
175: public ReplyInterface perform(RequestInterface request)
176: throws ProtocolException, ResourceException {
177: if (!checkRequest(request)) {
178: return performFrames(request);
179: }
180: if (fresource != null)
181: fresource.checkContent();
182: return super .perform(request);
183: }
184:
185: /**
186: * Perform a get (associated with a FileResource)
187: * @param request the HTTP request
188: * @return a Reply instance.
189: * @exception ProtocolException If processing the request failed.
190: * @exception ResourceException If this resource got a fatal error.
191: */
192: protected Reply getFileResource(Request request)
193: throws ProtocolException, ResourceException {
194: Reply reply = handle(request);
195: return reply != null ? reply : super .getFileResource(request);
196: }
197:
198: /**
199: * Perform a post.
200: * @param request the HTTP request
201: * @return a Reply instance.
202: * @exception ProtocolException If processing the request failed.
203: * @exception ResourceException If this resource got a fatal error.
204: */
205: public Reply post(Request request) throws ProtocolException,
206: ResourceException {
207: Reply reply = handle(request);
208: return reply != null ? reply : super .post(request);
209: }
210:
211: /**
212: * Handles all relevant HTTP methods.
213: * Merges the partial replies from each of the segments into
214: * one global reply.
215: * <strong>Remark</strong>: no direct relation to PostableResource.handle()
216: * @param request The HTTP request
217: * @return a Reply instance.
218: * @exception ProtocolException If processing the request failed.
219: */
220:
221: public Reply handle(Request request) throws ProtocolException {
222: if (fresource == null)
223: return null;
224:
225: if (SSIFrame.debug)
226: System.out.println("@@@@ handle: "
227: + (request.isInternal() ? "internal" : "external"));
228: fresource.checkContent();
229:
230: Integer depth = (Integer) request.getState(STATE_DEPTH);
231: if (depth == null)
232: depth = new Integer(0);
233:
234: int unparsedSize = 0;
235:
236: Segment[] segments = getSegments();
237: if (segments == null) {
238: parseFirstTime();
239: if ((segments = getSegments()) == null)
240: return null; // Last resort: fall back to superclass
241: }
242: Reply reply = null;
243: try {
244: // Obtain a command registry
245: updateRegistry();
246:
247: vars = (Dictionary) request.getState(STATE_VARIABLES);
248:
249: // Initialize the registry-dependent variables:
250: vars = commReg.initVariables(this , request, vars);
251:
252: // Add our "very global" variables
253: vars.put("secure", getValue(ATTR_SECURE, Boolean.TRUE));
254: vars.put("maxDepth", getValue(ATTR_MAX_DEPTH, new Integer(
255: 10)));
256: vars.put("depth", depth);
257: vars.put("registry", commReg);
258:
259: // Prepare the initial reply
260: // (which represents the unparsed parts of the document)
261: // and a prototype reply for segments that return null.
262: reply = createDefaultReply(request, HTTP.OK);
263: Reply defSegReply = createDefaultReply(request, HTTP.OK);
264:
265: int unpSize = getUnparsedSize();
266: if (unpSize == -1)
267: reply.setHeaderValue(Reply.H_CONTENT_LENGTH, null);
268: else
269: reply.setContentLength(unpSize);
270: defSegReply.setHeaderValue(Reply.H_CONTENT_LENGTH, null);
271:
272: long ims = request.getIfModifiedSince();
273: long cmt = fresource.getFileStamp();
274: // used to be getLastModified()
275: // should be something better
276: // than either
277: if (SSIFrame.debug)
278: System.out.println("@@@@ IMS: " + cmt + " vs " + ims);
279: if (ims != -1 && cmt != -1 && cmt <= ims) {
280: reply.setStatus(HTTP.NOT_MODIFIED);
281: defSegReply.setStatus(HTTP.NOT_MODIFIED);
282: } else if (ims != -1) {
283: if (SSIFrame.debug)
284: System.out.println("@@@@ Removed NOT MODIFIED");
285: }
286:
287: if (cmt != -1)
288: defSegReply.setLastModified(cmt);
289:
290: // For each segment:
291: // . obtain a reply,
292: // . merge its headers with the global reply's headers,
293: Reply[] partReps = new Reply[segments.length];
294: for (int i = 0; i < segments.length; i++) {
295: if (!segments[i].isUnparsed()) {
296: if (SSIFrame.debug)
297: System.out.println("@@@@ Analyzing segment "
298: + segments[i]);
299:
300: partReps[i] = segments[i].init(this , request, vars,
301: commReg, i);
302:
303: if (SSIFrame.debug) {
304: if (partReps[i] == null)
305: System.out.println("@@@@ (null segment)");
306: System.out.println("@@@@ cacheReplies : "
307: + cacheReplies());
308: }
309:
310: merge(reply, partReps[i] != null ? partReps[i]
311: : defSegReply);
312: }
313: }
314:
315: // Set a stream, unless we're not supposed to.
316: // Also handle the case of no command segments.
317: switch (reply.getStatus()) {
318: default:
319: reply.setStream(new SSIStream(cacheReplies(), segments,
320: partReps, new RandomAccessFile(fresource
321: .getFile(), "r")));
322: case HTTP.NO_CONTENT:
323: case HTTP.NOT_MODIFIED:
324: }
325:
326: if (SSIFrame.debug)
327: System.out.println("@@@@ Last-modified: "
328: + reply.getLastModified());
329:
330: reply.setDate(System.currentTimeMillis());
331: return reply;
332:
333: } catch (SSIException ex) {
334: reply = createDefaultReply(request,
335: HTTP.INTERNAL_SERVER_ERROR);
336: reply.setContent("SSIFrame is misconfigured: "
337: + ex.getMessage());
338: throw new HTTPException(reply);
339: } catch (Exception ex) {
340: ex.printStackTrace();
341: if (SSIFrame.debug) {
342: if (SSIFrame.debug)
343: System.out.println("@@@@ Fallback to FileResource");
344: }
345: return null; // Last resort: fall back to superclass
346: }
347: }
348:
349: // The headers to merge and their corresponding callbacks
350: // (more to come)
351: private static final int mergeHeaders[] = { Reply.H_AGE,
352: Reply.H_CONTENT_LENGTH, Reply.H_EXPIRES,
353: Reply.H_LAST_MODIFIED, };
354:
355: private static final Merger mergers[] = { new IntMaximizer(), // Reply.H_AGE
356: new IntAdder(), // Reply.H_CONTENT_LENGTH
357: new DateMinimizer(), // Reply.H_EXPIRES
358: new DateMaximizer() // Reply.H_LAST_MODIFIED
359: };
360:
361: /**
362: * Merges the headers (and status code) of a segment's reply with
363: * those of the global reply.
364: *
365: * @param glob the global reply
366: * @param part the segment's partial reply
367: */
368: private void merge(Reply glob, Reply part) {
369: // Deal with status code first
370: int pstat = part.getStatus();
371: int gstat = glob.getStatus();
372:
373: if (pstat == HTTP.NOT_MODIFIED) {
374: switch (gstat) {
375: default:
376: glob.setStatus(HTTP.OK);
377: case HTTP.NOT_MODIFIED:
378: }
379: } else if (gstat == HTTP.NOT_MODIFIED) {
380: if (SSIFrame.debug)
381: System.out.println("**** removed NOT MODIFIED");
382: glob.setStatus(HTTP.OK);
383: }
384:
385: // Now handle headers
386: // "pointers to methods" would make this simpler
387: for (int i = 0; i < mergeHeaders.length; i++)
388: glob.setHeaderValue(mergeHeaders[i], mergers[i].merge(glob
389: .getHeaderValue(mergeHeaders[i]), part
390: .getHeaderValue(mergeHeaders[i])));
391:
392: // Now handle annoying quasi-headers:
393: int pint, gint;
394:
395: // Cache-Control: max-age=n
396: // (don't merge if set as attribute)
397: if (getMaxAge() != -1 && (pint = part.getMaxAge()) != -1) {
398: if ((gint = glob.getMaxAge()) != -1)
399: pint = Math.min(gint, pint);
400: glob.setMaxAge(pint);
401: }
402: }
403:
404: /**
405: * Retrieves the segments from the attribute
406: * @return An array of segments
407: */
408: private final Segment[] getSegments() {
409: return (Segment[]) getValue(ATTR_SEGMENTS, null);
410: }
411:
412: /**
413: * Updates the working command registry if either the registryClass
414: * attribute has changed or it has never been created before.
415: * <p>To avoid unnecessarily creating command registry
416: * instances, this method will keep track of which kinds of command
417: * registries have been created, and avoid making duplicates.
418: * @exception SSIException If the operation can't be performed.
419: */
420: private void updateRegistry() throws SSIException {
421: try {
422: Class attrRegClass = (Class) getValue(ATTR_REGISTRY_CLASS,
423: null);
424: if (attrRegClass == null)
425: attrRegClass = Class
426: .forName("org.w3c.jigsaw.ssi.commands.DefaultCommandRegistry");
427:
428: if (regClass == null || !attrRegClass.equals(regClass)) {
429: regClass = attrRegClass;
430: commReg = fetchRegistry(regClass);
431: }
432: } catch (ClassNotFoundException ex) {
433: throw new SSIException("Cannot make registry: "
434: + ex.getMessage());
435: }
436: }
437:
438: /**
439: * Returns an instance of the given command registry class,
440: * either a new instance, or an old one if it exists in the dictionary.
441: * @exception SSIException If the operation can't be performed.
442: */
443: private CommandRegistry fetchRegistry(Class regClass)
444: throws SSIException {
445: try {
446: CommandRegistry reg = (CommandRegistry) regs.get(regClass);
447: if (reg != null)
448: return reg;
449: else {
450: reg = (CommandRegistry) regClass.newInstance();
451: regs.put(regClass, reg);
452: return reg;
453: }
454: } catch (Exception ex) {
455: throw new SSIException("Cannot fetch command registry: "
456: + ex.getMessage());
457: }
458: }
459:
460: /** Reads the unparsed file into memory, if not already done */
461: private byte[] readUnparsed() throws IOException {
462: File file = fresource.getFile();
463: ByteArrayOutputStream out = new ByteArrayOutputStream(
464: (int) file.length());
465:
466: FileInputStream in = new FileInputStream(file);
467:
468: byte[] buf = new byte[4096];
469: int len = 0;
470:
471: while ((len = in.read(buf)) != -1)
472: out.write(buf, 0, len);
473:
474: in.close();
475: out.close();
476:
477: byte[] unparsed = out.toByteArray();
478: return unparsed;
479: }
480:
481: /**
482: * Does a first-time parse and sets the segment list attribute
483: * accordingly.
484: */
485: private void parseFirstTime() {
486: if (debug)
487: System.out.println("@@@ parseFirstTime");
488: cacheReplies = true;
489: byte[] unparsed = null;
490: try {
491: unparsed = readUnparsed();
492: } catch (IOException ex) {
493: setValue(ATTR_SEGMENTS, null);
494: return;
495: }
496:
497: // The parsing code was adapted from phttpd
498:
499: int byteIdx = 0, startInc, endInc, startParam, endParam, paramIdx, i;
500: byte ch, quote;
501: int max, length = 0;
502: boolean valueFound;
503:
504: int unparsedSize = 0;
505:
506: // For maintaining the segment list
507: Vector buildSegments = new Vector(20);
508:
509: StringBuffer cmdBuf = null;
510: String cmdName = null;
511: Vector /*<String>*/parNames = null;
512: Vector /*<String>*/parValues = null;
513: String name = null, value = null;
514:
515: // To store where the last segment ended
516: int lastSegEnd = 0;
517:
518: do {
519: byteIdx += 4;
520: while (byteIdx < unparsed.length) {
521: if ((ch = unparsed[byteIdx]) == (byte) '#')
522: if (byteArrayNEquals(unparsed, byteIdx - 4,
523: startPat, 0, 4)) {
524: break;
525: }
526:
527: // This is an ugly work-around to the
528: // absence of unsigned bytes in Java.
529: byteIdx += startIncrements[ch >= 0 ? ch : 0];
530: }
531:
532: if (++byteIdx >= unparsed.length)
533: break; // Nothing found
534:
535: // Record the start of the command name and parameter list
536: startInc = (startParam = paramIdx = byteIdx) - 5;
537:
538: // Add the previous segment of unparsed text
539: // (Unless empty)
540: if (startInc > lastSegEnd) {
541: buildSegments.addElement(new Segment(lastSegEnd,
542: startInc));
543: unparsedSize += startInc - lastSegEnd;
544: lastSegEnd = startInc;
545: }
546:
547: // Now find the end of the comment
548: byteIdx += 2;
549: while (byteIdx < unparsed.length) {
550: if ((ch = unparsed[byteIdx]) == (byte) '>')
551: if (unparsed[byteIdx - 2] == (byte) '-'
552: && unparsed[byteIdx - 1] == (byte) '-')
553: break;
554:
555: // This is an ugly work-around to the absence of
556: // unsigned bytes in Java:
557: byteIdx += endIncrements[ch >= 0 ? ch : 0];
558: }
559: if (++byteIdx >= unparsed.length)
560: break; // No end found
561:
562: // The end of the parameter list is 3 bytes earlier
563: endParam = byteIdx - 3;
564:
565: // Record the nominal end of the command segment
566: endInc = byteIdx;
567:
568: // Skip white space before command
569: while (paramIdx < endParam && isSpace(unparsed[paramIdx]))
570: paramIdx++;
571: if (paramIdx >= endParam)
572: continue; // No command name
573:
574: max = endParam - paramIdx;
575:
576: cmdName = parseCmdName(unparsed, paramIdx, max);
577:
578: // If not found, take this one as unparsed and
579: // search for the next include.
580: if (cmdName == null) {
581: buildSegments.addElement(new Segment(startInc, endInc));
582: unparsedSize += endInc - startInc;
583: lastSegEnd = endInc;
584: continue;
585: }
586:
587: parNames = new Vector(5);
588: parValues = new Vector(5);
589:
590: parseCmdParams(unparsed, paramIdx + cmdName.length(),
591: endParam, parNames, parValues);
592:
593: buildSegments.addElement(new Segment(this , cmdName,
594: new ArrayDictionary(parNames, parValues),
595: lastSegEnd, endInc));
596: lastSegEnd = endInc;
597:
598: } while (byteIdx < unparsed.length);
599:
600: // Add the last chunk of unparsed text as a segment
601: buildSegments.addElement(new Segment(lastSegEnd,
602: unparsed.length));
603: unparsedSize += unparsed.length - lastSegEnd;
604:
605: setUnparsedSize(unparsedSize);
606:
607: Segment[] segs = new Segment[buildSegments.size()];
608: buildSegments.copyInto(segs);
609: setValue(ATTR_SEGMENTS, segs);
610: }
611:
612: private final String parseCmdName(byte[] unparsed, int start,
613: int max) {
614: StringBuffer cmdBuf = new StringBuffer(80);
615: char ch;
616: for (int i = 0; i < max; i++) {
617: ch = (char) unparsed[start + i];
618: if (Character.isWhitespace(ch))
619: break;
620: cmdBuf.append(ch);
621: }
622: return cmdBuf.length() == 0 ? null : cmdBuf.toString();
623: }
624:
625: private final void parseCmdParams(byte[] unparsed, int start,
626: int end, Vector names, Vector values) {
627: String name = null;
628: String value = null;
629: int startParam = -1;
630:
631: int paramIdx = start;
632: while (paramIdx < end) {
633: while (isSpace(unparsed[paramIdx++]))
634: ;
635: if (paramIdx >= end)
636: break;
637:
638: byte ch = unparsed[--paramIdx];
639: startParam = paramIdx;
640: while (paramIdx < end && !isSpace(ch) && ch != (byte) '=')
641: ch = unparsed[++paramIdx];
642:
643: int length = paramIdx - startParam;
644: if (length <= 0)
645: break;
646:
647: name = new String(unparsed, 0, startParam, length);
648: value = emptyString;
649:
650: boolean valueFound = false;
651: while (isSpace(ch) || ch == (byte) '=') {
652: if (ch == (byte) '=')
653: valueFound = true;
654: ch = unparsed[++paramIdx];
655: }
656:
657: if (paramIdx >= end)
658: valueFound = false;
659:
660: byte quote;
661: if (valueFound)
662: if (ch == '"' || ch == '\'') {
663: quote = ch;
664: ch = unparsed[++paramIdx];
665:
666: startParam = paramIdx;
667: while (paramIdx < end && ch != quote)
668: ch = unparsed[++paramIdx];
669: length = paramIdx - startParam;
670: value = new String(unparsed, 0, startParam, length);
671: paramIdx++;
672: } else {
673: startParam = paramIdx;
674: while (paramIdx < end && !isSpace(ch))
675: ch = unparsed[++paramIdx];
676: length = paramIdx - startParam;
677: value = new String(unparsed, 0, startParam, length);
678: }
679: names.addElement(name);
680: values.addElement(value);
681: }
682:
683: }
684:
685: /**
686: * Analogous to standard C's <code>strncmp</code>, for byte arrays.
687: * (Should be in some utility package, I'll put it here for now)
688: * @param ba1 the first byte array
689: * @param off1 where to start in the first array
690: * @param ba2 the second byte array
691: * @param off2 where to start in the second array
692: * @param n the length to compare up to
693: * @return <strong>true</strong> if both specified parts of the arrays are
694: * equal, <strong>false</strong> if they aren't .
695: */
696: public static final boolean byteArrayNEquals(byte[] ba1, int off1,
697: byte[] ba2, int off2, int n) {
698: // So that only one addition is needed inside loop
699: int corr = off2 - off1;
700: int max = n + off1;
701: for (int i = off1; i < max; i++)
702: if (ba1[i] != ba2[i + corr])
703: return false;
704: return true;
705: }
706:
707: /**
708: * Does the same as Character.isSpace, without need to cast the
709: * byte into a char.
710: * @param ch the character
711: * @return whether or not ch is ASCII white space
712: * @see java.lang.Character#isSpace
713: */
714: private final boolean isSpace(byte ch) {
715: return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
716: }
717:
718: public long getLastModified()
719: //FIXME
720: {
721: long a = super .getLastModified();
722: return a - a % 1000;
723: }
724:
725: public final Reply createDefaultReply(Request request, int status) {
726: Reply reply = super .createDefaultReply(request, status);
727: reply.setHeaderValue(Reply.H_LAST_MODIFIED, null);
728: reply.setKeepConnection(false);
729: return reply;
730: }
731:
732: public final Reply createCommandReply(Request request, int status) {
733: return createDefaultReply(request, status);
734: }
735:
736: static {
737: // Initialize search increments first
738: for (int i = 0; i < 128; i++) {
739: startIncrements[i] = 5;
740: endIncrements[i] = 3;
741: }
742: startIncrements[(int) ('<')] = 4;
743: startIncrements[(int) ('!')] = 3;
744: startIncrements[(int) ('-')] = 1;
745: endIncrements[(int) ('-')] = 1;
746:
747: // Initialize attributes
748: Attribute a = null;
749: Class cls = null;
750: Class regClass = null;
751:
752: try {
753: cls = Class.forName("org.w3c.jigsaw.ssi.SSIFrame");
754: regClass = Class
755: .forName("org.w3c.jigsaw.ssi.commands.DefaultCommandRegistry");
756: } catch (Exception ex) {
757: ex.printStackTrace();
758: System.exit(0);
759: }
760:
761: // The maxDepth attribute
762: a = new IntegerAttribute("maxDepth", new Integer(10),
763: Attribute.EDITABLE);
764: ATTR_MAX_DEPTH = AttributeRegistry.registerAttribute(cls, a);
765:
766: // The secure attribute
767: a = new BooleanAttribute("secure", Boolean.TRUE,
768: Attribute.EDITABLE);
769: ATTR_SECURE = AttributeRegistry.registerAttribute(cls, a);
770:
771: // The segments attribute
772: a = new SegmentArrayAttribute("segments", null,
773: Attribute.COMPUTED);
774: ATTR_SEGMENTS = AttributeRegistry.registerAttribute(cls, a);
775:
776: // The unparsedSize attribute
777: a = new IntegerAttribute("unparsedSize", null,
778: Attribute.COMPUTED);
779: ATTR_UNPARSED_SIZE = AttributeRegistry
780: .registerAttribute(cls, a);
781:
782: // The registryClass attribute
783: a = new ClassAttribute("registryClass", regClass,
784: Attribute.EDITABLE);
785: ATTR_REGISTRY_CLASS = AttributeRegistry.registerAttribute(cls,
786: a);
787:
788: }
789:
790: }
791:
792: /**
793: * Merger classes are used to provide callbacks and make
794: * header merging more uniform. (Though it may be overkill...)
795: */
796: abstract class Merger {
797: abstract HeaderValue merge(HeaderValue g, HeaderValue p);
798: }
799:
800: class IntMaximizer extends Merger {
801: HeaderValue merge(HeaderValue g, HeaderValue p) {
802: if (p != null) {
803: if (g != null) {
804: ((HttpInteger) g).setValue(Math.max(((Integer) g
805: .getValue()).intValue(), ((Integer) p
806: .getValue()).intValue()));
807: } else
808: return p;
809: }
810: return g;
811: }
812: }
813:
814: class IntMinimizer extends Merger {
815: HeaderValue merge(HeaderValue g, HeaderValue p) {
816: if (p != null) {
817: if (g != null) {
818: ((HttpInteger) g).setValue(Math.min(((Integer) g
819: .getValue()).intValue(), ((Integer) p
820: .getValue()).intValue()));
821: } else
822: return p;
823: }
824: return g;
825: }
826: }
827:
828: class IntAdder extends Merger {
829: HeaderValue merge(HeaderValue g, HeaderValue p) {
830: if (SSIFrame.debug)
831: System.out.println("&&&& Adder: g=" + g + ", p=" + p);
832: if (g != null) {
833: if (p != null) {
834: int b = ((Integer) g.getValue()).intValue()
835: + ((Integer) p.getValue()).intValue();
836:
837: ((HttpInteger) g).setValue(b);
838: } else
839: return null;
840: }
841: return g;
842: }
843: }
844:
845: class DateMinimizer extends Merger {
846: HeaderValue merge(HeaderValue g, HeaderValue p) {
847: if (p != null) {
848: if (g != null) {
849: ((HttpDate) g).setValue(Math
850: .min(((Long) g.getValue()).longValue(),
851: ((Long) p.getValue()).longValue()));
852: } else
853: return p;
854: }
855: return g;
856: }
857: }
858:
859: class DateMaximizer extends Merger {
860: HeaderValue merge(HeaderValue g, HeaderValue p) {
861: if (p != null) {
862: if (g != null) {
863: ((HttpDate) g).setValue(Math
864: .max(((Long) g.getValue()).longValue(),
865: ((Long) p.getValue()).longValue()));
866: } else
867: return p;
868: }
869: return g;
870: }
871: }
|