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    package jdbm.recman;
049    
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    import org.apache.directory.server.i18n.I18n;
063    
064    
065    /**
066     *  A RecordManager wrapping and caching another RecordManager.
067     *
068     * @author <a href="mailto:boisvert@intalio.com">Alex Boisvert</a>
069     * @author <a href="cg@cdegroot.com">Cees de Groot</a>
070     * @version $Id: CacheRecordManager.java,v 1.9 2005/06/25 23:12:32 doomdark Exp $
071     */
072    public class CacheRecordManager implements RecordManager
073    {
074        /** Wrapped RecordManager */
075        protected RecordManager recMgr;
076    
077        /** Cache for underlying RecordManager */
078        protected CachePolicy<Long,CacheEntry> cache;
079    
080    
081        /**
082         * Construct a CacheRecordManager wrapping another RecordManager and
083         * using a given cache policy.
084         *
085         * @param recMgr Wrapped RecordManager
086         * @param cache Cache policy
087         */
088        public CacheRecordManager( RecordManager recMgr, CachePolicy<Long,CacheEntry> cache )
089        {
090            if ( recMgr == null ) 
091            {
092                throw new IllegalArgumentException( I18n.err( I18n.ERR_517 ) );
093            }
094            
095            if ( cache == null ) 
096            {
097                throw new IllegalArgumentException( I18n.err( I18n.ERR_542 ) );
098            }
099    
100            this.recMgr = recMgr;
101            this.cache = cache;
102            
103            this.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 recMgr;
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<Long,CacheEntry> 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 ) throws IOException
139        {
140            return insert( obj, DefaultSerializer.INSTANCE );
141        }
142            
143            
144        /**
145         * Inserts a new record using a custom serializer.
146         *
147         * @param obj the object for the new record.
148         * @param serializer a custom serializer
149         * @return the rowid for the new record.
150         * @throws IOException when one of the underlying I/O operations fails.
151         */
152        public synchronized long insert( Object obj, Serializer serializer ) throws IOException
153        {
154            checkIfClosed();
155    
156            long recid = recMgr.insert( obj, serializer );
157            try 
158            {
159                cache.put( recid, new CacheEntry( recid, obj, serializer, false ) );
160            } 
161            catch ( CacheEvictionException except ) 
162            {
163                throw new WrappedRuntimeException( except );
164            }
165            return recid;
166        }
167    
168    
169        /**
170         * Deletes a record.
171         *
172         * @param recid the rowid for the record that should be deleted.
173         * @throws IOException when one of the underlying I/O operations fails.
174         */
175        public synchronized void delete( long recid ) throws IOException
176        {
177            checkIfClosed();
178    
179            recMgr.delete( recid );
180            cache.remove( 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 ) throws IOException
192        {
193            update( recid, obj, DefaultSerializer.INSTANCE );
194        }
195        
196    
197        /**
198         * Updates a record using a custom serializer.
199         *
200         * @param recid the recid for the record that is to be updated.
201         * @param obj the new object for the record.
202         * @param serializer a custom serializer
203         * @throws IOException when one of the underlying I/O operations fails.
204         */
205        public synchronized void update( long recid, Object obj, Serializer serializer ) throws IOException
206        {
207            CacheEntry  entry;
208            
209            checkIfClosed();
210    
211            try {
212                entry = cache.get( recid );
213                
214                if ( entry != null ) 
215                {
216                    // reuse existing cache entry
217                    entry.obj = obj;
218                    entry.serializer = serializer;
219                    entry.isDirty = true;
220                } 
221                else 
222                {
223                    cache.put( recid, new CacheEntry( recid, obj, serializer, true ) );
224                }
225            } 
226            catch ( CacheEvictionException except ) 
227            {
228                throw new IOException( except.getLocalizedMessage() );
229            }
230        }
231    
232    
233        /**
234         * Fetches a record using standard Java serialization.
235         *
236         * @param recid the recid for the record that must be fetched.
237         * @return the object contained in the record.
238         * @throws IOException when one of the underlying I/O operations fails.
239         */
240        public Object fetch( long recid ) 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 ) throws IOException
255        {
256            checkIfClosed();
257    
258            CacheEntry entry = cache.get( recid );
259            if ( entry == null ) 
260            {
261                entry = new CacheEntry( recid, null, serializer, false );
262                entry.obj = recMgr.fetch( recid, serializer );
263                try 
264                {
265                    cache.put( recid, entry );
266                } 
267                catch ( CacheEvictionException except ) 
268                {
269                    throw new WrappedRuntimeException( except );
270                }
271            }
272            
273            if ( entry.obj instanceof byte[] )
274            {
275                byte[] copy = new byte[ ( ( byte[] ) entry.obj ).length ];
276                System.arraycopy( entry.obj, 0, copy, 0, ( ( byte[] ) entry.obj ).length );
277                return copy;
278            }
279            
280            return entry.obj;
281        }
282    
283    
284        /**
285         * Closes the record manager.
286         *
287         * @throws IOException when one of the underlying I/O operations fails.
288         */
289        public synchronized void close() throws IOException
290        {
291            checkIfClosed();
292    
293            updateCacheEntries();
294            recMgr.close();
295            recMgr = null;
296            cache = null;
297        }
298    
299    
300        /**
301         * Returns the number of slots available for "root" rowids. These slots
302         * can be used to store special rowids, like rowids that point to
303         * other rowids. Root rowids are useful for bootstrapping access to
304         * a set of data.
305         */
306        public synchronized int getRootCount()
307        {
308            checkIfClosed();
309    
310            return recMgr.getRootCount();
311        }
312    
313    
314        /**
315         * Returns the indicated root rowid.
316         *
317         * @see #getRootCount
318         */
319        public synchronized long getRoot( int id ) throws IOException
320        {
321            checkIfClosed();
322    
323            return recMgr.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 ) throws IOException
333        {
334            checkIfClosed();
335    
336            recMgr.setRoot( id, rowid );
337        }
338    
339    
340        /**
341         * Commit (make persistent) all changes since beginning of transaction.
342         */
343        public synchronized void commit() throws IOException
344        {
345            checkIfClosed();
346            updateCacheEntries();
347            recMgr.commit();
348        }
349    
350    
351        /**
352         * Rollback (cancel) all changes since beginning of transaction.
353         */
354        public synchronized void rollback() throws IOException
355        {
356            checkIfClosed();
357    
358            recMgr.rollback();
359    
360            // discard all cache entries since we don't know which entries
361            // where part of the transaction
362            cache.removeAll();
363        }
364    
365    
366        /**
367         * Obtain the record id of a named object. Returns 0 if named object
368         * doesn't exist.
369         */
370        public synchronized long getNamedObject( String name ) throws IOException
371        {
372            checkIfClosed();
373    
374            return recMgr.getNamedObject( name );
375        }
376    
377    
378        /**
379         * Set the record id of a named object.
380         */
381        public synchronized void setNamedObject( String name, long recid ) throws IOException
382        {
383            checkIfClosed();
384    
385            recMgr.setNamedObject( name, recid );
386        }
387    
388    
389        /**
390         * Check if RecordManager has been closed.  If so, throw an IllegalStateException
391         */
392        private void checkIfClosed() throws IllegalStateException
393        {
394            if ( recMgr == null ) 
395            {
396                throw new IllegalStateException( I18n.err( I18n.ERR_538 ) );
397            }
398        }
399    
400        
401        /**
402         * Update all dirty cache objects to the underlying RecordManager.
403         */
404        protected void updateCacheEntries() throws IOException
405        {
406            Enumeration<CacheEntry> enume = cache.elements();
407            while ( enume.hasMoreElements() ) 
408            {
409                CacheEntry entry = enume.nextElement();
410                if ( entry.isDirty ) 
411                {
412                    recMgr.update( entry.recid, entry.obj, entry.serializer );
413                    entry.isDirty = false;
414                }
415            }
416        }
417    
418        
419        private class CacheEntry
420        {
421            long recid;
422            Object obj;
423            Serializer serializer;
424            boolean isDirty;
425            
426            CacheEntry( long recid, Object obj, Serializer serializer, boolean isDirty )
427            {
428                this.recid = recid;
429                this.obj = obj;
430                this.serializer = serializer;
431                this.isDirty = isDirty;
432            }
433            
434        } // class CacheEntry
435    
436        
437        private class CacheListener implements CachePolicyListener<CacheEntry>
438        {
439            
440            /** 
441             * Notification that cache is evicting an object
442             *
443             * @param obj object evicted from cache
444             */
445            public void cacheObjectEvicted( CacheEntry obj ) throws CacheEvictionException
446            {
447                CacheEntry entry = obj;
448                if ( entry.isDirty ) 
449                {
450                    try 
451                    {
452                        recMgr.update( entry.recid, entry.obj, entry.serializer );
453                    } 
454                    catch ( IOException except ) 
455                    {
456                        throw new CacheEvictionException( except );
457                    }
458                }
459            }
460        }
461    }