0001: /******************************************************************************
0002: * Cache.java
0003: * ****************************************************************************/package org.openlaszlo.cache;
0004:
0005: import java.net.*;
0006: import java.io.*;
0007: import java.util.Map;
0008: import java.util.Properties;
0009: import java.util.Iterator;
0010:
0011: import org.openlaszlo.xml.internal.XMLUtils;
0012: import org.openlaszlo.utils.FileUtils;
0013: import org.openlaszlo.utils.ChainedException;
0014: import org.openlaszlo.utils.LZHttpUtils;
0015: import org.openlaszlo.utils.MathUtils;
0016: import org.openlaszlo.server.LPS;
0017:
0018: import org.openlaszlo.data.Data;
0019: import org.openlaszlo.data.DataSource;
0020: import org.openlaszlo.data.DataSourceException;
0021:
0022: import org.apache.commons.collections.SequencedHashMap;
0023:
0024: import org.apache.log4j.*;
0025:
0026: import EDU.oswego.cs.dl.util.concurrent.ReentrantLock;
0027: import EDU.oswego.cs.dl.util.concurrent.Sync;
0028:
0029: /**
0030: * A base class for maintaining a disk-backed cache of requests.
0031: *
0032: * Cache has 2 sequenced hash map of Items. Each item uniquely
0033: * represents something to be cached (and transcoded). The
0034: * first map contains items that are "in memory". Items that
0035: * are "in memory" are also stored on disk. The second map
0036: * contains items that are only on disk.
0037: *
0038: * The cache chucks out (via LRU) items from each map when it is beyond
0039: * its maximum size for that map. Each time an item is requested,
0040: * it is moved to the front of the "in-memory" map.
0041: *
0042: * Each item has its own lock. When an item is requested,
0043: * we lock it and then go to the DataSource to see if
0044: * we need to refresh any copy of it we have.
0045: * When an item has it's size change or it marked dirty, it
0046: * is also marked as needing its size changed reckoned with the
0047: * cache sizes. Whenever the cache is locked and runs into
0048: * an item that needs reckoning, it updates the cache sizes
0049: * to reflect the item.
0050: *
0051: * The cache supports multiple encodings of individual items.
0052: *
0053: * @author <a href="mailto:bloch@laszlosystems.com">Eric Bloch</a>
0054: */
0055: public abstract class Cache {
0056:
0057: public final String DEFAULT_DISK_MAX = "500000000";
0058: public final String DEFAULT_MEM_MAX = "1000000";
0059: public final String DEFAULT_MEM_ITEM_MAX = "0";
0060:
0061: /** Logger. */
0062: private static Logger mLogger = Logger.getLogger(Cache.class);
0063: private static Logger mLockLogger = Logger
0064: .getLogger("trace.cache.lock");
0065:
0066: /** Name */
0067: private final String mName;
0068:
0069: /** See the constructor. */
0070: private File mCacheDirectory;
0071:
0072: /** Sequenced Maps of Cached Items keyed by request.
0073: * All access to these maps must must be synchronized
0074: */
0075: private SequencedHashMap mDiskMap = null;
0076: private SequencedHashMap mMemMap = null;
0077: private SequencedHashMap mActiveMap = null;
0078:
0079: /** Maxmimum size for the on-disk cache in bytes */
0080: private long mMaxDiskSize = 0;
0081:
0082: /** Maxmimum size for the in-memory cache in bytes */
0083: private long mMaxMemSize = 0;
0084:
0085: /** Maxmimum size for the in-memory cache item in bytes */
0086: private long mMaxMemItemSize = 0;
0087:
0088: /** Current size of the in-disk cache in bytes */
0089: private long mDiskSize = 0;
0090:
0091: /** Current size of the in-memory cache in bytes */
0092: private long mMemSize = 0;
0093:
0094: /** Current cache id */
0095: private long mCurName = 0;
0096: private Object mNameLock = new Object();
0097:
0098: /**
0099: * Creates a new <code>Cache</code> instance.
0100: *
0101: * @param name
0102: * @param cacheDirectory a <code>File</code> naming a directory
0103: * where cache files should be kept
0104: * @param props
0105: */
0106: public Cache(String name, File cacheDirectory, Properties props)
0107: throws IOException {
0108:
0109: mCacheDirectory = cacheDirectory;
0110: mName = name;
0111: cacheDirectory.mkdirs();
0112:
0113: String maxSize;
0114: String load;
0115: String mapsize;
0116:
0117: // TODO: 2004-09-07 bloch catch NumberParseException's here
0118: maxSize = props.getProperty(mName + ".disk.size",
0119: DEFAULT_DISK_MAX);
0120: if (maxSize != null) {
0121: mMaxDiskSize = Long.parseLong(maxSize);
0122: }
0123: maxSize = props.getProperty(mName + ".mem.size",
0124: DEFAULT_MEM_MAX);
0125: if (maxSize != null) {
0126: mMaxMemSize = Long.parseLong(maxSize);
0127: }
0128: maxSize = props.getProperty(mName + ".mem.item.max",
0129: DEFAULT_MEM_ITEM_MAX);
0130: if (maxSize != null) {
0131: mMaxMemItemSize = Long.parseLong(maxSize);
0132: }
0133:
0134: load = props.getProperty(mName + ".disk.load");
0135: if (load != null) {
0136: int l = Integer.parseInt(load);
0137: mapsize = props.getProperty(mName + ".disk.mapsize");
0138: if (mapsize != null) {
0139: float m = Float.parseFloat(mapsize);
0140: mDiskMap = new SequencedHashMap(l, m);
0141: } else {
0142: mDiskMap = new SequencedHashMap(l);
0143: }
0144: } else {
0145: mDiskMap = new SequencedHashMap();
0146: }
0147: load = props.getProperty(mName + ".mem.load");
0148: if (load != null) {
0149: int l = Integer.parseInt(load);
0150: mapsize = props.getProperty(mName + ".mem.mapsize");
0151: if (mapsize != null) {
0152: float m = Float.parseFloat(mapsize);
0153: mMemMap = new SequencedHashMap(l, m);
0154: } else {
0155: mMemMap = new SequencedHashMap(l);
0156: }
0157: } else {
0158: mMemMap = new SequencedHashMap();
0159: }
0160: load = props.getProperty(mName + ".active.load");
0161: if (load != null) {
0162: int l = Integer.parseInt(load);
0163: mapsize = props.getProperty(mName + ".active.mapsize");
0164: if (mapsize != null) {
0165: float m = Float.parseFloat(mapsize);
0166: mActiveMap = new SequencedHashMap(l, m);
0167: } else {
0168: mActiveMap = new SequencedHashMap(l);
0169: }
0170: } else {
0171: mActiveMap = new SequencedHashMap();
0172: }
0173:
0174: long t = System.currentTimeMillis();
0175: loadFromDirectory();
0176: // mLogger.debug(
0177: /* (non-Javadoc)
0178: * @i18n.test
0179: * @org-mes="loading " + p[0] + " took " + p[1] + " seconds"
0180: */
0181: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0182: // Cache.class.getName(),"051018-183", new Object[] {mName, MathUtils.formatDouble((System.currentTimeMillis() - t) / 1000.0, 2)})
0183: // );
0184: }
0185:
0186: /**
0187: * this routine is a place holder which can be overridden to hook into
0188: * the cache loading. It is used by the cm/CompilationManager to get
0189: * at the contents of the dependencyTracker as it is being loaded from
0190: * disk and modify various paths that might have changed since
0191: * prefetching.
0192: */
0193: protected void afterCacheRead(Object metaData) {
0194: } // override me
0195:
0196: /**
0197: * @return an item if it exists or null otherwise
0198: */
0199: protected synchronized Item getItem(Serializable key) {
0200: // mLogger.debug("getItem");
0201:
0202: Item item = (Item) mMemMap.get(key);
0203: if (item != null) {
0204: return item;
0205: }
0206: return (Item) mDiskMap.get(key);
0207: }
0208:
0209: /**
0210: * Find and optionally lock item in the cache that matches this key
0211: * If the item doesn't exist, create it. If the item
0212: * does exist, move it to the top of the LRU list by removing
0213: * and re-adding.
0214: * @return the locked item
0215: */
0216: protected synchronized Item findItem(Serializable key, String enc,
0217: boolean doLockAndLeaveActive) throws IOException {
0218:
0219: //mLogger.debug("findItem");
0220:
0221: SequencedHashMap curMap = mMemMap;
0222: SequencedHashMap newMap = mMemMap;
0223:
0224: boolean hasMemCache = (mMaxMemSize != 0);
0225:
0226: if (!hasMemCache) {
0227: newMap = mDiskMap;
0228: }
0229:
0230: Item item = (Item) curMap.get(key);
0231: if (item == null) {
0232: curMap = mDiskMap;
0233: item = (Item) curMap.get(key);
0234: if (item == null && doLockAndLeaveActive) {
0235: curMap = mActiveMap;
0236: item = (Item) curMap.get(key);
0237: // TODO: [2003-08-27 bloch] add assert in java 1.4 item.active()
0238: }
0239: }
0240:
0241: // New items default to the mem cache if it exists.
0242: // Note that some items that are too big may
0243: // be cached on disk (and not in mem) but may
0244: // temporarily be in the "mem cache" map.
0245: // This is confusing, but simple and safe to implement
0246: // w/out complicated locking. This is because
0247: // we don't want to lock the maps while we're
0248: // fetching the items and we won't know the size
0249: // until we fetch the item.
0250: //
0251: // TODO: [2003-09-04 bloch]
0252: // Note: this now only happens when doLockAndLeaveActive is
0253: // false. At some point we could rearchitect to remove
0254: // this wart
0255:
0256: try {
0257: if (item == null) {
0258: item = new Item(key, enc, hasMemCache);
0259: if (doLockAndLeaveActive) {
0260: item.lock();
0261: item.setActive(true);
0262: }
0263:
0264: // mLogger.debug(
0265: /* (non-Javadoc)
0266: * @i18n.test
0267: * @org-mes="Made new item for " + p[0]
0268: */
0269: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0270: // Cache.class.getName(),"051018-270", new Object[] {key.toString()})
0271: //);
0272: } else {
0273: if (doLockAndLeaveActive) {
0274: item.lock();
0275: item.setActive(true);
0276: }
0277: if (item.needsReckoning()) {
0278: // mLogger.debug(
0279: /* (non-Javadoc)
0280: * @i18n.test
0281: * @org-mes="Reckoning an old item for " + p[0]
0282: */
0283: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0284: // Cache.class.getName(),"051018-285", new Object[] {key.toString()})
0285: //);
0286: item.reckon();
0287: }
0288: if (item.dirty()) {
0289: // mLogger.debug(
0290: /* (non-Javadoc)
0291: * @i18n.test
0292: * @org-mes="Found a dirty item for " + p[0]
0293: */
0294: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0295: // Cache.class.getName(),"051018-296", new Object[] {key.toString()})
0296: //);
0297: if (doLockAndLeaveActive) {
0298: item.removeAndUnlock();
0299: } else {
0300: item.remove();
0301: }
0302: curMap.remove(key);
0303: item = new Item(key, enc, hasMemCache);
0304: if (doLockAndLeaveActive) {
0305: item.lock();
0306: item.setActive(true);
0307: }
0308: // mLogger.debug(
0309: /* (non-Javadoc)
0310: * @i18n.test
0311: * @org-mes="Removed and made new item for " + p[0]
0312: */
0313: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0314: // Cache.class.getName(),"051018-315", new Object[] {key.toString()})
0315: //);
0316: } else {
0317: // Remove the item and re-add it below
0318: // mLogger.debug(
0319: /* (non-Javadoc)
0320: * @i18n.test
0321: * @org-mes="Found old item for " + p[0]
0322: */
0323:
0324: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0325: // Cache.class.getName(),"051018-325", new Object[] {key.toString()})
0326: //);
0327: curMap.remove(key);
0328: if (curMap == mDiskMap) {
0329: if (newMap == mMemMap) {
0330: // Update sizes when we're moving from disk to mem
0331: long size = item.getSize();
0332: if (size <= mMaxMemItemSize
0333: || mMaxMemItemSize <= 0) {
0334: item.readIntoMemory();
0335: // Update sizes after we read into memory in case
0336: // the above read fails for some bizarro reason.
0337: mDiskSize -= size;
0338: mMemSize += size;
0339: } else {
0340: newMap = mDiskMap;
0341: }
0342: }
0343: }
0344: }
0345: }
0346:
0347: if (!doLockAndLeaveActive) {
0348: newMap.put(key, item);
0349: } else {
0350: mActiveMap.put(key, item);
0351: }
0352:
0353: } catch (IOException e) {
0354: // If we get any kind of exception, we better unlock the item
0355: // since no one will be able to unlock it.
0356: if (doLockAndLeaveActive && item != null) {
0357: item.unlock();
0358: }
0359: throw e;
0360: } catch (RuntimeException re) {
0361: // If we get any kind of exception, we better unlock the item
0362: // since no one will be able to unlock it.
0363: if (doLockAndLeaveActive && item != null) {
0364: item.unlock();
0365: }
0366: throw re;
0367: }
0368:
0369: return item;
0370: }
0371:
0372: /**
0373: * Reckon this item and update the cache.
0374: */
0375: protected synchronized void updateCache(Item item) {
0376:
0377: item.lock();
0378: item.reckon();
0379: item.unlock();
0380:
0381: // While we have the cache locked, update the maps
0382: updateMapsAndReckonItems(false /* leave at least one item in each map */);
0383: }
0384:
0385: /**
0386: * Reckon and deactivate this item and update the cache.
0387: */
0388: protected synchronized void updateCacheAndDeactivateItem(Item item) {
0389:
0390: item.lock();
0391: item.reckon();
0392:
0393: if (item.active()) {
0394: Object key = item.getKey();
0395: mActiveMap.remove(key);
0396: if (item.isInMemory()) {
0397: mMemMap.put(key, item);
0398: } else {
0399: mDiskMap.put(key, item);
0400: }
0401: item.setActive(false);
0402: }
0403:
0404: item.unlock();
0405:
0406: // While we have the cache locked, update the maps
0407: updateMapsAndReckonItems(true);
0408: }
0409:
0410: /**
0411: * Set the maximum size of the on-disk cache.
0412: * Can cause the cache to remove things.
0413: */
0414: public synchronized void setMaxDiskSize(long size) {
0415: if (size >= mMaxDiskSize || size == -1) {
0416: mMaxDiskSize = size;
0417: return;
0418: }
0419: mMaxDiskSize = size;
0420: updateMapsAndReckonItems(true);
0421: }
0422:
0423: /**
0424: * Get the maximum size of the on-disk cache cache
0425: */
0426: public synchronized long getMaxDiskSize() {
0427: return mMaxDiskSize;
0428: }
0429:
0430: /**
0431: * Get the current size of the on-disk cache
0432: */
0433: public synchronized long getDiskSize() {
0434: return mDiskSize;
0435: }
0436:
0437: /**
0438: * Set the maxmimum size of the in-memory cache.
0439: * Can cause the cache to remove things.
0440: */
0441: public synchronized void setMaxMemSize(long size) {
0442: if (size >= mMaxMemSize || size == -1) {
0443: mMaxMemSize = size;
0444: return;
0445: }
0446: mMaxMemSize = size;
0447: updateMapsAndReckonItems(true);
0448: }
0449:
0450: /**
0451: * Get the maxmimum size of the in-memory cache
0452: */
0453: public synchronized long getMaxMemSize() {
0454: return mMaxMemSize;
0455: }
0456:
0457: /**
0458: * Get the current size of the in-memory cache
0459: */
0460: public synchronized long getMemSize() {
0461: return mMemSize;
0462: }
0463:
0464: /**
0465: * Update the maps and remove items that
0466: * push us beyond the maximum size of the
0467: * cache. Never empty out the maps.
0468: * Always leave at least one item if clearAll is false.
0469: *
0470: * Also ignore items that are in progress.
0471: * @param clearAll if false, must leave one elt in each map
0472: */
0473: public synchronized void updateMapsAndReckonItems(boolean clearAll) {
0474:
0475: long removedBytes = 0;
0476:
0477: //mLogger.debug(
0478: /* (non-Javadoc)
0479: * @i18n.test
0480: * @org-mes=p[0] + " cache at " + p[1] + " bytes on-disk and " + p[2] + " bytes in-memory."
0481: */
0482: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0483: // Cache.class.getName(),"051018-482", new Object[] {mName, new Long(mDiskSize), new Long(mMemSize)})
0484: // );
0485: // Infinite mem size... (nothing will get reckoned, hope that's ok)
0486: if (mMaxMemSize == -1) {
0487: return;
0488: }
0489:
0490: int done = 0;
0491: if (!clearAll) {
0492: done = 1;
0493: }
0494:
0495: // If we have a mem cache, push items from memory to disk
0496: while (mMemSize > mMaxMemSize && mMemMap.size() > done) {
0497: Map.Entry first = mMemMap.getFirst();
0498: Object key = first.getKey();
0499: Item item = (Item) first.getValue();
0500: item.lock();
0501:
0502: if (item.needsReckoning()) {
0503: item.reckon();
0504: }
0505:
0506: if (!item.dirty()) {
0507: // Some items that were too large for memory
0508: // might be in this map, but are actually
0509: // accounted for in the disksize already.
0510: // We can skip these.
0511: if (item.mInfo.isInMemory()) {
0512: removedBytes = item.removeFromMemory();
0513: mMemSize -= removedBytes;
0514: mDiskSize += removedBytes;
0515: } else {
0516: // mLogger.debug(
0517: /* (non-Javadoc)
0518: * @i18n.test
0519: * @org-mes="mem item was already on disk"
0520: */
0521: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0522: // Cache.class.getName(),"051018-521")
0523: // );
0524: }
0525: // mLogger.debug(
0526: /* (non-Javadoc)
0527: * @i18n.test
0528: * @org-mes="Pushing item " + p[0] + " from mem to disk (" + p[1] + " bytes)"
0529: */
0530: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0531: // Cache.class.getName(),"051018-530", new Object[] {key, new Long(removedBytes)})
0532: // );
0533: mMemMap.remove(key);
0534: mDiskMap.put(key, item);
0535: } else {
0536: if (item.mInfo.isInMemory()) {
0537: mMemMap.remove(key);
0538: // mLogger.debug(
0539: /* (non-Javadoc)
0540: * @i18n.test
0541: * @org-mes="Removing dirty item " + p[0] + " from mem "
0542: */
0543: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0544: // Cache.class.getName(),"051018-543", new Object[] {key})
0545: //);
0546: }
0547: }
0548: item.unlock();
0549: }
0550:
0551: // Infinite disk size... (no disk items will get reckoned, hope that's ok)
0552: if (mMaxDiskSize == -1) {
0553: return;
0554: }
0555:
0556: while (mDiskSize > mMaxDiskSize && mDiskMap.size() > done) {
0557: Map.Entry first = mDiskMap.getFirst();
0558: Object key = first.getKey();
0559: Item item = (Item) first.getValue();
0560: item.lock();
0561:
0562: if (item.needsReckoning()) {
0563: item.reckon();
0564: }
0565:
0566: if (!item.dirty()) {
0567: removedBytes = item.getSize();
0568: item.removeAndUnlock();
0569: // mLogger.debug(
0570: /* (non-Javadoc)
0571: * @i18n.test
0572: * @org-mes="Removing item " + p[0] + " from disk (" + p[1] + " bytes)"
0573: */
0574: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0575: // Cache.class.getName(),"051018-574", new Object[] {key, new Long(removedBytes)})
0576: // );
0577: mDiskSize -= removedBytes;
0578: mDiskMap.remove(key);
0579: } else {
0580: mDiskMap.remove(key);
0581: // mLogger.debug(
0582: /* (non-Javadoc)
0583: * @i18n.test
0584: * @org-mes="Removing dirty item " + p[0] + " from disk "
0585: */
0586: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0587: // Cache.class.getName(),"051018-586", new Object[] {key})
0588: //);
0589: }
0590: }
0591: // mLogger.debug(
0592: /* (non-Javadoc)
0593: * @i18n.test
0594: * @org-mes=p[0] + " now at disk: " + p[1] + " bytes."
0595: */
0596: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0597: // Cache.class.getName(),"051018-596", new Object[] {mName, new Long(mDiskSize)})
0598: //);
0599: // mLogger.debug(
0600: /* (non-Javadoc)
0601: * @i18n.test
0602: * @org-mes=p[0] + " now at mem: " + p[1] + " bytes."
0603: */
0604: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0605: // Cache.class.getName(),"051018-604", new Object[] {mName, new Long(mMemSize)})
0606: //);
0607: }
0608:
0609: /**
0610: * Dump textual XML representation of cache to buf
0611: *
0612: * @param buf buffer to append
0613: * @param name name for XML element
0614: * @param details if true, dump details
0615: */
0616: public synchronized void dumpXML(StringBuffer buf, String name,
0617: boolean details) {
0618:
0619: buf.append("<" + name + " \n");
0620: buf.append("disk-total=\"");
0621: buf.append(getMaxDiskSize());
0622: buf.append("\" ");
0623: buf.append("disk-in-use=\"");
0624: buf.append(getDiskSize());
0625: buf.append("\" ");
0626: buf.append("mem-total=\"");
0627: buf.append(getMaxMemSize());
0628: buf.append("\" ");
0629: buf.append("mem-in-use=\"");
0630: buf.append(getMemSize());
0631:
0632: buf.append("\" ");
0633: buf.append("diskmap-entries=\"");
0634: buf.append(mDiskMap.size());
0635:
0636: buf.append("\" ");
0637: buf.append("memmap-entries=\"");
0638: buf.append(mMemMap.size());
0639:
0640: buf.append("\" ");
0641: buf.append("activemap-entries=\"");
0642: buf.append(mActiveMap.size());
0643:
0644: buf.append("\" ");
0645: buf.append(" >\n");
0646: if (details) {
0647: buf.append("<active>\n");
0648: dumpMap(buf, mActiveMap, false);
0649: buf.append("</active>\n");
0650: buf.append("<in-memory>\n");
0651: dumpMap(buf, mMemMap, true);
0652: buf.append("</in-memory>\n");
0653: buf.append("<disk>\n");
0654: dumpMap(buf, mDiskMap, true);
0655: buf.append("</disk>\n");
0656: }
0657: buf.append("</" + name + ">\n");
0658: }
0659:
0660: /**
0661: * @param buf buffer to append
0662: * @param map map to dump
0663: */
0664: private synchronized void dumpMap(StringBuffer buf,
0665: SequencedHashMap map, boolean lockItems) {
0666:
0667: Iterator iter = map.iterator();
0668: while (iter.hasNext()) {
0669: buf.append("<item ");
0670: Object key = iter.next();
0671: Item item = (Item) map.get(key);
0672: if (lockItems) {
0673: item.lock();
0674: }
0675: String keystring = key.toString();
0676: if (keystring.length() > 128) {
0677: keystring = keystring.substring(0, 127) + "...";
0678: }
0679: buf
0680: .append("key=\"" + XMLUtils.escapeXml(keystring)
0681: + "\" ");
0682: buf.append("in-memory=\""
0683: + (new Boolean(item.isInMemory())).toString()
0684: + "\" ");
0685: buf.append("dirty=\""
0686: + (new Boolean(item.dirty())).toString() + "\" ");
0687: buf.append("active=\""
0688: + (new Boolean(item.active())).toString() + "\" ");
0689: buf.append("needs-reckon=\""
0690: + (new Boolean(item.needsReckoning())).toString()
0691: + "\" ");
0692: buf.append("mem-to-reckon=\"" + item.memToReckon() + "\" ");
0693: buf.append("disk-to-reckon=\"" + item.diskToReckon()
0694: + "\" ");
0695: buf.append("size=\"" + item.getSize() + "\" ");
0696: buf.append("key-size=\"" + item.getKeySize() + "\" ");
0697: buf.append("path-name=\"" + item.getPathName() + "\" ");
0698: buf.append("info-name=\"" + item.getInfoName() + "\" ");
0699: long lm = item.getInfo().getLastModified();
0700: buf.append("last-modified=\"" + lm + "\" ");
0701: buf.append("last-modified-gmt=\""
0702: + LZHttpUtils.getDateString(lm) + "\" ");
0703: buf.append("/>\n");
0704: if (lockItems) {
0705: item.unlock();
0706: }
0707: }
0708: }
0709:
0710: /**
0711: * Load the state of the cache from what is in the cache
0712: * Then update the cache log itself to represent
0713: * what we now know.
0714: *
0715: * FIXME: [2003-03-11] bloch the last-used time is lost when
0716: * we shutdown; this means the LRU ordering isn't preserved on
0717: * restart. We should add lastUsed times to the cachedInfo.
0718: */
0719: private synchronized void loadFromDirectory() throws IOException {
0720:
0721: String[] fileNames = mCacheDirectory.list();
0722:
0723: for (int i = 0; i < fileNames.length; i++) {
0724: if (!fileNames[i].endsWith(".dat"))
0725: continue;
0726:
0727: int x = fileNames[i].indexOf(".dat");
0728: String name = fileNames[i].substring(0, x);
0729: long n;
0730: try {
0731: n = Long.parseLong(name);
0732: if (n > mCurName) {
0733: mCurName = n;
0734: }
0735: } catch (NumberFormatException ne) {
0736: mLogger.warn(
0737: /* (non-Javadoc)
0738: * @i18n.test
0739: * @org-mes="ignoring a strange .dat file in the cache named: " + p[0]
0740: */
0741: org.openlaszlo.i18n.LaszloMessages.getMessage(
0742: Cache.class.getName(), "051018-717",
0743: new Object[] { fileNames[i] }));
0744: continue;
0745: }
0746:
0747: Item item = null;
0748: File file = new File(mCacheDirectory + File.separator
0749: + fileNames[i]);
0750: try {
0751: item = new Item(file);
0752: } catch (Throwable e) {
0753: mLogger.error(
0754: /* (non-Javadoc)
0755: * @i18n.test
0756: * @org-mes="can't load cache file: " + p[0]
0757: */
0758: org.openlaszlo.i18n.LaszloMessages.getMessage(
0759: Cache.class.getName(), "051018-733",
0760: new Object[] { fileNames[i] }));
0761:
0762: mLogger.error(
0763: /* (non-Javadoc)
0764: * @i18n.test
0765: * @org-mes="exception: "
0766: */
0767: org.openlaszlo.i18n.LaszloMessages.getMessage(
0768: Cache.class.getName(), "051018-742"), e);
0769: mLogger.error(
0770: /* (non-Javadoc)
0771: * @i18n.test
0772: * @org-mes="deleting cache files for : " + p[0]
0773: */
0774: org.openlaszlo.i18n.LaszloMessages.getMessage(
0775: Cache.class.getName(), "051018-750",
0776: new Object[] { fileNames[i] }));
0777: // Deletes name* files.
0778: if (!FileUtils.deleteFilesStartingWith(mCacheDirectory,
0779: name)) {
0780: mLogger.error(
0781: /* (non-Javadoc)
0782: * @i18n.test
0783: * @org-mes="couldn't delete some files with name: " + p[0]
0784: */
0785: org.openlaszlo.i18n.LaszloMessages.getMessage(
0786: Cache.class.getName(), "051018-760",
0787: new Object[] { fileNames[i] }));
0788: }
0789: continue;
0790: }
0791:
0792: Object key = item.getKey();
0793: if (item.isInMemory()) {
0794: mMemMap.put(key, item);
0795: mMemSize += item.getSize();
0796: } else {
0797: mDiskMap.put(key, item);
0798: mDiskSize += item.getSize();
0799: }
0800:
0801: String keystring = key.toString();
0802: if (keystring.length() > 128) {
0803: keystring = keystring.substring(0, 127) + "...";
0804: }
0805: // mLogger.debug(
0806: /* (non-Javadoc)
0807: * @i18n.test
0808: * @org-mes="loaded cached item for " + p[0]
0809: */
0810: // org.openlaszlo.i18n.LaszloMessages.getMessage(
0811: // Cache.class.getName(),"051018-785", new Object[] {keystring})
0812: // );
0813: }
0814:
0815: updateMapsAndReckonItems(true);
0816:
0817: }
0818:
0819: /**
0820: * Clear the cache
0821: */
0822: public synchronized boolean clearCache() {
0823: mLogger.info(
0824: /* (non-Javadoc)
0825: * @i18n.test
0826: * @org-mes="Clearing " + p[0]
0827: */
0828: org.openlaszlo.i18n.LaszloMessages.getMessage(Cache.class
0829: .getName(), "051018-804", new Object[] { mName }));
0830:
0831: boolean ok1 = clearMap(mActiveMap);
0832: boolean ok2 = clearMap(mDiskMap);
0833: boolean ok3 = clearMap(mMemMap);
0834: boolean ok4 = true;
0835:
0836: mMemSize = 0;
0837: mDiskSize = 0;
0838:
0839: ok4 = FileUtils.deleteFiles(mCacheDirectory);
0840:
0841: return ok1 && ok2 && ok3 && ok4;
0842: }
0843:
0844: /**
0845: * Clear entries from given map
0846: * @param map to clear
0847: */
0848: private boolean clearMap(SequencedHashMap map) {
0849:
0850: boolean ok = true;
0851: while (map.size() > 0) {
0852: Map.Entry first = map.getFirst();
0853: Object key = first.getKey();
0854: Item item = (Item) first.getValue();
0855: item.lock();
0856: if (!item.removeAndUnlock()) {
0857: ok = false;
0858: }
0859: map.remove(key);
0860: }
0861:
0862: return ok;
0863: }
0864:
0865: /**
0866: * Return a unique name
0867: *
0868: * Since time always moves fwd, we shouldn't
0869: * have to worry about using names we've
0870: * previously "allocated".
0871: *
0872: * FIXME: [2003-02-25 bloch] if someone sets the clock
0873: * back, we need to clear the cache; we could easily
0874: * detect this using timestamps on files in the cache.
0875: */
0876: private long nextUniqueName() {
0877:
0878: long name;
0879:
0880: synchronized (mNameLock) {
0881: name = System.currentTimeMillis();
0882: if (name <= mCurName) {
0883: name = mCurName + 1;
0884: }
0885: mCurName = name;
0886: }
0887:
0888: return name;
0889: }
0890:
0891: /**
0892: * A class that represents an item in the Cache.
0893: *
0894: * @author <a href="mailto:bloch@laszlosystems.com">Eric Bloch</a>
0895: */
0896: protected class Item implements Serializable {
0897:
0898: transient private Sync mLock;
0899: transient private boolean mDirty = false;
0900: transient private boolean mNeedsReckoning = false;
0901: /** true if the item is actively being updated */
0902: transient private boolean mActive = false;
0903:
0904: transient private byte mBuffer[];
0905:
0906: transient private long mMemSizeToReckon = 0;
0907: transient private long mDiskSizeToReckon = 0;
0908:
0909: /**
0910: * Information about this cached item.
0911: */
0912: CachedInfo mInfo = null;
0913:
0914: /**
0915: * Construct an Item based on this request
0916: * and lock it for write.
0917: */
0918: public Item(Serializable key, String enc, boolean inMemory) {
0919:
0920: mLock = new ReentrantLock();
0921: mInfo = new CachedInfo(key, enc, inMemory, nextUniqueName());
0922: mBuffer = null;
0923: }
0924:
0925: /**
0926: * @return true if item is dirty
0927: */
0928: public boolean dirty() {
0929: return mDirty;
0930: }
0931:
0932: /**
0933: * @return true if item is active
0934: */
0935: public boolean active() {
0936: return mActive;
0937: }
0938:
0939: /** set if item is active */
0940: public void setActive(boolean a) {
0941: mActive = a;
0942: }
0943:
0944: /**
0945: * @return true if item is dirty
0946: */
0947: public boolean needsReckoning() {
0948: return mNeedsReckoning;
0949: }
0950:
0951: /**
0952: * @return mem to reckon
0953: */
0954: public long memToReckon() {
0955: return mMemSizeToReckon;
0956: }
0957:
0958: /**
0959: * @return disk to reckon
0960: */
0961: public long diskToReckon() {
0962: return mDiskSizeToReckon;
0963: }
0964:
0965: /**
0966: * Update cache sizes with size from item
0967: * that haven't been reckonned yet.
0968: * Must have item and entire cache locked to call this
0969: */
0970: public void reckon() {
0971: mMemSize += mMemSizeToReckon;
0972: mDiskSize += mDiskSizeToReckon;
0973:
0974: mDiskSizeToReckon = 0;
0975: mMemSizeToReckon = 0;
0976:
0977: mNeedsReckoning = false;
0978: // mLogger.debug("reckoned an item");
0979: }
0980:
0981: /**
0982: * Item should be locked during this call
0983: */
0984: public void readIntoMemory() throws IOException {
0985: FileInputStream fis = null;
0986: try {
0987: String p = getPathName();
0988: fis = new FileInputStream(p);
0989: mBuffer = new byte[(int) mInfo.getSize()];
0990: fis.read(mBuffer, 0, (int) mInfo.getSize());
0991: mInfo.setInMemory(true);
0992: } catch (IOException e) {
0993: markDirty();
0994: throw e;
0995: } finally {
0996: FileUtils.close(fis);
0997: }
0998: }
0999:
1000: /**
1001: * Item should be locked during this call
1002: * @return size of item
1003: * caller is responsible for updating cache sizes
1004: */
1005: public long removeFromMemory() {
1006: mBuffer = null;
1007: mInfo.setInMemory(false);
1008: return mInfo.getSize();
1009: }
1010:
1011: /**
1012: * Construct an Item based on the contents of this file
1013: */
1014: public Item(File f) throws IOException, OptionalDataException,
1015: StreamCorruptedException, ClassNotFoundException {
1016: mLock = new ReentrantLock();
1017:
1018: InputStream in = null;
1019: try {
1020: in = new BufferedInputStream(new FileInputStream(f));
1021: ObjectInputStream istr = new ObjectInputStream(in);
1022: mInfo = (CachedInfo) istr.readObject();
1023: // after reading the object, call our override routine
1024: afterCacheRead(mInfo.getMetaData());
1025: } finally {
1026: FileUtils.close(in);
1027: }
1028:
1029: // FIXME: [2003-08-15 pkang] isInMemory() will always evaluate to
1030: // true for items that are read from disk. This is because those
1031: // items are written out only when they're fetched and, by default,
1032: // they are created to exist in memory. Since it's better that we
1033: // don't read every item into memory during start-up, isInMemory is
1034: // set to false here.
1035:
1036: mInfo.setInMemory(false);
1037:
1038: // if (mInfo.isInMemory()) {
1039: // readIntoMemory();
1040: // }
1041: }
1042:
1043: /**
1044: * Lock the item
1045: */
1046: public void lock() {
1047: mLockLogger.debug(
1048: /* (non-Javadoc)
1049: * @i18n.test
1050: * @org-mes="acquiring lock: " + p[0]
1051: */
1052: org.openlaszlo.i18n.LaszloMessages.getMessage(Cache.class
1053: .getName(), "051018-1032",
1054: new Object[] { getKey() }));
1055: try {
1056: mLock.acquire();
1057: } catch (InterruptedException e) {
1058: throw new ChainedException(e);
1059: }
1060: mLockLogger.debug(
1061: /* (non-Javadoc)
1062: * @i18n.test
1063: * @org-mes="acquired lock: " + p[0]
1064: */
1065: org.openlaszlo.i18n.LaszloMessages.getMessage(Cache.class
1066: .getName(), "051018-1045",
1067: new Object[] { getKey() }));
1068: }
1069:
1070: /*
1071: * Done with item
1072: */
1073: public void unlock() {
1074: mLockLogger.debug(
1075: /* (non-Javadoc)
1076: * @i18n.test
1077: * @org-mes="releasing read lock: " + p[0]
1078: */
1079: org.openlaszlo.i18n.LaszloMessages.getMessage(Cache.class
1080: .getName(), "051018-1059",
1081: new Object[] { getKey() }));
1082: mLock.release();
1083: mLockLogger.debug(
1084: /* (non-Javadoc)
1085: * @i18n.test
1086: * @org-mes="released read lock: " + p[0]
1087: */
1088: org.openlaszlo.i18n.LaszloMessages.getMessage(Cache.class
1089: .getName(), "051018-1068",
1090: new Object[] { getKey() }));
1091: }
1092:
1093: /**
1094: * @return the key for this item.
1095: */
1096: public Serializable getKey() {
1097: if (mInfo != null)
1098: return mInfo.getKey();
1099: else
1100: return "uninitialized item";
1101: }
1102:
1103: /**
1104: * @return whether this item is in memory or not
1105: */
1106: public boolean isInMemory() {
1107: if (mInfo != null)
1108: return mInfo.isInMemory();
1109: else
1110: return false;
1111: }
1112:
1113: /**
1114: * Return the cached size of this item in bytes
1115: * Must have the item read locked for this.
1116: */
1117: public long getSize() {
1118: return mInfo.getSize();
1119: }
1120:
1121: /**
1122: * Return the size of this items key in bytes.
1123: * May return -1 if the size is unknown/expensive to compute.
1124: * Must have the item read locked for this.
1125: */
1126: public long getKeySize() {
1127: return mInfo.getKeySize();
1128: }
1129:
1130: /**
1131: * @return item's info
1132: */
1133: public CachedInfo getInfo() {
1134: return mInfo;
1135: }
1136:
1137: /**
1138: * Return the path name of the cached version of this item
1139: */
1140: public String getPathName() {
1141: // FIXME: [2003-03-03 bloch] unhardcode gz extension someday
1142: return mCacheDirectory.getAbsolutePath() + File.separator
1143: + mInfo.getName() + ".swf"
1144: + (mInfo.getEncoding() == null ? "" : ".gz");
1145: }
1146:
1147: /**
1148: * Return the path name of the cached info for this item
1149: */
1150: public String getInfoName() {
1151: return mCacheDirectory.getAbsolutePath() + File.separator
1152: + mInfo.getName() + ".dat";
1153: }
1154:
1155: /**
1156: * Encoding the given item using the requested encoding.
1157: * Encode to a temporary file and then rename.
1158: *
1159: * @return the size in bytes of the encoded file
1160: * caller is responsible for updating item and cache sizes
1161: */
1162: public long encode(String enc) throws IOException {
1163:
1164: File f = new File(getPathName());
1165: File tempDir = f.getParentFile();
1166: File tempFile = File.createTempFile("lzuc-", null, tempDir);
1167: // mLogger.debug(
1168: /* (non-Javadoc)
1169: * @i18n.test
1170: * @org-mes="Temporary file is " + p[0]
1171: */
1172: // org.openlaszlo.i18n.LaszloMessages.getMessage(
1173: // Cache.class.getName(),"051018-1152", new Object[] {tempFile.getAbsolutePath()})
1174: //);
1175: FileUtils.encode(f, tempFile, enc);
1176: long size = tempFile.length();
1177: if (!f.delete()) {
1178: mLogger.warn(
1179: /* (non-Javadoc)
1180: * @i18n.test
1181: * @org-mes="Can't delete " + p[0]
1182: */
1183: org.openlaszlo.i18n.LaszloMessages.getMessage(
1184: Cache.class.getName(), "051018-1163",
1185: new Object[] { f.getAbsolutePath() }));
1186: }
1187: if (!tempFile.renameTo(f)) {
1188: mLogger.error(
1189: /* (non-Javadoc)
1190: * @i18n.test
1191: * @org-mes="Can't rename temporary file to " + p[0]
1192: */
1193: org.openlaszlo.i18n.LaszloMessages.getMessage(
1194: Cache.class.getName(), "051018-1173",
1195: new Object[] { f.getAbsolutePath() }));
1196: }
1197: return size;
1198: }
1199:
1200: /**
1201: * Mark the item as dirty
1202: */
1203: public void markDirty() {
1204: if (mDirty) {
1205: return;
1206: }
1207: // Removed the cache file and any buffer and mark the item
1208: // as dirty
1209: File f = new File(getPathName());
1210: if (f.exists() && !f.delete()) {
1211: mLogger.warn(
1212: /* (non-Javadoc)
1213: * @i18n.test
1214: * @org-mes="Can't delete " + p[0]
1215: */
1216: org.openlaszlo.i18n.LaszloMessages.getMessage(
1217: Cache.class.getName(), "051018-1163",
1218: new Object[] { getPathName() }));
1219: }
1220: f = new File(getInfoName());
1221: if (f.exists() && !f.delete()) {
1222: mLogger.warn(
1223: /* (non-Javadoc)
1224: * @i18n.test
1225: * @org-mes="Can't delete " + p[0]
1226: */
1227: org.openlaszlo.i18n.LaszloMessages.getMessage(
1228: Cache.class.getName(), "051018-1163",
1229: new Object[] { getInfoName() }));
1230: }
1231: mDirty = true;
1232: mBuffer = null;
1233: mNeedsReckoning = true;
1234: // mLogger.debug(
1235: /* (non-Javadoc)
1236: * @i18n.test
1237: * @org-mes="Marking item dirty: " + p[0]
1238: */
1239: // org.openlaszlo.i18n.LaszloMessages.getMessage(
1240: // Cache.class.getName(),"051018-1219", new Object[] {mInfo.getKey()})
1241: //);
1242: if (mInfo.isInMemory()) {
1243: mMemSizeToReckon -= mInfo.getSize();
1244: } else {
1245: mDiskSizeToReckon -= mInfo.getSize();
1246: }
1247: // mLogger.debug(
1248: /* (non-Javadoc)
1249: * @i18n.test
1250: * @org-mes="disk to reckon : " + p[0] + ", mem to reckon : " + p[1]
1251: */
1252: // org.openlaszlo.i18n.LaszloMessages.getMessage(
1253: // Cache.class.getName(),"051018-1232", new Object[] {new Long(mDiskSizeToReckon), new Long(mMemSizeToReckon)})
1254: // );
1255: mInfo.setSize(0);
1256: }
1257:
1258: /**
1259: * @return true if this item is still valid
1260: * given the current data
1261: */
1262: public boolean validForData(Data d) {
1263: // TODO: [2003-01-14 bloch] Make sure the cached
1264: // ETag, Content-Length, and Content-MD5 are ok.
1265: // Need to add these to the CachedInfo class and put
1266: // logic here to deal with missing/present values.
1267: // Will need to get the right interface between
1268: // Item/CachedInfo/Data to make this work.
1269: //
1270: //if (hdr != null) {
1271: //}
1272:
1273: return true;
1274: }
1275:
1276: /**
1277: * Remove the cached version of this item and then unlock it.
1278: * Must have item locked for this. Caller is responsible for updatng cache sizes
1279: * @return true if there were no problems
1280: */
1281: public boolean removeAndUnlock() {
1282: boolean ok = remove();
1283: unlock();
1284: return ok;
1285: }
1286:
1287: /**
1288: * Remove the cached version of this item
1289: * @return true if there were no problems
1290: * Caller is responsible for updatng cache sizes.
1291: */
1292: private boolean remove() {
1293:
1294: boolean ok = true;
1295: // mLogger.debug(
1296: /* (non-Javadoc)
1297: * @i18n.test
1298: * @org-mes="removing item for " + p[0] + " (" + p[1] + " - " + p[2] + " bytes) ."
1299: */
1300: // org.openlaszlo.i18n.LaszloMessages.getMessage(
1301: // Cache.class.getName(),"051018-1280", new Object[] {mInfo.getKey(), getPathName(), new Long(getSize())})
1302: // );
1303: File f = new File(getPathName());
1304: if (f.exists() && !f.delete()) {
1305: mLogger.warn(
1306: /* (non-Javadoc)
1307: * @i18n.test
1308: * @org-mes="Can't delete " + p[0]
1309: */
1310: org.openlaszlo.i18n.LaszloMessages.getMessage(
1311: Cache.class.getName(), "051018-1163",
1312: new Object[] { getPathName() }));
1313: ok = false;
1314: }
1315: f = new File(getInfoName());
1316: if (f.exists() && !f.delete()) {
1317: mLogger.warn(
1318: /* (non-Javadoc)
1319: * @i18n.test
1320: * @org-mes="Can't delete " + p[0]
1321: */
1322: org.openlaszlo.i18n.LaszloMessages.getMessage(
1323: Cache.class.getName(), "051018-1163",
1324: new Object[] { getInfoName() }));
1325: ok = false;
1326: }
1327:
1328: return ok;
1329: }
1330:
1331: /**
1332: * @return stream depending on whether we're in mem
1333: * or on disk
1334: */
1335: public InputStream getStream() {
1336: if (mInfo.isInMemory()) {
1337: return new ByteArrayInputStream(mBuffer);
1338: } else {
1339: try {
1340: return new FileInputStream(getPathName());
1341: } catch (FileNotFoundException e) {
1342: throw new ChainedException(e.getMessage());
1343: }
1344: }
1345: }
1346:
1347: /**
1348: * @return returns raw byte-array data.
1349: * This should only be used to retrieve immutable data! If the caller modifies this byte array,
1350: * it could cause trouble.
1351: */
1352: public byte[] getRawByteArray() {
1353: try {
1354: if (!mInfo.isInMemory()) {
1355: readIntoMemory();
1356: }
1357: if (mInfo.isInMemory()) {
1358: return mBuffer;
1359: } else {
1360: ByteArrayOutputStream bos = null;
1361: FileInputStream instream = null;
1362: try {
1363: // If it's still not in memory, maybe it was too big, so let's copy it into
1364: // a new byte array and return that.
1365: bos = new ByteArrayOutputStream();
1366: instream = new FileInputStream(getPathName());
1367: FileUtils.send(instream, bos);
1368: return bos.toByteArray();
1369: } catch (FileNotFoundException e) {
1370: throw new ChainedException(e.getMessage());
1371: } finally {
1372: if (bos != null) {
1373: bos.close();
1374: }
1375: if (instream != null) {
1376: instream.close();
1377: }
1378: }
1379:
1380: }
1381: } catch (IOException e) {
1382: throw new ChainedException(e.getMessage());
1383: }
1384: }
1385:
1386: /**
1387: * @return the file object
1388: */
1389: public File getFile() {
1390: return new File(getPathName());
1391: }
1392:
1393: /**
1394: * Get the meta data for the current item
1395: */
1396: public Serializable getMetaData() {
1397: return mInfo.getMetaData();
1398: }
1399:
1400: /**
1401: * Update the item from the given input stream
1402: * and meta data and mark the item as clean
1403: * (not dirty).
1404: *
1405: * @param in input stream; ignored if null
1406: * @param metaData
1407: */
1408: public void update(InputStream in, Serializable metaData)
1409: throws IOException {
1410:
1411: // mLogger.debug(
1412: /* (non-Javadoc)
1413: * @i18n.test
1414: * @org-mes="updating item stream and metadata"
1415: */
1416: // org.openlaszlo.i18n.LaszloMessages.getMessage(
1417: // Cache.class.getName(),"051018-1399")
1418: //);
1419: update(metaData);
1420:
1421: if (in != null) {
1422: update(in);
1423: }
1424:
1425: markClean();
1426: }
1427:
1428: /**
1429: * Update the item from the given input stream
1430: *
1431: * @param in input stream
1432: */
1433: public void update(InputStream in) throws IOException {
1434:
1435: FileOutputStream out = null;
1436:
1437: try {
1438:
1439: // mLogger.debug(
1440: /* (non-Javadoc)
1441: * @i18n.test
1442: * @org-mes="updating item from stream"
1443: */
1444: // org.openlaszlo.i18n.LaszloMessages.getMessage(
1445: // Cache.class.getName(),"051018-1428")
1446: //);
1447: out = new FileOutputStream(getPathName());
1448: long size = FileUtils.send(in, out);
1449: FileUtils.close(out);
1450:
1451: String enc = mInfo.getEncoding();
1452: if (enc != null && !enc.equals("")) {
1453: size = encode(enc);
1454: }
1455:
1456: long oldSize = mInfo.getSize();
1457: mInfo.setSize(size);
1458: mNeedsReckoning = true;
1459:
1460: // Remove size from any old item
1461: if (mInfo.isInMemory()) {
1462: mMemSizeToReckon -= oldSize;
1463: } else {
1464: mDiskSizeToReckon -= oldSize;
1465: }
1466:
1467: // Check to see if we're too big for memory
1468: if (mInfo.isInMemory() && mMaxMemItemSize > 0
1469: && size > mMaxMemItemSize) {
1470: mLogger.debug("this item too big for memory ");
1471: mInfo.setInMemory(false);
1472: }
1473:
1474: if (mInfo.isInMemory()) {
1475: mLogger.debug("reading item into memory ");
1476: readIntoMemory();
1477: mMemSizeToReckon += size;
1478: } else {
1479: mDiskSizeToReckon += size;
1480: }
1481:
1482: } finally {
1483: FileUtils.close(out);
1484: }
1485: }
1486:
1487: /**
1488: * Mark the item as clean
1489: */
1490: public void markClean() {
1491: mDirty = false;
1492: }
1493:
1494: /**
1495: * Update the item's meta data.
1496: *
1497: * @param metaData
1498: */
1499: public void update(Serializable metaData) throws IOException {
1500:
1501: mInfo.setMetaData(metaData);
1502: updateInfo();
1503: }
1504:
1505: /**
1506: * Update the item's info
1507: */
1508: public void updateInfo() throws IOException {
1509:
1510: FileOutputStream out = null;
1511:
1512: try {
1513:
1514: // Write out the info (.dat) file
1515: out = new FileOutputStream(getInfoName());
1516: ObjectOutputStream ostr = new ObjectOutputStream(out);
1517: ostr.writeObject(mInfo);
1518: ostr.flush();
1519: ostr.close();
1520:
1521: } finally {
1522: FileUtils.close(out);
1523: }
1524: }
1525: }
1526: }
|