001    /**
002     * JDBM LICENSE v1.00
003     *
004     * Redistribution and use of this software and associated documentation
005     * ("Software"), with or without modification, are permitted provided
006     * that the following conditions are met:
007     *
008     * 1. Redistributions of source code must retain copyright
009     *    statements and notices.  Redistributions must also contain a
010     *    copy of this document.
011     *
012     * 2. Redistributions in binary form must reproduce the
013     *    above copyright notice, this list of conditions and the
014     *    following disclaimer in the documentation and/or other
015     *    materials provided with the distribution.
016     *
017     * 3. The name "JDBM" must not be used to endorse or promote
018     *    products derived from this Software without prior written
019     *    permission of Cees de Groot.  For written permission,
020     *    please contact cg@cdegroot.com.
021     *
022     * 4. Products derived from this Software may not be called "JDBM"
023     *    nor may "JDBM" appear in their names without prior written
024     *    permission of Cees de Groot.
025     *
026     * 5. Due credit should be given to the JDBM Project
027     *    (http://jdbm.sourceforge.net/).
028     *
029     * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
030     * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
031     * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
032     * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
033     * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
034     * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
035     * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
036     * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
037     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
038     * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
039     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
040     * OF THE POSSIBILITY OF SUCH DAMAGE.
041     *
042     * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
043     * Copyright 2000-2001 (C) Alex Boisvert. All Rights Reserved.
044     * Contributions are Copyright (C) 2000 by their associated contributors.
045     *
046     * $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
047     */
048    
049    package jdbm.recman;
050    
051    import jdbm.RecordManager;
052    import jdbm.helper.CacheEvictionException;
053    import jdbm.helper.CachePolicy;
054    import jdbm.helper.CachePolicyListener;
055    import jdbm.helper.DefaultSerializer;
056    import jdbm.helper.Serializer;
057    import jdbm.helper.WrappedRuntimeException;
058    
059    import java.io.IOException;
060    import java.util.Enumeration;
061    
062    /**
063     *  A RecordManager wrapping and caching another RecordManager.
064     *
065     * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
066     * @author <a href="cg@cdegroot.com">Cees de Groot</a>
067     * @version $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
068     */
069    public class CacheRecordManager
070        implements RecordManager
071    {
072    
073        /**
074         * Wrapped RecordManager
075         */
076        protected RecordManager _recman;
077    
078    
079        /**
080         * Cache for underlying RecordManager
081         */
082        protected CachePolicy _cache;
083    
084    
085        /**
086         * Construct a CacheRecordManager wrapping another RecordManager and
087         * using a given cache policy.
088         *
089         * @param recman Wrapped RecordManager
090         * @param cache Cache policy
091         */
092        public CacheRecordManager( RecordManager recman, CachePolicy cache )
093        {
094            if ( recman == null ) {
095                throw new IllegalArgumentException( "Argument 'recman' is null" );
096            }
097            if ( cache == null ) {
098                throw new IllegalArgumentException( "Argument 'cache' is null" );
099            }
100            _recman = recman;
101            _cache = cache;
102            
103            _cache.addListener( new CacheListener() );
104        }
105    
106        
107        /**
108         * Get the underlying Record Manager.
109         *
110         * @return underlying RecordManager or null if CacheRecordManager has
111         *         been closed. 
112         */
113        public RecordManager getRecordManager()
114        {
115            return _recman;
116        }
117    
118        
119        /**
120         * Get the underlying cache policy
121         *
122         * @return underlying CachePolicy or null if CacheRecordManager has
123         *         been closed. 
124         */
125        public CachePolicy getCachePolicy()
126        {
127            return _cache;
128        }
129    
130        
131        /**
132         *  Inserts a new record using a custom serializer.
133         *
134         *  @param obj the object for the new record.
135         *  @return the rowid for the new record.
136         *  @throws IOException when one of the underlying I/O operations fails.
137         */
138        public long insert( Object obj )
139            throws IOException
140        {
141            return insert( obj, DefaultSerializer.INSTANCE );
142        }
143            
144            
145        /**
146         *  Inserts a new record using a custom serializer.
147         *
148         *  @param obj the object for the new record.
149         *  @param serializer a custom serializer
150         *  @return the rowid for the new record.
151         *  @throws IOException when one of the underlying I/O operations fails.
152         */
153        public synchronized long insert( Object obj, Serializer serializer )
154            throws IOException
155        {
156            checkIfClosed();
157    
158            long recid = _recman.insert( obj, serializer );
159            try {
160                _cache.put( new Long( recid ), new CacheEntry( recid, obj, serializer, false ) );
161            } catch ( CacheEvictionException except ) {
162                throw new WrappedRuntimeException( except );
163            }
164            return recid;
165        }
166    
167    
168        /**
169         *  Deletes a record.
170         *
171         *  @param recid the rowid for the record that should be deleted.
172         *  @throws IOException when one of the underlying I/O operations fails.
173         */
174        public synchronized void delete( long recid )
175            throws IOException
176        {
177            checkIfClosed();
178    
179            _recman.delete( recid );
180            _cache.remove( new Long( recid ) );
181        }
182    
183    
184        /**
185         *  Updates a record using standard Java serialization.
186         *
187         *  @param recid the recid for the record that is to be updated.
188         *  @param obj the new object for the record.
189         *  @throws IOException when one of the underlying I/O operations fails.
190         */
191        public void update( long recid, Object obj )
192            throws IOException
193        {
194            update( recid, obj, DefaultSerializer.INSTANCE );
195        }
196        
197    
198        /**
199         *  Updates a record using a custom serializer.
200         *
201         *  @param recid the recid for the record that is to be updated.
202         *  @param obj the new object for the record.
203         *  @param serializer a custom serializer
204         *  @throws IOException when one of the underlying I/O operations fails.
205         */
206        public synchronized void update( long recid, Object obj, 
207                                         Serializer serializer )
208            throws IOException
209        {
210            CacheEntry  entry;
211            Long        id;
212            
213            checkIfClosed();
214    
215            id = new Long( recid );
216            try {
217                entry = (CacheEntry) _cache.get( id );
218                if ( entry != null ) {
219                    // reuse existing cache entry
220                    entry._obj = obj;
221                    entry._serializer = serializer;
222                    entry._isDirty = true;
223                } else {
224                    _cache.put( id, new CacheEntry( recid, obj, serializer, true ) );
225                }
226            } catch ( CacheEvictionException except ) {
227                throw new IOException( except.getMessage() );
228            }
229        }
230    
231    
232        /**
233         *  Fetches a record using standard Java serialization.
234         *
235         *  @param recid the recid for the record that must be fetched.
236         *  @return the object contained in the record.
237         *  @throws IOException when one of the underlying I/O operations fails.
238         */
239        public Object fetch( long recid )
240            throws IOException
241        {
242            return fetch( recid, DefaultSerializer.INSTANCE );
243        }
244    
245            
246        /**
247         *  Fetches a record using a custom serializer.
248         *
249         *  @param recid the recid for the record that must be fetched.
250         *  @param serializer a custom serializer
251         *  @return the object contained in the record.
252         *  @throws IOException when one of the underlying I/O operations fails.
253         */
254        public synchronized Object fetch( long recid, Serializer serializer )
255            throws IOException
256        {
257            checkIfClosed();
258    
259            Long id = new Long( recid );
260            CacheEntry entry = (CacheEntry) _cache.get( id );
261            if ( entry == null ) {
262                entry = new CacheEntry( recid, null, serializer, false );
263                entry._obj = _recman.fetch( recid, serializer );
264                try {
265                    _cache.put( id, entry );
266                } catch ( CacheEvictionException except ) {
267                    throw new WrappedRuntimeException( except );
268                }
269            }
270            
271            if ( entry._obj instanceof byte[] )
272            {
273                byte[] copy = new byte[((byte[])entry._obj).length];
274                System.arraycopy( entry._obj, 0, copy, 0, ((byte[])entry._obj).length );
275                return copy;
276            }
277            
278            return entry._obj;
279        }
280    
281    
282        /**
283         *  Closes the record manager.
284         *
285         *  @throws IOException when one of the underlying I/O operations fails.
286         */
287        public synchronized void close()
288            throws IOException
289        {
290            checkIfClosed();
291    
292            updateCacheEntries();
293            _recman.close();
294            _recman = null;
295            _cache = null;
296        }
297    
298    
299        /**
300         *  Returns the number of slots available for "root" rowids. These slots
301         *  can be used to store special rowids, like rowids that point to
302         *  other rowids. Root rowids are useful for bootstrapping access to
303         *  a set of data.
304         */
305        public synchronized int getRootCount()
306        {
307            checkIfClosed();
308    
309            return _recman.getRootCount();
310        }
311    
312    
313        /**
314         *  Returns the indicated root rowid.
315         *
316         *  @see #getRootCount
317         */
318        public synchronized long getRoot( int id )
319            throws IOException
320        {
321            checkIfClosed();
322    
323            return _recman.getRoot( id );
324        }
325    
326    
327        /**
328         *  Sets the indicated root rowid.
329         *
330         *  @see #getRootCount
331         */
332        public synchronized void setRoot( int id, long rowid )
333            throws IOException
334        {
335            checkIfClosed();
336    
337            _recman.setRoot( id, rowid );
338        }
339    
340    
341        /**
342         * Commit (make persistent) all changes since beginning of transaction.
343         */
344        public synchronized void commit()
345            throws IOException
346        {
347            checkIfClosed();
348            updateCacheEntries();
349            _recman.commit();
350        }
351    
352    
353        /**
354         * Rollback (cancel) all changes since beginning of transaction.
355         */
356        public synchronized void rollback()
357            throws IOException
358        {
359            checkIfClosed();
360    
361            _recman.rollback();
362    
363            // discard all cache entries since we don't know which entries
364            // where part of the transaction
365            _cache.removeAll();
366        }
367    
368    
369        /**
370         * Obtain the record id of a named object. Returns 0 if named object
371         * doesn't exist.
372         */
373        public synchronized long getNamedObject( String name )
374            throws IOException
375        {
376            checkIfClosed();
377    
378            return _recman.getNamedObject( name );
379        }
380    
381    
382        /**
383         * Set the record id of a named object.
384         */
385        public synchronized void setNamedObject( String name, long recid )
386            throws IOException
387        {
388            checkIfClosed();
389    
390            _recman.setNamedObject( name, recid );
391        }
392    
393    
394        /**
395         * Check if RecordManager has been closed.  If so, throw an
396         * IllegalStateException
397         */
398        private void checkIfClosed()
399            throws IllegalStateException
400        {
401            if ( _recman == null ) {
402                throw new IllegalStateException( "RecordManager has been closed" );
403            }
404        }
405    
406        
407        /**
408         * Update all dirty cache objects to the underlying RecordManager.
409         */
410        protected void updateCacheEntries()
411            throws IOException
412        {
413            Enumeration enume = _cache.elements();
414            while ( enume.hasMoreElements() ) {
415                CacheEntry entry = (CacheEntry) enume.nextElement();
416                if ( entry._isDirty ) {
417                    _recman.update( entry._recid, entry._obj, entry._serializer );
418                    entry._isDirty = false;
419                }
420            }
421        }
422    
423        
424        private class CacheEntry
425        {
426    
427            long _recid;
428            Object _obj;
429            Serializer _serializer;
430            boolean _isDirty;
431            
432            CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
433            {
434                _recid = recid;
435                _obj = obj;
436                _serializer = serializer;
437                _isDirty = isDirty;
438            }
439            
440        } // class CacheEntry
441    
442        private class CacheListener
443            implements CachePolicyListener
444        {
445            
446            /** Notification that cache is evicting an object
447             *
448             * @arg obj object evited from cache
449             *
450             */
451            public void cacheObjectEvicted( Object obj ) 
452                throws CacheEvictionException
453            {
454                CacheEntry entry = (CacheEntry) obj;
455                if ( entry._isDirty ) {
456                    try {
457                        _recman.update( entry._recid, entry._obj, entry._serializer );
458                    } catch ( IOException except ) {
459                        throw new CacheEvictionException( except );
460                    }
461                }
462            }
463            
464        }
465    }