001: /*
002: * Copyright (c) 2001 by Matt Welsh and The Regents of the University of
003: * California. All rights reserved.
004: *
005: * Permission to use, copy, modify, and distribute this software and its
006: * documentation for any purpose, without fee, and without written agreement is
007: * hereby granted, provided that the above copyright notice and the following
008: * two paragraphs appear in all copies of this software.
009: *
010: * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
011: * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
012: * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
013: * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
014: *
015: * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
016: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
017: * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
018: * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
019: * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
020: *
021: * Author: Matt Welsh <mdw@cs.berkeley.edu>
022: *
023: */
024:
025: package com.rimfaxe.webserver.seda;
026:
027: import seda.apps.Haboob.*;
028: import seda.apps.Haboob.http.*;
029: import seda.sandStorm.api.*;
030: import seda.sandStorm.core.*;
031: import seda.sandStorm.lib.http.*;
032: import seda.sandStorm.lib.aSocket.*;
033: import seda.sandStorm.lib.aDisk.*;
034: import seda.util.*;
035: import java.io.*;
036: import java.util.*;
037:
038: /**
039: * This version of PageCache maintains a list of cacheEntries for each
040: * page size, and attempts to reuse old entries of the same size on reject.
041: * This is the best implementation of the Haboob web page cache.
042: */
043: public class PageCacheSized implements EventHandlerIF, HaboobConst {
044:
045: private static final boolean DEBUG = false;
046: private static final boolean PROFILE = false;
047:
048: // Don't actually read file; just store empty buffer in cache
049: private static final boolean DEBUG_NO_FILE_READ = false;
050: // Don't even stat file; just allocate buffer of fixed size
051: private static final boolean DEBUG_NO_FILE_READ_SAMESIZE = false;
052: private static final int DEBUG_NO_FILE_READ_SAMESIZE_SIZE = 8192;
053: // Don't read file through aFile interface - just do it directly
054: private static final boolean DEBUG_DIRECT_FILE = false;
055:
056: // Rewrite incoming filename so all cache entries hit
057: private static final boolean DEBUG_SINGLE_CACHE_PAGE = false;
058: // If true, rewrite all request URLs to DEBUG_SINGLE_CACHE_PAGE_FNAME
059: // If false, all cache misses access same file, but different entries
060: private static final boolean DEBUG_SINGLE_CACHE_PAGE_SAMENAME = false;
061: // This file is of size 8192 bytes
062: private static final String DEBUG_SINGLE_CACHE_PAGE_FNAME = "/dir00000/class1_7";
063:
064: // Whether to prioritize cache hits over misses
065: private static final boolean PRIORITIZE_HITS = true;
066: private myComparator myComp;
067:
068: // Whether to handle misses in separate stage
069: private static final boolean SEPARATE_MISS_STAGE = true;
070: private SinkIF missStageSink;
071: private boolean missStage;
072:
073: private String DEFAULT_URL;
074: private String ROOT_DIR;
075:
076: private SinkIF mysink, sendSink;
077: private Hashtable pageTbl; // Map URL -> cacheEntry
078: private Hashtable sizeTbl; // Map size -> linked list of free cacheEntries
079: private Hashtable aFileTbl; // Map aFile -> cacheEntry
080: private int maxCacheSize;
081: private Random rand;
082:
083: private Hashtable mimeTbl; // Filename extension -> MIME type
084: private static final String defaultMimeType = "text/plain";
085:
086: // Used to initialize hit stage
087: public PageCacheSized() {
088: missStage = false;
089: }
090:
091: // Used to initialize miss stage
092: PageCacheSized(PageCacheSized hitStage) {
093: missStage = true;
094: pageTbl = hitStage.pageTbl;
095: sizeTbl = hitStage.sizeTbl;
096: aFileTbl = hitStage.aFileTbl;
097: mimeTbl = hitStage.mimeTbl;
098: rand = new Random();
099: DEFAULT_URL = hitStage.DEFAULT_URL;
100: ROOT_DIR = hitStage.ROOT_DIR;
101: maxCacheSize = hitStage.maxCacheSize;
102: if (PRIORITIZE_HITS) {
103: myComp = hitStage.myComp;
104: }
105: }
106:
107: public void init(ConfigDataIF config) throws Exception {
108: mysink = config.getStage().getSink();
109: System.err.println("PageCacheSized: missStage=" + missStage
110: + ", mysink=" + mysink);
111: sendSink = config.getManager().getStage(HTTP_SEND_STAGE)
112: .getSink();
113:
114: if (!missStage) {
115: pageTbl = new Hashtable();
116: sizeTbl = new Hashtable();
117: aFileTbl = new Hashtable();
118: rand = new Random();
119:
120: mimeTbl = new Hashtable();
121: mimeTbl.put(".html", "text/html");
122: mimeTbl.put(".gif", "image/gif");
123: mimeTbl.put(".jpg", "image/jpeg");
124: mimeTbl.put(".jpeg", "image/jpeg");
125: mimeTbl.put(".pdf", "application/pdf");
126:
127: DEFAULT_URL = config.getString("defaultURL");
128: if (DEFAULT_URL == null)
129: throw new IllegalArgumentException(
130: "Must specify defaultURL");
131: ROOT_DIR = config.getString("rootDir");
132: if (ROOT_DIR == null)
133: throw new IllegalArgumentException(
134: "Must specify rootDir");
135: maxCacheSize = config.getInt("maxCacheSize");
136:
137: if (PRIORITIZE_HITS) {
138: myComp = new myComparator();
139: }
140:
141: if (SEPARATE_MISS_STAGE) {
142: StageIF missStage = config.getManager().createStage(
143: "PageCacheSized missStage",
144: new PageCacheSized(this ), null);
145: missStageSink = missStage.getSink();
146: }
147: }
148: }
149:
150: public void destroy() {
151: }
152:
153: public void handleEvent(QueueElementIF item) {
154: if (DEBUG)
155: System.err.println("PageCacheSized (missStage=" + missStage
156: + "): GOT QEL: " + item);
157:
158: if (item instanceof httpRequest) {
159: HaboobStats.numRequests++;
160:
161: httpRequest req = (httpRequest) item;
162: if (req.getRequest() != httpRequest.REQUEST_GET) {
163: HaboobStats.numErrors++;
164: HttpSend
165: .sendResponse(new httpResponder(
166: new httpBadRequestResponse(req,
167: "Only GET requests supported at this time"),
168: req, true));
169: return;
170: }
171:
172: String url;
173: if (DEBUG_SINGLE_CACHE_PAGE
174: && DEBUG_SINGLE_CACHE_PAGE_SAMENAME) {
175: url = DEBUG_SINGLE_CACHE_PAGE_FNAME;
176: } else {
177: url = req.getURL();
178: }
179:
180: cacheEntry entry;
181: synchronized (pageTbl) {
182:
183: if (DEBUG)
184: System.err
185: .println("PageCacheSized: Checking cache for URL "
186: + url);
187: long t1 = 0, t2;
188: if (PROFILE)
189: t1 = System.currentTimeMillis();
190: entry = (cacheEntry) pageTbl.get(url);
191: if (PROFILE) {
192: t2 = System.currentTimeMillis();
193: HaboobStats.numCacheLookup++;
194: HaboobStats.timeCacheLookup += (t2 - t1);
195: }
196:
197: if (entry == null) {
198: // Got a cache miss
199: handleCacheMiss(req);
200: return;
201: }
202: }
203:
204: if (DEBUG)
205: System.err
206: .println("PageCacheSized: Got entry " + entry);
207: HaboobStats.numCacheHits++;
208: synchronized (entry) {
209: if (entry.pending) {
210: // Entry still pending - wait for it
211: if (DEBUG)
212: System.err
213: .println("PageCacheSized: Entry still pending");
214: entry.addWaiter(req);
215: } else {
216: // Got a hit - send it
217: if (DEBUG)
218: System.err
219: .println("PageCacheSized: Sending entry");
220: entry.send(req);
221: }
222: }
223:
224: } else if (item instanceof AFileIOCompleted) {
225:
226: AFileIOCompleted comp = (AFileIOCompleted) item;
227: AFile af = comp.getFile();
228: if (DEBUG)
229: System.err.println("PageCacheSized: Got AIOComp for "
230: + af.getFilename());
231:
232: cacheEntry entry = (cacheEntry) aFileTbl.get(af);
233: if (entry == null) {
234: System.err
235: .println("PageCacheSized: WARNING: Got AFileIOCompleted for non-entry: "
236: + comp);
237: return;
238: }
239: entry.done(comp);
240:
241: } else if (item instanceof SinkClosedEvent) {
242: SinkClosedEvent sce = (SinkClosedEvent) item;
243: if (sce.sink instanceof httpConnection) {
244: // Pass on to sendSink if not a file close event
245: sendSink.enqueue_lossy(sce);
246: }
247:
248: } else {
249: System.err
250: .println("PageCacheSized: Got unknown event type: "
251: + item);
252: }
253: }
254:
255: class myComparator implements Comparator {
256: public int compare(Object o1, Object o2) {
257: if ((o1 instanceof httpRequest)
258: && (o2 instanceof httpRequest)) {
259: httpRequest req1 = (httpRequest) o1;
260: httpRequest req2 = (httpRequest) o2;
261: int req1sz = isHit(req1);
262: int req2sz = isHit(req2);
263: if ((req1sz != -1) && (req2sz != -1)) {
264: if (req1sz < req2sz)
265: return -1;
266: else if (req1sz > req2sz)
267: return 1;
268: else
269: return 0;
270: } else if ((req1sz == -1) && (req2sz != -1)) {
271: return 1;
272: } else if ((req1sz != -1) && (req2sz == -1)) {
273: return -1;
274: } else {
275: return 0;
276: }
277: } else if ((o1 instanceof httpRequest)
278: && (!(o2 instanceof httpRequest))) {
279: return -1;
280: } else if (!(o1 instanceof httpRequest)
281: && ((o2 instanceof httpRequest))) {
282: return 1;
283: } else {
284: return 0;
285: }
286: }
287: }
288:
289: public void handleEvents(QueueElementIF items[]) {
290: if (PRIORITIZE_HITS) {
291: // Sort entries first
292: Arrays.sort(items, myComp);
293: }
294:
295: for (int i = 0; i < items.length; i++) {
296: handleEvent(items[i]);
297: }
298: }
299:
300: private int isHit(httpRequest req) {
301: cacheEntry entry = (cacheEntry) pageTbl.get(req.getURL());
302: if ((entry != null) && (!entry.pending))
303: return entry.size;
304: else
305: return -1;
306: }
307:
308: private void handleCacheMiss(httpRequest req) {
309: String url;
310: String fname;
311: long t1 = 0, t2;
312:
313: if (SEPARATE_MISS_STAGE && !missStage) {
314: if (!missStageSink.enqueue_lossy(req)) {
315: System.err
316: .println("PageCacheSized: WARNING: Could not enqueue "
317: + req + " to missStageSink");
318: }
319: return;
320: }
321:
322: if (DEBUG)
323: System.err
324: .println("PageCacheSized: Handling cache miss for "
325: + req);
326: HaboobStats.numCacheMisses++;
327:
328: url = req.getURL();
329:
330: String rootdir = (String) req.getSpecial("ROOT_DIR");
331: String webapp_root = (String) req.getSpecial("WEBAPP_ROOT");
332:
333: url = url.substring(webapp_root.length());
334: fname = rootdir + url;
335:
336: //System.out.println("PageCacheSized, ROOT_DIR="+rootdir+" WEBAPP_ROOT="+webapp_root+"\nresolved filename to "+fname);
337:
338: AFile af = null;
339: AFileStat stat = null;
340: cacheEntry entry;
341:
342: if (DEBUG_NO_FILE_READ && DEBUG_NO_FILE_READ_SAMESIZE) {
343: // Create bogus entry
344: if (DEBUG)
345: System.err
346: .println("PageCacheSized: Creating bogus cacheEntry");
347: entry = getEntry(req, null,
348: DEBUG_NO_FILE_READ_SAMESIZE_SIZE);
349:
350: } else if (DEBUG_DIRECT_FILE) {
351:
352: // Don't use AFile - just read file directly
353: try {
354: File f = new File(fname);
355: RandomAccessFile raf = new RandomAccessFile(f, "r");
356: if (DEBUG)
357: System.err.println("PageCacheSized: Got file size "
358: + f.length());
359: entry = getEntry(req, null, (int) f.length());
360: if (DEBUG)
361: System.err
362: .println("PageCacheSized: Reading file directly, length "
363: + f.length()
364: + ", entrysize "
365: + entry.response.getPayload()
366: .getBytes().length);
367: BufferElement payload = entry.response.getPayload();
368: raf.readFully(payload.getBytes(), payload.offset,
369: payload.size);
370: raf.close();
371: entry.pending = false;
372: httpResponder respd = new httpResponder(entry.response,
373: req);
374: HttpSend.sendResponse(respd);
375: return;
376:
377: } catch (IOException ioe) {
378: // File not found
379: System.err
380: .println("PageCacheSized: Could not open file "
381: + fname + ": " + ioe);
382: //ioe.printStackTrace();
383: HaboobStats.numErrors++;
384: httpNotFoundResponse notfound = new httpNotFoundResponse(
385: req, ioe.getMessage());
386: HttpSend.sendResponse(new httpResponder(notfound, req,
387: true));
388: return;
389: }
390:
391: } else {
392:
393: while (true) {
394: // Open file and stat it to determine size
395: try {
396: af = new AFile(fname, mysink, false, true);
397: stat = af.stat();
398: break;
399:
400: } catch (FileIsDirectoryException fde) {
401: // Tried to open a directory
402:
403: if (url.endsWith("/")) {
404: // Replace file with DEFAULT_URL and try again
405: if (fname.endsWith("/")) {
406: fname = fname + DEFAULT_URL;
407: } else {
408: fname = fname + "/" + DEFAULT_URL;
409: }
410: continue;
411: } else {
412: // Redirect to url+"/" (so that img src works in the document)
413: String newURL = url + "/";
414: httpRedirectResponse redirect = new httpRedirectResponse(
415: req, newURL);
416: HttpSend.sendResponse(new httpResponder(
417: redirect, req, true));
418: return;
419: }
420:
421: } catch (IOException ioe) {
422: // File not found
423: System.err
424: .println("PageCacheSized: Could not open file "
425: + fname + ": " + ioe);
426: HaboobStats.numErrors++;
427: httpNotFoundResponse notfound = new httpNotFoundResponse(
428: req, ioe.getMessage());
429: HttpSend.sendResponse(new httpResponder(notfound,
430: req, true));
431: return;
432: }
433: }
434:
435: // Allocate entry
436: if (DEBUG)
437: System.err.println("PageCacheSized: Got file size "
438: + stat.length);
439: entry = getEntry(req, af, (int) stat.length);
440: }
441:
442: if (DEBUG_NO_FILE_READ) {
443: // Pretend we got it already
444: entry.done(null);
445: } else {
446: // Issue read
447: entry.doRead();
448: }
449: }
450:
451: private String getMimeType(String url) {
452: Enumeration e = mimeTbl.keys();
453: while (e.hasMoreElements()) {
454: String key = (String) e.nextElement();
455: if (url.endsWith(key))
456: return (String) mimeTbl.get(key);
457: }
458: return defaultMimeType;
459: }
460:
461: // Obtain a new cache entry (either allocating a new entry or
462: // reusing an old one)
463: private cacheEntry getEntry(httpRequest req, AFile af, int size) {
464: cacheEntry entry = null;
465:
466: if (DEBUG)
467: System.err.println("PageCacheSized: Finding entry of size "
468: + size);
469:
470: if ((maxCacheSize != -1)
471: && (HaboobStats.cacheSizeBytes + size > maxCacheSize * 1024)) {
472: // Cache is full, try to reuse entry
473: if (DEBUG)
474: System.err
475: .println("PageCacheSized: Cache is full (size "
476: + (HaboobStats.cacheSizeBytes / 1024)
477: + " Kb)");
478: Integer isz = new Integer(size);
479: ssLinkedList ll = (ssLinkedList) sizeTbl.get(isz);
480: if ((ll == null) || (ll.size() == 0)) {
481: // No entries available, allocate new
482: if (DEBUG)
483: System.err
484: .println("PageCacheSized: No entry of this size, allocating");
485: return new cacheEntry(req, af, size);
486: } else {
487: // Reuse entry
488: if (DEBUG)
489: System.err.println("PageCacheSized: Sizelist has "
490: + ll.size() + " elements");
491: boolean found = false;
492: int count = 0;
493: while (count < ll.size()) {
494: entry = (cacheEntry) ll.remove_head();
495: if (entry.pending) {
496: ll.add_to_tail(entry);
497: count++;
498: } else {
499: if (DEBUG)
500: System.err
501: .println("PageCacheSized: Reusing entry "
502: + entry);
503: found = true;
504: break;
505: }
506: }
507: if (!found) {
508: // All entries pending, allocate anyway
509: if (DEBUG)
510: System.err
511: .println("PageCacheSized: All entries pending, allocating new");
512: return new cacheEntry(req, af, size);
513: }
514:
515: // Place back on list and reuse
516: ll.add_to_tail(entry);
517: entry.reuse(req, af);
518: return entry;
519: }
520:
521: } else {
522: if (DEBUG)
523: System.err
524: .println("PageCacheSized: Cache not full (size "
525: + (HaboobStats.cacheSizeBytes / 1024)
526: + " Kb), allocating");
527: // Cache not full, go ahead and allocate
528: return new cacheEntry(req, af, size);
529: }
530:
531: }
532:
533: private class cacheEntry {
534: httpResponse response;
535: boolean pending;
536: int size;
537: AFile af;
538: ssLinkedList waiting, sizeList;
539: String url;
540: long tStartRead, tEndRead;
541:
542: // Allocate a new cache entry
543: private cacheEntry(httpRequest req, AFile af, int size) {
544: if (DEBUG)
545: System.err
546: .println("PageCacheSized: Allocating new cache entry for "
547: + af.getFilename() + ", size=" + size);
548:
549: if (af == null) {
550: if (DEBUG)
551: System.err.println("PageCacheSized: af==null");
552: this .response = new httpOKResponse("text/plain", size);
553: } else {
554: Hashtable headertable = new Hashtable();
555: String maxage = "" + req.getSpecial("MAX-AGE");
556: if (DEBUG)
557: System.err
558: .println("PageCacheSized: special value max-age="
559: + maxage);
560: if (!maxage.equalsIgnoreCase("null")) {
561: if (!maxage.equalsIgnoreCase("-1")) {
562: headertable.put("Cache-Control", "max-age="
563: + maxage);
564: }
565: }
566:
567: this .response = new httpOKResponse(getMimeType(af
568: .getFilename()), size, headertable);
569: }
570:
571: this .size = size;
572: this .url = req.getURL();
573: this .af = af;
574: pending = true;
575: waiting = new ssLinkedList();
576: addWaiter(req);
577:
578: // Add to pageTbl
579: pageTbl.put(url, this );
580: // Add to aFileTbl
581: if (af != null) {
582: aFileTbl.put(af, this );
583: }
584: // Add to sizeTbl
585: Integer isz = new Integer(size);
586: ssLinkedList ll = (ssLinkedList) sizeTbl.get(isz);
587: if (ll == null) {
588: ll = new ssLinkedList();
589: sizeTbl.put(isz, ll);
590: }
591: ll.add_to_tail(this );
592: this .sizeList = ll;
593: HaboobStats.cacheSizeEntries++;
594: HaboobStats.cacheSizeBytes += size;
595: }
596:
597: // Reuse a cache entry
598: /* FIXME: Avoid reuse of cache entry that is currently being
599: * written out to another socket? (Maintain 'write count' which
600: * is incremented for each send, decremented for each SinkDreainedEvent,
601: * and SinkClosedEvent (when the associated SinkDrainedEvent did
602: * not arrive yet due to the conn being closed first).
603: */
604: private synchronized void reuse(httpRequest req, AFile af) {
605: if (DEBUG)
606: System.err.println("PageCacheSized: entry " + this
607: + " being reused for " + af.getFilename());
608: if (this .af != null) {
609: aFileTbl.remove(this .af);
610: this .af.close();
611: }
612: this .af = af;
613: if (af != null) {
614: aFileTbl.put(af, this );
615: }
616: synchronized (pageTbl) {
617: pageTbl.remove(url);
618: this .url = req.getURL();
619: pageTbl.put(url, this );
620: }
621: pending = true;
622: waiting.remove_all();
623: addWaiter(req);
624: }
625:
626: // Initiate file read
627: void doRead() {
628: if (DEBUG)
629: System.err
630: .println("PageCacheSized: Initiating read on entry "
631: + this );
632: if (af == null)
633: return;
634: try {
635: if (PROFILE)
636: tStartRead = System.currentTimeMillis();
637: af.read(response.getPayload());
638: } catch (SinkException se) {
639: System.err
640: .println("PageCacheSized: Got SinkException attempting read on "
641: + af + ": " + se);
642: HaboobStats.numErrors++;
643: httpRequest waiter;
644: while ((waiter = (httpRequest) waiting.remove_head()) != null) {
645: httpNotFoundResponse notfound = new httpNotFoundResponse(
646: waiter, se.getMessage());
647: httpResponder respd = new httpResponder(notfound,
648: waiter, true);
649: HttpSend.sendResponse(respd);
650: }
651: free();
652: }
653: }
654:
655: // Free cache entry and remove from system for GC
656: void free() {
657: System.err.println("PageCacheSized: Freeing entry " + this );
658: if (af != null) {
659: aFileTbl.remove(af);
660: af.close();
661: af = null;
662: }
663: pageTbl.remove(url);
664: sizeList.remove_item(this );
665: response = null;
666: }
667:
668: synchronized void addWaiter(httpRequest req) {
669: waiting.add_to_tail(req);
670: }
671:
672: httpResponse getResponse() {
673: return response;
674: }
675:
676: // Send response to all waiters when done reading
677: synchronized void done(AFileIOCompleted comp) {
678: if (DEBUG)
679: System.err
680: .println("PageCacheSized: Done with file read on "
681: + this );
682:
683: if ((comp != null) && (comp.sizeCompleted != size)) {
684: throw new RuntimeException(
685: "PageCacheSized: WARNING: Got "
686: + comp.sizeCompleted
687: + " bytes read, expecting " + size);
688: }
689:
690: if (af != null) {
691: af.close();
692: aFileTbl.remove(af);
693: af = null;
694: }
695: if (PROFILE) {
696: tEndRead = System.currentTimeMillis();
697: HaboobStats.numFileRead++;
698: HaboobStats.timeFileRead += (tEndRead - tStartRead);
699: }
700:
701: pending = false;
702: httpRequest waiter;
703:
704: while ((waiter = (httpRequest) waiting.remove_head()) != null) {
705: httpResponder respd = new httpResponder(response,
706: waiter);
707: HttpSend.sendResponse(respd);
708: }
709: }
710:
711: // Send cache entry on hit
712: void send(httpRequest req) {
713:
714: httpResponder respd = new httpResponder(response, req);
715: HttpSend.sendResponse(respd);
716: }
717:
718: public String toString() {
719: if (af != null) {
720: return "cacheEntry [af=" + af.getFilename() + ", size="
721: + size + "]";
722: } else {
723: return "cacheEntry [size=" + size + "]";
724: }
725: }
726: }
727:
728: // An experiment: Try running cache misses in a separate stage
729: class CacheMissStage implements EventHandlerIF {
730: SinkIF mysink;
731:
732: public void init(ConfigDataIF config) throws Exception {
733: System.err.println("CacheMissStage: mysink " + mysink);
734: mysink = config.getStage().getSink();
735: }
736:
737: public void destroy() throws Exception {
738: }
739:
740: public void handleEvent(QueueElementIF event) {
741: // Actually run the enclosing class method, since
742: // a cache entry may be loaded by an earlier request
743: // in the pipeline.
744: System.err.println("CacheMissStage: handling " + event);
745: PageCacheSized.this .handleEvent(event);
746: }
747:
748: public void handleEvents(QueueElementIF items[]) {
749: for (int i = 0; i < items.length; i++) {
750: handleEvent(items[i]);
751: }
752: }
753:
754: }
755:
756: }
|