001: /*
002: * ============================================================================
003: * GNU Lesser General Public License
004: * ============================================================================
005: *
006: * JasperReports - Free Java report-generating library.
007: * Copyright (C) 2005 Works, Inc. http://www.works.com/
008: *
009: * This library is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU Lesser General Public
011: * License as published by the Free Software Foundation; either
012: * version 2.1 of the License, or (at your option) any later version.
013: *
014: * This library is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: * Lesser General Public License for more details.
018: *
019: * You should have received a copy of the GNU Lesser General Public
020: * License along with this library; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
022: *
023: * Works, Inc.
024: * 6034 West Courtyard Drive
025: * Suite 210
026: * Austin, TX 78730-5032
027: * USA
028: * http://www.works.com/
029: */
030:
031: /*
032: * Licensed to JasperSoft Corporation under a Contributer Agreement
033: */
034: package net.sf.jasperreports.engine.fill;
035:
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.io.ObjectInputStream;
039: import java.io.ObjectOutputStream;
040: import java.io.ObjectStreamClass;
041: import java.io.OutputStream;
042: import java.lang.ref.Reference;
043: import java.lang.ref.ReferenceQueue;
044: import java.lang.ref.WeakReference;
045: import java.util.ArrayList;
046: import java.util.HashMap;
047: import java.util.Iterator;
048: import java.util.List;
049: import java.util.Map;
050: import java.util.Map.Entry;
051:
052: import net.sf.jasperreports.engine.JRConstants;
053: import net.sf.jasperreports.engine.JRRuntimeException;
054: import net.sf.jasperreports.engine.JRVirtualizable;
055: import net.sf.jasperreports.engine.JRVirtualizer;
056:
057: import org.apache.commons.collections.LRUMap;
058: import org.apache.commons.collections.ReferenceMap;
059: import org.apache.commons.logging.Log;
060: import org.apache.commons.logging.LogFactory;
061:
062: /**
063: * Abstract base for LRU and serialization based virtualizer
064: *
065: * @author John Bindel
066: * @version $Id: JRAbstractLRUVirtualizer.java 1797 2007-07-30 09:38:35Z teodord $
067: */
068: public abstract class JRAbstractLRUVirtualizer implements JRVirtualizer {
069: private static final Log log = LogFactory
070: .getLog(JRAbstractLRUVirtualizer.class);
071:
072: protected static class CacheReference extends WeakReference {
073: private final String id;
074:
075: public CacheReference(JRVirtualizable o, ReferenceQueue queue) {
076: super (o, queue);
077: id = o.getUID();
078: }
079:
080: public String getId() {
081: return id;
082: }
083: }
084:
085: /**
086: * This class keeps track of how many objects are currently in memory, and
087: * when there are too many, it pushes the last touched one to disk.
088: */
089: protected class Cache {
090: protected class LRUScanMap extends LRUMap {
091: private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;
092:
093: public LRUScanMap(int maxSize) {
094: super (maxSize);
095: }
096:
097: protected void removeLRU() {
098: Map.Entry entry = getFirst();
099: boolean found = isRemovable(entry);
100: if (!found) {
101: Iterator entriesIt = entrySet().iterator();
102: entriesIt.next(); //skipping the first, which is already checked
103: while (!found && entriesIt.hasNext()) {
104: entry = (Entry) entriesIt.next();
105: found = isRemovable(entry);
106: }
107: }
108:
109: if (!found) {
110: throw new JRRuntimeException(
111: "The virtualizer is used by more contexts than its in-memory cache size "
112: + getMaximumSize());
113: }
114:
115: Object key = entry.getKey();
116: Object value = entry.getValue();
117: this .remove(key);
118: processRemovedLRU(key, value);
119: }
120:
121: protected boolean isRemovable(Map.Entry entry) {
122: JRVirtualizable value = getMapValue(entry.getValue());
123: return value == null
124: || !lastObjectSet.containsKey(value);
125: }
126:
127: protected void processRemovedLRU(Object key, Object value) {
128: JRVirtualizable o = getMapValue(value);
129: if (o != null) {
130: virtualizeData(o);
131: }
132: }
133: }
134:
135: private final ReferenceQueue refQueue;
136: private final LRUScanMap map;
137:
138: Cache(int maxSize) {
139: map = new LRUScanMap(maxSize);
140: refQueue = new ReferenceQueue();
141: }
142:
143: protected JRVirtualizable getMapValue(Object val) {
144: JRVirtualizable o;
145: if (val == null) {
146: o = null;
147: } else {
148: Reference ref = (Reference) val;
149: if (ref.isEnqueued()) {
150: o = null;
151: } else {
152: o = (JRVirtualizable) ref.get();
153: }
154: }
155: return o;
156: }
157:
158: protected Object toMapValue(JRVirtualizable val) {
159: return val == null ? null : new CacheReference(val,
160: refQueue);
161: }
162:
163: protected void purge() {
164: CacheReference ref;
165: while ((ref = (CacheReference) refQueue.poll()) != null) {
166: map.remove(ref.getId());
167: }
168: }
169:
170: public JRVirtualizable get(String id) {
171: purge();
172:
173: return getMapValue(map.get(id));
174: }
175:
176: public JRVirtualizable put(String id, JRVirtualizable o) {
177: purge();
178:
179: return getMapValue(map.put(id, toMapValue(o)));
180: }
181:
182: public JRVirtualizable remove(String id) {
183: purge();
184:
185: return getMapValue(map.remove(id));
186: }
187:
188: public Iterator idIterator() {
189: purge();
190:
191: final Iterator valsIt = map.values().iterator();
192: return new Iterator() {
193: public boolean hasNext() {
194: return valsIt.hasNext();
195: }
196:
197: public Object next() {
198: CacheReference ref = (CacheReference) valsIt.next();
199: return ref.getId();
200: }
201:
202: public void remove() {
203: valsIt.remove();
204: }
205: };
206: }
207: }
208:
209: protected static final int CLASSLOADER_IDX_NOT_SET = -1;
210:
211: protected static boolean isAncestorClassLoader(ClassLoader loader) {
212: for (ClassLoader ancestor = JRAbstractLRUVirtualizer.class
213: .getClassLoader(); ancestor != null; ancestor = ancestor
214: .getParent()) {
215: if (ancestor.equals(loader)) {
216: return true;
217: }
218: }
219: return false;
220: }
221:
222: protected final Map classLoadersIndexes = new HashMap();
223: protected final List classLoadersList = new ArrayList();
224:
225: protected class ClassLoaderAnnotationObjectOutputStream extends
226: ObjectOutputStream {
227: public ClassLoaderAnnotationObjectOutputStream(OutputStream out)
228: throws IOException {
229: super (out);
230: }
231:
232: protected void annotateClass(Class clazz) throws IOException {
233: super .annotateClass(clazz);
234:
235: ClassLoader classLoader = clazz.getClassLoader();
236: int loaderIdx;
237: if (clazz.isPrimitive() || classLoader == null
238: || isAncestorClassLoader(classLoader)) {
239: loaderIdx = CLASSLOADER_IDX_NOT_SET;
240: } else {
241: Integer idx = (Integer) classLoadersIndexes
242: .get(classLoader);
243: if (idx == null) {
244: idx = new Integer(classLoadersList.size());
245: classLoadersIndexes.put(classLoader, idx);
246: classLoadersList.add(classLoader);
247: }
248: loaderIdx = idx.intValue();
249: }
250:
251: writeShort(loaderIdx);
252: }
253: }
254:
255: protected class ClassLoaderAnnotationObjectInputStream extends
256: ObjectInputStream {
257: public ClassLoaderAnnotationObjectInputStream(InputStream in)
258: throws IOException {
259: super (in);
260: }
261:
262: protected Class resolveClass(ObjectStreamClass desc)
263: throws IOException, ClassNotFoundException {
264: Class clazz;
265: try {
266: clazz = super .resolveClass(desc);
267: readShort();
268: } catch (ClassNotFoundException e) {
269: int loaderIdx = readShort();
270: if (loaderIdx == CLASSLOADER_IDX_NOT_SET) {
271: throw e;
272: }
273:
274: ClassLoader loader = (ClassLoader) classLoadersList
275: .get(loaderIdx);
276: clazz = Class.forName(desc.getName(), false, loader);
277: }
278:
279: return clazz;
280: }
281:
282: }
283:
284: private final Cache pagedIn;
285:
286: private final ReferenceMap pagedOut;
287:
288: protected JRVirtualizable lastObject;
289: protected ReferenceMap lastObjectMap;
290: protected ReferenceMap lastObjectSet;
291:
292: private boolean readOnly;
293:
294: /**
295: * @param maxSize
296: * the maximum size (in JRVirtualizable objects) of the paged in
297: * cache.
298: */
299: protected JRAbstractLRUVirtualizer(int maxSize) {
300: this .pagedIn = new Cache(maxSize);
301: this .pagedOut = new ReferenceMap(ReferenceMap.HARD,
302: ReferenceMap.WEAK);
303: this .lastObject = null;
304:
305: this .lastObjectMap = new ReferenceMap(ReferenceMap.WEAK,
306: ReferenceMap.WEAK);
307: this .lastObjectSet = new ReferenceMap(ReferenceMap.WEAK,
308: ReferenceMap.HARD);
309: }
310:
311: protected synchronized final boolean isPagedOut(String id) {
312: return pagedOut.containsKey(id);
313: }
314:
315: protected synchronized boolean isPagedOutAndTouch(
316: JRVirtualizable o, String uid) {
317: boolean virtualized = isPagedOut(uid);
318: if (!virtualized) {
319: touch(o);
320: }
321: return virtualized;
322: }
323:
324: protected final void setLastObject(JRVirtualizable o) {
325: if (lastObject != o) {
326: if (o != null) {
327: JRVirtualizationContext context = o.getContext();
328: Object ownerLast = lastObjectMap.get(context);
329: if (ownerLast != o) {
330: if (ownerLast != null) {
331: lastObjectSet.remove(ownerLast);
332: }
333: lastObjectMap.put(context, o);
334: lastObjectSet.put(o, Boolean.TRUE);
335: }
336: }
337: this .lastObject = o;
338: }
339: }
340:
341: /**
342: * Sets the read only mode for the virtualizer.
343: * <p/>
344: * When in read-only mode, the virtualizer assumes that virtualizable objects are final
345: * and any change in a virtualizable object's data is discarded.
346: * <p/>
347: * When the virtualizer is used for multiple virtualization contexts (in shared mode),
348: * calling this method would override the read-only flags from all the contexts and all the
349: * objects will be manipulated in read-only mode.
350: * Use {@link JRVirtualizationContext#setReadOnly(boolean) JRVirtualizationContext.setReadOnly(boolean)}
351: * to set the read-only mode for one specific context.
352: *
353: * @param ro the read-only mode to set
354: */
355: public void setReadOnly(boolean ro) {
356: this .readOnly = ro;
357: }
358:
359: /**
360: * Determines whether the virtualizer is in read-only mode.
361: *
362: * @return whether the virtualizer is in read-only mode
363: * @see #setReadOnly(boolean)
364: */
365: public boolean isReadOnly() {
366: return readOnly;
367: }
368:
369: protected final boolean isReadOnly(JRVirtualizable o) {
370: return readOnly || o.getContext().isReadOnly();
371: }
372:
373: public synchronized void registerObject(JRVirtualizable o) {
374: setLastObject(o);
375: JRVirtualizable old = pagedIn.put(o.getUID(), o);
376: if (old != null) {
377: pagedIn.put(o.getUID(), old);
378: throw new IllegalStateException(
379: "Wrong object stored with UID \"" + o.getUID()
380: + "\"");
381: }
382: }
383:
384: public void deregisterObject(JRVirtualizable o) {
385: String uid = o.getUID();
386:
387: //try to remove virtual data
388: try {
389: dispose(o.getUID());
390: } catch (Exception e) {
391: log.error("Error removing virtual data", e);
392: //ignore
393: }
394:
395: synchronized (this ) {
396: JRVirtualizable oldIn = pagedIn.remove(uid);
397: if (oldIn != null) {
398: if (oldIn != o) {
399: pagedIn.put(uid, oldIn);
400: throw new IllegalStateException(
401: "Wrong object stored with UID \""
402: + o.getUID() + "\"");
403: }
404: } else {
405: Object oldOut = pagedOut.remove(uid);
406: if (oldOut != null && oldOut != o) {
407: pagedOut.put(uid, oldOut);
408: throw new IllegalStateException(
409: "Wrong object stored with UID \""
410: + o.getUID() + "\"");
411: }
412: }
413:
414: // We don't really care if someone deregisters an object
415: // that's not registered.
416: }
417: }
418:
419: public synchronized void touch(JRVirtualizable o) {
420: // If we just touched this object, don't touch it again.
421: if (this .lastObject != o) {
422: setLastObject(pagedIn.get(o.getUID()));
423: }
424: }
425:
426: public void requestData(JRVirtualizable o) {
427: String uid = o.getUID();
428: if (isPagedOutAndTouch(o, uid)) {
429: // unvirtualize
430: try {
431: pageIn(o);
432: } catch (IOException e) {
433: log.error("Error devirtualizing object", e);
434: throw new JRRuntimeException(e);
435: }
436:
437: o.afterInternalization();
438:
439: synchronized (this ) {
440: setLastObject(o);
441: pagedOut.remove(uid);
442: pagedIn.put(uid, o);
443: }
444: }
445: }
446:
447: public void clearData(JRVirtualizable o) {
448: String uid = o.getUID();
449: if (isPagedOutAndTouch(o, uid)) {
450: // remove virtual data
451: dispose(uid);
452:
453: synchronized (this ) {
454: pagedOut.remove(uid);
455: }
456: }
457: }
458:
459: public void virtualizeData(JRVirtualizable o) {
460: String uid = o.getUID();
461: if (!isPagedOut(uid)) {
462: o.beforeExternalization();
463:
464: // virtualize
465: try {
466: pageOut(o);
467: } catch (IOException e) {
468: log.error("Error virtualizing object", e);
469: throw new JRRuntimeException(e);
470: }
471:
472: o.afterExternalization();
473:
474: // Wait until we know it worked before tossing the data.
475: o.removeVirtualData();
476:
477: synchronized (this ) {
478: pagedOut.put(uid, o);
479: }
480: }
481: }
482:
483: protected void finalize() throws Throwable {
484: cleanup();
485:
486: super .finalize();
487: }
488:
489: /**
490: * Writes serialized indentity and virtual data of a virtualizable object to a stream.
491: *
492: * @param o the serialized object
493: * @param out the output stream
494: * @throws JRRuntimeException
495: */
496: protected final void writeData(JRVirtualizable o, OutputStream out)
497: throws JRRuntimeException {
498: try {
499: ObjectOutputStream oos = new ClassLoaderAnnotationObjectOutputStream(
500: out);
501: oos.writeObject(o.getIdentityData());
502: oos.writeObject(o.getVirtualData());
503: oos.flush();
504: } catch (IOException e) {
505: log.error("Error virtualizing object", e);
506: throw new JRRuntimeException(e);
507: }
508: }
509:
510: /**
511: * Reads serialized identity and virtual data for a virtualizable object
512: * from a stream.
513: *
514: * @param o the virtualizable object
515: * @param in the input stream
516: * @throws JRRuntimeException
517: */
518: protected final void readData(JRVirtualizable o, InputStream in)
519: throws JRRuntimeException {
520: try {
521: ObjectInputStream ois = new ClassLoaderAnnotationObjectInputStream(
522: in);
523: o.setIdentityData(ois.readObject());
524: o.setVirtualData(ois.readObject());
525: } catch (IOException e) {
526: log.error("Error devirtualizing object", e);
527: throw new JRRuntimeException(e);
528: } catch (ClassNotFoundException e) {
529: log.error("Error devirtualizing object", e);
530: throw new JRRuntimeException(e);
531: }
532: }
533:
534: protected synchronized void reset() {
535: readOnly = false;
536: }
537:
538: protected final void disposeAll() {
539: // Remove all paged-out swap files.
540: for (Iterator it = pagedOut.keySet().iterator(); it.hasNext();) {
541: String id = (String) it.next();
542: try {
543: dispose(id);
544: it.remove();
545: } catch (Exception e) {
546: log.error("Error cleaning up virtualizer.", e);
547: // Do nothing because we want to try to remove all swap files.
548: }
549: }
550:
551: for (Iterator it = pagedIn.idIterator(); it.hasNext();) {
552: String id = (String) it.next();
553: try {
554: dispose(id);
555: it.remove();
556: } catch (Exception e) {
557: log.error("Error cleaning up virtualizer.", e);
558: // Do nothing because we want to try to remove all swap files.
559: }
560: }
561: }
562:
563: /**
564: * Writes a virtualizable object's data to an external storage.
565: *
566: * @param o a virtualizable object
567: * @throws IOException
568: */
569: protected abstract void pageOut(JRVirtualizable o)
570: throws IOException;
571:
572: /**
573: * Reads a virtualizable object's data from an external storage.
574: *
575: * @param o a virtualizable object
576: * @throws IOException
577: */
578: protected abstract void pageIn(JRVirtualizable o)
579: throws IOException;
580:
581: /**
582: * Removes the external data associated with a virtualizable object.
583: *
584: * @param virtualId the ID of the virtualizable object
585: */
586: protected abstract void dispose(String virtualId);
587: }
|