KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > ozoneDB > ClientCacheDatabase


1 // You can redistribute this software and/or modify it under the terms of
2
// the Ozone Library License version 1 published by ozone-db.org.
3
//
4
// The original code and portions created by SMB are
5
// Copyright (C) 1997-@year@ by SMB GmbH. All rights reserved.
6
//
7
// $Id: ClientCacheDatabase.java,v 1.8 2003/01/23 18:07:00 per_nyfelt Exp $
8

9 package org.ozoneDB;
10
11 import org.ozoneDB.DxLib.*;
12 import org.ozoneDB.core.DbRemote.*;
13 import org.ozoneDB.core.*;
14
15 import java.io.*;
16 import java.util.Hashtable JavaDoc;
17
18
19 /**
20  * This is an {@link ExternalDatabase} that implements a client side cache on
21  * top of another {@link ExternalDatabase}.<p>
22  *
23  * <i> In contrast to {@link LocalDatabase} and {@link RemoteDatabase} which
24  * produce the exactly same results for the same code, this implementation of
25  * {@link ExternalDatabase} is not guaranteed to do so.</i><p>
26  *
27  * Note: The method parameters of type {@link OzoneRemote} are supposed to by
28  * proxy objects (of type {@link OzoneProxy}). However, it's possible to pass
29  * a database objects (of type {@link OzoneCompatible}). In this case the
30  * parameter should be substituted. Currently this is done by the invoke()
31  * method only.<p>
32  *
33  * Impl. Note: All interface methods are synchronized because they have to be
34  * executed as an atomar operation.
35  *
36  *
37  * @author <a HREF="http://www.softwarebuero.de/">SMB</a>
38  * @version $Revision: 1.8 $Date: 2003/01/23 18:07:00 $
39  * @see OzoneInterface
40  */

41 public class ClientCacheDatabase extends ExternalDatabase {
42
43     /**
44      * Holds all currently cached target objects. Maps ObjectID into
45      * CacheObjectContainer.
46      */

47     private DxMap idTable;
48
49     /**
50      * Holds the names that are known to this database instance. Maps String
51      * into ObjectID.
52      */

53     private DxMap nameTable;
54
55     private long totalMemory;
56
57     private ExternalDatabase delegate;
58
59     private long idCount;
60     private long idBorder;
61     private long idRange = 1000;
62
63     private boolean debug;
64
65
66     /**
67      * Constructs a new ClientCacheDatabase with the given delegate as
68      * back-end.<p>
69      *
70      * Note: The size of the client side cache can be adjusted via the heap
71      * size of the VM (parameter -Xmx). The cache uses all available heap in
72      * its VM.
73      *
74      *
75      * @param _delegate The back-end database.
76      */

77     public ClientCacheDatabase( ExternalDatabase _delegate ) {
78         this( _delegate, false );
79     }
80
81
82     /**
83      * Constructs a new ClientCacheDatabase with the given delegate as
84      * back-end and the given debug option.<p>
85      *
86      * Note: The size of the client side cache can be adjusted via the heap
87      * size of the VM (parameter -Xmx). The cache uses all available heap in
88      * its VM.
89      *
90      *
91      * @param _delegate The back-end database.
92      * @param _debug
93      */

94     public ClientCacheDatabase( ExternalDatabase _delegate, boolean _debug ) {
95         delegate = _delegate;
96         delegate.setWrapper( this );
97
98         debug = _debug;
99         idTable = new DxHashMap( 1000 );
100         nameTable = new DxHashMap( 100 );
101
102         calcMemory();
103     }
104
105
106     protected void open( Hashtable JavaDoc _props ) throws Exception JavaDoc {
107         throw new RuntimeException JavaDoc( "Method open() must not be called for this class." );
108     }
109
110
111     protected synchronized ObjectID nextID() throws Exception JavaDoc {
112         if (idCount >= idBorder) {
113             ObjectID id = (ObjectID)delegate.sendCommand( new DbNextID( idRange ), true );
114             idCount = id.value();
115             idBorder = idCount + idRange;
116         }
117         return new ObjectID( ++idCount );
118     }
119
120
121     public ExternalDatabase delegate() {
122         return delegate;
123     }
124
125
126     protected Object JavaDoc sendCommand( DbCommand command, boolean waitForResult, DbClient connection ) throws Exception JavaDoc {
127         throw new RuntimeException JavaDoc( "ClientCacheDatabase must access the actual database through its delegate only." );
128     }
129
130
131     protected DbClient newConnection() throws Exception JavaDoc {
132         return delegate.newConnection();
133     }
134
135
136     public boolean isOpen() throws Exception JavaDoc {
137         return delegate.isOpen();
138     }
139
140
141     public void close() throws Exception JavaDoc {
142         delegate.close();
143     }
144
145
146     protected void finalize() throws Throwable JavaDoc {
147         close();
148     }
149
150
151     public ExternalTransaction newTransaction() {
152         return new ExternalTransaction( this );
153     }
154
155
156     public void beginTX( AbstractTransaction tx ) throws TransactionException, IOException {
157         if (debug) {
158             System.out.println( "[debug] beginTX()" );
159         }
160         delegate.beginTX( tx );
161     }
162
163
164     public void joinTX( AbstractTransaction tx ) throws TransactionException {
165         if (debug) {
166             System.out.println( "[debug] joinTX()" );
167         }
168         delegate.joinTX( tx );
169     }
170
171
172     public boolean leaveTX( AbstractTransaction tx ) {
173         if (debug) {
174             System.out.println( "[debug] leaveTX()" );
175         }
176         return delegate.leaveTX( tx );
177     }
178
179
180     public void checkpointTX( AbstractTransaction tx ) throws TransactionException, IOException {
181         if (debug) {
182             System.out.println( "[debug] checkpointTX()" );
183         }
184         throw new RuntimeException JavaDoc( "Not yet implemented." );
185     }
186
187
188     public synchronized void prepareTX( AbstractTransaction tx ) throws TransactionException, IOException {
189         if (debug) {
190             System.out.println( "[debug] prepareTX()" );
191         }
192         try {
193             syncCache();
194             delegate.prepareTX( tx );
195             updateModTimes();
196         } catch (TransactionException e) {
197             abortCache();
198             throw e;
199         } catch (Exception JavaDoc e) {
200             throw new UnexpectedException( e.toString() );
201         }
202     }
203
204
205     public synchronized void commitTX( AbstractTransaction tx, boolean onePhase ) throws TransactionException, IOException {
206         if (debug) {
207             System.out.println( "[debug] commitTX(): onePhase:" + onePhase );
208         }
209
210         if (onePhase) {
211             prepareTX( tx );
212         }
213
214         try {
215             delegate.commitTX( tx, false );
216         } catch (TransactionException e) {
217             throw e;
218         } catch (Exception JavaDoc e) {
219             throw new UnexpectedException( e.toString() );
220         }
221
222         // reset container states; handle exceptions in a special way
223
try {
224             DxIterator it = idTable.iterator();
225             while (it.next() != null) {
226                 // clearing the states of all containers without checking should
227
// be the fastest way here
228
CacheObjectContainer container = (CacheObjectContainer)it.object();
229                 container.clearState();
230             }
231         } catch (Exception JavaDoc e) {
232             // if someting failes after the server transaction is commited we
233
// have to signal a strong error
234
throw new UnexpectedException( e.toString() );
235         }
236     }
237
238
239     public synchronized void rollbackTX( AbstractTransaction tx ) throws TransactionException, IOException {
240         if (debug) {
241             System.out.println( "[debug] rollbackTX()" );
242         }
243
244         delegate.rollbackTX( tx );
245         abortCache();
246     }
247
248
249     // OzoneInterface methods *****************************
250

251
252     /**
253      * Force the database server to reload all classes which extend
254      * OzoneObject. This is useful while testing new classes.
255      */

256     public void reloadClasses() throws Exception JavaDoc {
257         delegate.reloadClasses();
258     }
259
260
261     public synchronized OzoneProxy createObject( String JavaDoc className, int access, String JavaDoc name, String JavaDoc sig, Object JavaDoc[] args )
262             throws RuntimeException JavaDoc {
263         try {
264             AbstractTransaction tx = delegate.txForThread( Thread.currentThread() );
265             if (tx == null) {
266                 throw new TransactionException( "Thread has not yet joined a transaction.", TransactionException.STATE );
267             }
268
269             // todo: uncommented until verified that it works
270
//OzoneCompatible target = (OzoneCompatible)Class.forName( className ).newInstance();
271
OzoneCompatible target = (OzoneCompatible)Env.currentEnv().classManager.classForName( className ).newInstance();
272
273             CacheObjectContainer container = new CacheObjectContainer( target, nextID(), name, access );
274             container.setDatabase( this );
275             container.raiseState( ObjectContainer.STATE_CREATED );
276             container.setDirty( true );
277             container.tx = tx;
278
279             idTable.addForKey( container, container.id() );
280
281             return container.ozoneProxy();
282         } catch (Exception JavaDoc e) {
283             // only supported from JDK1.4 on
284
// throw new RuntimeException("Caught during createObject()",e);
285
throw new RuntimeException JavaDoc("Caught during createObject(): "+e);
286         }
287     }
288
289
290     public synchronized void deleteObject( OzoneRemote obj ) throws RuntimeException JavaDoc {
291         try {
292             OzoneProxy proxy = (OzoneProxy)obj;
293             CacheObjectContainer container = fetch0( proxy.remoteID(), Lock.LEVEL_WRITE );
294
295             container.raiseState( ObjectContainer.STATE_DELETED );
296         } catch (Exception JavaDoc e) {
297             // only supported from JDK1.4 on
298
// throw new RuntimeException("Caught during createObject()",e);
299
throw new RuntimeException JavaDoc("Caught during createObject(): "+e);
300         }
301     }
302
303
304     public synchronized OzoneProxy copyObject( OzoneRemote obj ) throws Exception JavaDoc {
305         throw new RuntimeException JavaDoc( "copyObject(): Method not implemented." );
306     }
307
308
309     public synchronized void nameObject( OzoneRemote obj, String JavaDoc name ) throws Exception JavaDoc {
310
311         ObjectID id = (ObjectID)nameTable.elementForKey( name );
312         if (id != null) {
313             throw new PermissionDeniedException( "Root object name '" + name + "' already exists." );
314         }
315
316         OzoneProxy proxy = (OzoneProxy)obj;
317         CacheObjectContainer container = fetch0( proxy.remoteID(), Lock.LEVEL_WRITE );
318         container.setName( name );
319         nameTable.addForKey( container.id(), name );
320     }
321
322
323     public synchronized OzoneProxy objectForName( String JavaDoc name ) throws Exception JavaDoc {
324         if (debug) {
325             System.out.println( "[debug] objectForName(): name:" + name );
326         }
327
328         ObjectID id = (ObjectID)nameTable.elementForKey( name );
329
330         if (id != null) {
331             CacheObjectContainer container = (CacheObjectContainer)idTable.elementForKey( id );
332             if (container != null) {
333                 return container.ozoneProxy();
334             } else {
335                 container = fetch0( id, Lock.LEVEL_READ );
336
337                 if (container != null) {
338                     return container.ozoneProxy();
339                 } else {
340                     nameTable.removeForKey( name );
341                     return null;
342                 }
343             }
344         } else {
345             OzoneProxy proxy = delegate.objectForName( name );
346             if (proxy != null) {
347                 id = proxy.remoteID();
348                 nameTable.addForKey( id, name );
349
350                 CacheObjectContainer container = fetch0( id, Lock.LEVEL_READ );
351                 return container.ozoneProxy();
352             } else {
353                 return null;
354             }
355         }
356     }
357
358
359     public Object JavaDoc invoke( OzoneProxy proxy, String JavaDoc methodName, String JavaDoc sig, Object JavaDoc[] args, int lockLevel )
360             throws Exception JavaDoc {
361         throw new RuntimeException JavaDoc( "invoke(): Method not implemented." );
362     }
363
364
365     public Object JavaDoc invoke( OzoneProxy proxy, int methodIndex, Object JavaDoc[] args, int lockLevel ) throws Exception JavaDoc {
366         throw new RuntimeException JavaDoc( "invoke(): Method not implemented." );
367     }
368
369
370     // fetch/sync cache ***********************************
371

372
373     public OzoneCompatible fetch(OzoneProxy proxy,int lockLevel) throws Exception JavaDoc,org.ozoneDB.ObjectNotFoundException, java.io.IOException JavaDoc, java.lang.ClassNotFoundException JavaDoc, org.ozoneDB.TransactionException, org.ozoneDB.core.TransactionError {
374         if (debug) {
375             System.out.println( "[debug] fetch(): id:" + proxy.remoteID() );
376         }
377
378         CacheObjectContainer container = fetch0(proxy.remoteID(),lockLevel);
379         OzoneCompatible target = container.target();
380         if (target == null) {
381             if (debug) {
382                 System.out.println( "[debug] fetch(): id:" + proxy.remoteID() );
383             }
384             throw new ObjectNotFoundException( "Target is null." );
385         }
386
387         return target;
388     }
389
390
391     /**
392      * Fetch the object with the specified ObjectID from the server.
393      *
394      *
395      * @param id The id that specifies the object to fetch.
396      * @param lockLevel the type of lock requested (Lock.READ, Lock.WRITE etc.)
397      * @return The transaction for the current thread.
398      */

399     protected CacheObjectContainer fetch0( ObjectID id, int lockLevel ) throws Exception JavaDoc,ObjectNotFoundException,TransactionException {
400
401         AbstractTransaction tx = delegate.txForThread( Thread.currentThread() );
402         if (tx == null) {
403             throw new TransactionException( "Thread has not yet joined a transaction.", TransactionException.STATE );
404         }
405
406         CacheObjectContainer container = (CacheObjectContainer)idTable.elementForKey( id );
407         if (container == null) {
408             container = fetchChunk(id,100000);
409         }
410
411         if (container == null) {
412             throw new ObjectNotFoundException( "No object for the given ID." );
413         }
414
415         // we are going to change the proxy so we have to synchronize it;
416
// afterwards the value of container.tx, which in fact is a lock,
417
// prevents other threads from changing the container
418
synchronized (container) {
419
420             // check if the target is member of our tx; because there is no
421
// detection on the client, we may end up in a real, hard deadlock!
422
while (container.tx != null && container.tx != tx) {
423                 try {
424                     // we have to call wait on the container to let the JVM
425
// release the lock that the synchronized statement put on it
426
container.wait();
427                 } catch (InterruptedException JavaDoc e) {
428                 }
429             }
430
431             // make this proxy/object a member of the transaction; in fact,
432
// this puts a lock on it
433
if (container.tx == null) {
434                 container.tx = tx;
435             }
436
437             if (lockLevel == Lock.LEVEL_READ) {
438                 container.raiseState( CacheObjectContainer.STATE_READ );
439             } else {
440                 container.raiseState( CacheObjectContainer.STATE_MODIFIED );
441                 container.setDirty( true );
442             }
443         }
444
445         return container;
446     }
447
448
449     protected synchronized CacheObjectContainer fetchChunk( ObjectID rootID, int size ) throws Exception JavaDoc {
450         ensureSpace(size);
451
452         DxArrayBag chunk = (DxArrayBag) delegate.sendCommand(new DbCacheChunk(rootID,size),true);
453
454         for (int i = 0; i < chunk.count(); i++) {
455             CacheObjectContainer container = (CacheObjectContainer)chunk.elementAtIndex( i );
456             if (debug) {
457                 System.out.println( "[debug] fetchChunk(): container:" + container.id() );
458             }
459
460             // set the container link of this client side copy of the target
461
container.setTarget( container.target() );
462
463             container.setDatabase( this );
464
465             if (idTable.containsKey( container.id() )) {
466                 System.out.print( "[debug] fetchChunk(): container already registered... " );
467
468                 // use the newly fetched container instead of the old one if
469
// the old isn't currently locked
470
CacheObjectContainer c = (CacheObjectContainer)idTable.elementForKey( container.id() );
471                 if (c.tx != null) {
472                     System.out.println( "and locked - using old one." );
473                 } else {
474                     System.out.println( "not locked - using new one." );
475                     idTable.removeForKey( container.id() );
476                     idTable.addForKey( container, container.id() );
477                 }
478             } else {
479                 idTable.addForKey( container, container.id() );
480             }
481         }
482
483         return (CacheObjectContainer)idTable.elementForKey( rootID );
484     }
485
486
487     /**
488      * Synchronize all changed objects with the server. This does not commits
489      * the transaction but only transfers all changed objects back to the
490      * server.
491      */

492     protected synchronized void syncCache() throws Exception JavaDoc {
493         if (debug) {
494             System.out.println( "[debug] syncCache()" );
495         }
496
497         // although we send a byte[] the proxy links are handled correctly -
498
// see DbCacheChunk for details
499

500         ByteArrayOutputStream bout = new ByteArrayOutputStream();
501         ObjectOutputStream out = new ObjectOutputStream( bout );
502
503         int count = 0;
504         DxIterator it = idTable.iterator();
505         while (it.next() != null) {
506             CacheObjectContainer container = (CacheObjectContainer)it.object();
507
508             if (debug) {
509                 System.out.println( "[debug] id:" + container.id() + ", state:" + container.state() + ", dirty:"
510                         + container.dirty() );
511             }
512
513             if (container.dirty()) {
514                 out.writeObject( container );
515
516                 // if the sendCommand() fails, which would make the container
517
// dirty again, then the entire transaction is aborted the
518
// container is thrown away
519
container.setDirty( false );
520                 count++;
521             }
522
523             if (bout.size() > 500000) {
524                 if (debug) {
525                     System.out.println( "[debug] syncCache(): writing " + count + " objects" );
526                 }
527
528                 out.close();
529                 delegate.sendCommand( new DbCacheChunk( bout.toByteArray() ), true );
530                 bout = new ByteArrayOutputStream();
531                 out = new ObjectOutputStream( bout );
532                 count = 0;
533             }
534         }
535
536         if (debug) {
537             System.out.println( "[debug] syncCache(): writing " + count + " objects" );
538         }
539         out.close();
540         delegate.sendCommand( new DbCacheChunk( bout.toByteArray() ), true );
541     }
542
543
544     /**
545      * Throws away all modified containers. Clears idTable and nameTable.
546      */

547     protected synchronized void abortCache() {
548         try {
549             DxIterator it = idTable.iterator();
550             while (it.next() != null) {
551                 CacheObjectContainer container = (CacheObjectContainer)it.object();
552                 if (container.state() >= ObjectContainer.STATE_MODIFIED) {
553                     idTable.removeForKey( container.id() );
554                     if (container.name() != null) {
555                         nameTable.removeForKey( container.name() );
556                     }
557                 }
558             }
559         } catch (Exception JavaDoc e) {
560             // this should never happen; all we can do here is to signal a
561
// strong error
562
throw new RuntimeException JavaDoc( e.toString() );
563         }
564     }
565
566
567     /**
568      * Update the modification times of all currently modified containers. This
569      * method should be called after prepareTX only.
570      */

571     protected synchronized void updateModTimes() {
572         try {
573             if (debug) {
574                 System.out.println( "[debug] updateModTimes()" );
575             }
576
577             DbModTimes command = new DbModTimes();
578
579             int count = 0;
580             DxIterator it = idTable.iterator();
581             while (it.next() != null) {
582                 CacheObjectContainer container = (CacheObjectContainer)it.object();
583                 if (container.state() == ObjectContainer.STATE_MODIFIED || container.state()
584                         == ObjectContainer.STATE_CREATED) {
585                     if (debug) {
586                         System.out.println( "[debug] send: id:" + container.id() );
587                     }
588
589                     command.addObjectID( container.id() );
590                     count++;
591                 }
592             }
593
594             // avoid sending the command if nothing has changed anyway
595
if (count > 0) {
596                 DxMap map = (DxMap)delegate.sendCommand( command, true );
597
598                 it = map.iterator();
599                 Long JavaDoc modTime = null;
600                 while ((modTime = (Long JavaDoc)it.next()) != null) {
601                     ObjectID id = (ObjectID)it.key();
602
603                     if (debug) {
604                         System.out.println( "[debug] receive: id:" + id + ", modTime:" + modTime );
605                     }
606
607                     CacheObjectContainer container = (CacheObjectContainer)idTable.elementForKey( id );
608                     container.setModTime( modTime.longValue() );
609                 }
610             }
611         } catch (Exception JavaDoc e) {
612             // this should never happen; all we can do here is to signal a
613
// strong error
614
throw new RuntimeException JavaDoc( e.toString() );
615         }
616     }
617
618
619     /**
620      * Try to free the specified amount of cache space. This will remove
621      * containers from the cache that are currently not used by a transaction
622      */

623     protected synchronized void ensureSpace( long neededSpace ) {
624         if (freeMemory() < neededSpace) {
625
626             // build priority queue for all currently cached objects
627
DxMap priorityQueue = new DxTreeMap();
628             DxIterator it = idTable.iterator();
629             while (it.next() != null) {
630                 CacheObjectContainer container = (CacheObjectContainer)it.object();
631                 priorityQueue.addForKey( container, new Long JavaDoc( container.lastTouched() ) );
632             }
633
634             // free 100 objects at once until there is enough free memory or
635
// no unlocked container left, lowest priority first
636
it = priorityQueue.iterator();
637             CacheObjectContainer container = (CacheObjectContainer)it.next();
638             while (freeMemory() < neededSpace && container != null) {
639
640                 for (int i = 0; i < 100 && container != null; i++) {
641                     if (container.tx == null) {
642                         idTable.removeForKey( container.id() );
643                         container = (CacheObjectContainer)it.next();
644                     } else {
645                         System.out.println( "[debug] ensureSpace(): trying to free locked container." );
646                     }
647                 }
648                 System.gc();
649             }
650         }
651     }
652
653
654     // free memory ****************************************
655

656
657     /**
658      * Initialize the internal memory counter so that freeMemory() returns
659      * correct results.
660      */

661     protected void calcMemory() {
662         Runtime JavaDoc rt = Runtime.getRuntime();
663         try {
664             DxBag bag = new DxArrayBag();
665             for (;;) {
666                 bag.add( new byte[100000] );
667             }
668         } catch (OutOfMemoryError JavaDoc e) {
669             totalMemory = rt.totalMemory();
670             rt.gc();
671         }
672     }
673
674
675     /**
676      * Return the amount of *total* free memory in the system. The results
677      * returned by Runtime.freeMemory() may change overtime and so its
678      * useless for ozone.<p>
679      *
680      *
681      * Note: this will not work properly if some kind of weal references are
682      * used in this VM. In case of empty space we need to force teh GC to also
683      * free weak references.
684      */

685     public long freeMemory() {
686         Runtime JavaDoc rt = Runtime.getRuntime();
687         long hiddenMemory = totalMemory - rt.totalMemory();
688
689         // keep 2MB free at least
690
return Math.max( rt.freeMemory() + hiddenMemory - 2000000L, 0 );
691     }
692
693 }
694
Popular Tags