EMMA Coverage Report (generated Sun May 02 20:42:29 CEST 2010)
[all classes][hu.netmind.beankeeper.model.impl]

COVERAGE SUMMARY FOR SOURCE FILE [ClassTrackerImpl.java]

nameclass, %method, %block, %line, %
ClassTrackerImpl.java100% (1/1)92%  (22/24)78%  (1576/2019)82%  (296/362)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class ClassTrackerImpl100% (1/1)92%  (22/24)78%  (1576/2019)82%  (296/362)
getClassInfo (String, String): ClassInfo 0%   (0/1)0%   (0/31)0%   (0/8)
getSubClasses (ClassEntry): List 0%   (0/1)0%   (0/61)0%   (0/10)
allocateObjectIds (ClassEntry, long): long 100% (1/1)64%  (124/194)66%  (19.9/30)
getNextId (ClassEntry): Long 100% (1/1)66%  (89/135)85%  (17.8/21)
getRelatedClassEntries (ClassEntry): List 100% (1/1)68%  (23/34)71%  (5/7)
getStorableRootClassEntries (ClassEntry): List 100% (1/1)68%  (23/34)71%  (5/7)
getClassEntry (Integer): ClassEntry 100% (1/1)71%  (12/17)67%  (2/3)
getClassEntryId (ClassEntry): Integer 100% (1/1)76%  (16/21)75%  (3/4)
registerClassEntry (ClassEntry): Integer 100% (1/1)77%  (121/157)72%  (20.9/29)
reload (): void 100% (1/1)78%  (175/225)76%  (34.4/45)
getClassInfo (Class, String): ClassInfo 100% (1/1)83%  (10/12)67%  (2/3)
checkValid (ClassInfo): void 100% (1/1)85%  (225/266)92%  (42.3/46)
getClassInfo (ClassEntry): ClassInfo 100% (1/1)86%  (189/220)95%  (35/37)
updateClassEntryRelations (ClassEntry): void 100% (1/1)89%  (281/317)97%  (56/58)
getMatchingClassEntry (String): ClassEntry 100% (1/1)92%  (56/61)98%  (11.7/12)
isPrimitive (Class): boolean 100% (1/1)98%  (86/88)67%  (2/3)
<static initializer> 100% (1/1)100% (4/4)100% (1/1)
ClassTrackerImpl (): void 100% (1/1)100% (26/26)100% (8/8)
getClassInfo (Class, Object): ClassInfo 100% (1/1)100% (20/20)100% (3/3)
getType (Class): ClassTracker$ClassType 100% (1/1)100% (26/26)100% (9/9)
handle (PersistenceEvent): void 100% (1/1)100% (11/11)100% (3/3)
init (Map): void 100% (1/1)100% (22/22)100% (6/6)
isStorable (ClassEntry): boolean 100% (1/1)100% (32/32)100% (7/7)
release (): void 100% (1/1)100% (5/5)100% (2/2)

1/**
2 * Copyright (C) 2006 NetMind Consulting Bt.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 3 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 */
18 
19package hu.netmind.beankeeper.model.impl;
20 
21import java.util.Map;
22import java.util.HashMap;
23import java.util.List;
24import java.util.Iterator;
25import java.util.Date;
26import java.util.ArrayList;
27import java.util.HashSet;
28import java.util.Stack;
29import java.lang.reflect.Modifier;
30import hu.netmind.beankeeper.parser.QueryStatement;
31import hu.netmind.beankeeper.event.EventDispatcher;
32import hu.netmind.beankeeper.event.PersistenceEventListener;
33import hu.netmind.beankeeper.event.PersistenceEvent;
34import hu.netmind.beankeeper.transaction.event.TransactionEvent;
35import hu.netmind.beankeeper.transaction.event.TransactionCommittedEvent;
36import hu.netmind.beankeeper.transaction.event.TransactionRolledbackEvent;
37import hu.netmind.beankeeper.node.event.NodeStateChangeEvent;
38import hu.netmind.beankeeper.common.StoreException;
39import hu.netmind.beankeeper.type.TypeHandler;
40import hu.netmind.beankeeper.model.*;
41import hu.netmind.beankeeper.transaction.Transaction;
42import hu.netmind.beankeeper.transaction.InternalTransactionTracker;
43import hu.netmind.beankeeper.db.Database;
44import hu.netmind.beankeeper.db.Limits;
45import hu.netmind.beankeeper.db.SearchResult;
46import hu.netmind.beankeeper.object.Identifier; 
47import hu.netmind.beankeeper.node.NodeManager;
48import hu.netmind.beankeeper.type.TypeHandlerTracker;
49import org.apache.log4j.Logger;
50 
51/**
52 * This class keeps track of different classes and objects. It's main
53 * purpose is to implement object reflection based logic, and provide
54 * type information.
55 * @author Brautigam Robert
56 * @version Revision: $Revision$
57 */
58public class ClassTrackerImpl implements ClassTracker, PersistenceEventListener
59{
60   private static Logger logger = Logger.getLogger(ClassTrackerImpl.class);
61   private static final long ALLOCATE_COUNT = 1000;
62   private static final long MAX_OBJECT_COUNT = Long.MAX_VALUE>>16;
63 
64   private Map currentIds = new HashMap(); // Entry -> Current object Id
65   
66   private Map classInfos; // Entry->Info mapping (locally managed)
67   private Map relatedClassEntries; // Entry->Entry list (locally managed)
68   private Map rootClassEntries; // Entry->Entry list (locally managed)
69   private Map entriesById; // Id->Entry (synced)
70   private Map maxIdByEntry; // Entry->Max Id (synced)
71   private Map classIdsByEntry; // Entry->Id (synced)
72 
73   private int classMaxId = 0;
74 
75   private EventDispatcher eventDispatcher = null; // Injected
76   private InternalTransactionTracker transactionTracker = null; // Injected
77   private TypeHandlerTracker typeHandlerTracker = null; // Injected
78   private Database database = null; // Injected
79   private NodeManager nodeManager = null; // Injected
80 
81   public void init(Map parameters)
82   {
83      eventDispatcher.registerListener(this);
84      // Init local stuff
85      classInfos = new HashMap();
86      relatedClassEntries = new HashMap();
87      rootClassEntries = new HashMap();
88      // Load everything
89      reload();
90   }
91 
92   public void release()
93   {
94      eventDispatcher.unregisterListener(this);
95   }
96 
97   /**
98    * Return whether the given class is a primitive type.
99    */
100   private boolean isPrimitive(Class clazz)
101   {
102      if ( clazz == null )
103         return false;
104      return (clazz.equals(int.class)) || 
105            (clazz.equals(short.class)) || 
106            (clazz.equals(byte.class)) || 
107            (clazz.equals(long.class)) || 
108            (clazz.equals(float.class)) || 
109            (clazz.equals(double.class)) || 
110            (clazz.equals(char.class)) ||
111            (clazz.equals(boolean.class)) ||
112            (clazz.equals(Integer.class)) || 
113            (clazz.equals(Short.class)) || 
114            (clazz.equals(Byte.class)) || 
115            (clazz.equals(Long.class)) || 
116            (clazz.equals(Float.class)) || 
117            (clazz.equals(Double.class)) || 
118            (clazz.equals(Character.class)) ||
119            (clazz.equals(String.class)) || (clazz.equals(Boolean.class)) ||
120            (clazz.equals(byte[].class)) ||
121            (clazz.equals(Date.class)) || clazz.equals(Character.class);
122   }
123   
124   /**
125    * Get the type id of given class.
126    */
127   public ClassType getType(Class clazz)
128   {
129      if ( clazz == null )
130         return ClassType.TYPE_NONE;
131      if ( typeHandlerTracker.isHandled(clazz) )
132         return ClassType.TYPE_HANDLED;
133      if ( isPrimitive(clazz) )
134         return ClassType.TYPE_PRIMITIVE;
135      // Array types not allowed
136      if ( clazz.getName().startsWith("[") )
137         return ClassType.TYPE_RESERVED;
138      // The rest is custom objects.
139      return ClassType.TYPE_OBJECT;
140   }
141 
142   /**
143    * Get class info for a class entry.
144    */
145   public ClassInfo getClassInfo(Class clazz, String dynamicName)
146   {
147      if ( clazz == null )
148         return null;
149      return getClassInfo(new ClassEntry(clazz,dynamicName));
150   }
151 
152   /**
153    * Get class info for an object and it's class.
154    */
155   public ClassInfo getClassInfo(Class clazz, Object obj)
156   {
157      if ( (obj instanceof DynamicObject) && (obj.getClass().equals(clazz)) )
158         return getClassInfo(clazz, ((DynamicObject) obj).getPersistenceDynamicName());
159      else
160         return getClassInfo(clazz, null);
161   }
162 
163   /**
164    * Get class info from a string.
165    */
166   public ClassInfo getClassInfo(String className, String dynamicName)
167   {
168      if ( className == null )
169         return null;
170      try
171      {
172         Class clazz = Class.forName(className);
173         return getClassInfo(clazz, dynamicName);
174      } catch ( StoreException e ) {
175         throw e;
176      } catch ( Exception e ) {
177         throw new StoreException("class cannot be located with name '"+className+"'",e);
178      }
179   }
180 
181   /**
182    * Get all related classes to the given entry. Related class entries are
183    * all given class' super- and sub-classes which are all storable.
184    * Calling this method on non-storable entry will result in an undefined
185    * result.
186    */
187   public List getRelatedClassEntries(ClassEntry entry)
188   {
189      getClassInfo(entry); // Register
190      synchronized ( this )
191      {
192         List result = (List) relatedClassEntries.get(entry);
193         if ( result == null )
194            return new ArrayList();
195         return new ArrayList(result);
196      }
197   }
198 
199   /**
200    * Get all subclasses of given entry, including itself.
201    */
202   public List getSubClasses(ClassEntry entry)
203   {
204      List relatedEntries = getRelatedClassEntries(entry);
205      ArrayList result = new ArrayList();
206      result.add(entry.getSourceClass());
207      for ( int i=0; i<relatedEntries.size(); i++ )
208      {
209         ClassEntry subEntry = (ClassEntry) relatedEntries.get(i);
210         if ( (entry.getSourceClass().isAssignableFrom(subEntry.getSourceClass())) &&
211               (! result.contains(subEntry.getSourceClass())) )
212            result.add(subEntry.getSourceClass());
213      }
214      if ( logger.isDebugEnabled() )
215         logger.debug("returning subclasses: "+result+", for: "+entry);
216      return result;
217   }
218 
219   /**
220    * Get all storable roots for given entry. A storable root for a
221    * storable entry is itself. A non-storable entry (such as java.lang.Object)
222    * will have potentially a lot of storable roots: All classes in the
223    * class hierarchy which are storable, but have non-storable superclasses.
224    * So, storable roots are the first storable entry in a class hierarchy
225    * path (roots of the storable sub-forest). When a query is received 
226    * for a non-stored class, the query will split into queries for all
227    * storable roots.
228    */
229   public List getStorableRootClassEntries(ClassEntry entry)
230   {
231      getClassInfo(entry); // Register
232      synchronized ( this )
233      {
234         List result = (List) rootClassEntries.get(entry);
235         if ( result == null )
236            return new ArrayList();
237         return new ArrayList(result);
238      }
239   }
240 
241   /**
242    * Get a class entry for a class id.
243    */
244   public ClassEntry getClassEntry(Integer id)
245   {
246      synchronized ( this )
247      {
248         return (ClassEntry) entriesById.get(id);
249      }
250   }
251 
252   /**
253    * Get the id for a class entry.
254    */
255   private Integer getClassEntryId(ClassEntry entry)
256   {
257      getClassInfo(entry); // Create classinfo if not yet present
258      synchronized ( this )
259      {
260         return (Integer)classIdsByEntry.get(entry);
261      }
262   }
263 
264   /**
265    * Get the next class id.
266    */
267   public Integer registerClassEntry(ClassEntry entry)
268   {
269      // Make it a server call
270      if ( nodeManager.getRole() == NodeManager.NodeRole.CLIENT )
271      {
272         return (Integer) nodeManager.callServer(ClassTracker.class.getName(),
273               "registerClassEntry",new Class[] { ClassEntry.class }, new Object[] { entry });
274      }
275      synchronized ( classInfos )
276      {
277         // Try to get class id from an already registered entry, and if
278         // id is there return it immediately.
279         Integer classId = (Integer) classIdsByEntry.get(entry);
280         if ( classId != null )
281            return classId; // Entry was already known to server
282         // Create new id
283         if ( classMaxId > 65534 )
284            throw new StoreException("can not handle this many classes, out of ids, max is 65535.");
285         classMaxId++;
286         classId = classMaxId;
287         // Now create the class entry in the classes table
288         Transaction transaction = transactionTracker.getTransaction();
289         transaction.begin();
290         try
291         {
292            // Add new class to the classes table.
293            logger.debug("entry: "+entry+" was not present in database, so adding.");
294            Map attrs = new HashMap();
295            attrs.put("classname",entry.getSourceClass().getName());
296            attrs.put("dynamic",entry.getDynamicName());
297            attrs.put("id",new Integer(classId));
298            attrs.put("maxId",new Long(0));
299            database.insert(transaction,"persistence_classes",attrs);
300         } catch ( StoreException e ) {
301            transaction.markRollbackOnly();
302            throw e;
303         } catch ( Throwable e ) {
304            transaction.markRollbackOnly();
305            throw new StoreException("unexpected error while trying to create class entry for: "+entry,e);
306         } finally {
307            transaction.commit();
308         }
309         // After class row is inserted, return the new id
310         return classId;
311      }
312   }
313 
314   /**
315    * Get the next usable id for the given entry, which
316    * is not yet allocated. These ids can be used to number
317    * objects of this entry, be sure to "allocate" the ids before
318    * you use them.
319    */
320   public Long getNextId(ClassEntry entry)
321   {
322      getClassInfo(entry); // Create classinfo if not yet present
323      // Get the beginning of the next interval which is not yet allocated
324      Long currentId = null;
325      synchronized ( this )
326      {
327         Long maxId = (Long) maxIdByEntry.get(entry);
328         // Get the next id which should be given out
329         currentId = (Long) currentIds.get(entry);
330         if ( currentId == null )
331            currentId = maxId;
332         if ( logger.isDebugEnabled() )
333            logger.debug("generating id for entry: "+entry+", max value: "+maxId+", current: "+currentId);
334         // If the id is not yet allocated, then allocate a bunch again. Note:
335         // this is a remote call here, so we have to get the start of the new
336         // interval too, which is assigned to current id here.
337         if ( currentId.longValue() >= maxId.longValue() )
338         {
339            // Set the new allocated interval
340            currentId = allocateObjectIds(entry,ALLOCATE_COUNT);
341            maxIdByEntry.put(entry,currentId+ALLOCATE_COUNT);
342         }
343         // The id is ok, so this should be the id, but also put the
344         // next one into the free ids.
345         if ( currentId.longValue()+1 >= MAX_OBJECT_COUNT )
346            throw new StoreException("maximum number of objects exceeded for a single table");
347         currentIds.put(entry,new Long(currentId.longValue()+1));
348      }
349      // Generate id
350      Integer classId = getClassEntryId(entry);
351      Identifier result = new Identifier(classId, currentId);
352      if ( logger.isDebugEnabled() )
353         logger.debug("generated id '"+result+"' for class '"+classId+"' which is: "+entry);
354      return result.getId();
355   }
356 
357   /**
358    * Allocate a given number of ids for the entry. When this method
359    * returns without exceptions, the given number of ids are successfully
360    * allocated. This means that now, you have a number range, which is
361    * guaranteed to be exclusively yours for this entry.
362    * @return The beginning of the interval allocated.
363    */
364   public long allocateObjectIds(ClassEntry entry, long count)
365   {
366      // Make it a server call
367      if ( nodeManager.getRole() == NodeManager.NodeRole.CLIENT )
368      {
369         return (Long) nodeManager.callServer(ClassTracker.class.getName(),
370               "allocateObjectIds",new Class[] { ClassEntry.class, long.class },
371               new Object[] { entry, count });
372      }
373      // Local method
374      if ( logger.isDebugEnabled() )
375         logger.debug("allocating "+count+" object ids for: "+entry);
376      getClassInfo(entry); // Initialize class if not yet initialized
377      synchronized ( this )
378      {
379         Integer classId = (Integer) classIdsByEntry.get(entry);
380         Long lastId = (Long) maxIdByEntry.get(entry);
381         if ( (lastId == null) || (classId == null) )
382            throw new StoreException("entry '"+entry+"' had no ids yet, it needs to be created first.");
383         Transaction transaction = transactionTracker.getTransaction();
384         transaction.begin();
385         try
386         {
387            Map keys = new HashMap();
388            keys.put("id",getClassEntryId(entry));
389            Map attrs = new HashMap();
390            attrs.put("maxId",new Long(lastId.longValue()+count));
391            database.save(transaction,"persistence_classes",keys,attrs);
392         } catch ( StoreException e ) {
393            transaction.markRollbackOnly();
394            throw e;
395         } catch ( Throwable e ) {
396            transaction.markRollbackOnly();
397            throw new StoreException("unexpected error.",e);
398         } finally {
399            transaction.commit();
400         }
401         if ( logger.isDebugEnabled() )
402            logger.debug("allocated "+count+" ids from "+lastId+" for "+entry);
403         maxIdByEntry.put(entry,new Long(lastId.longValue()+count));
404         return lastId;
405      }
406   }
407   
408   /**
409    * Get class information object of given class. Also, if class 
410    * information does not exist, it will be created.
411    */
412   public ClassInfo getClassInfo(ClassEntry entry)
413   {
414      if ( (entry == null) || (entry.getSourceClass()==null) )
415         return null;
416      // Compute full name
417      String fullClassName = entry.getFullName();
418      // Do class searching and creating in a synchronized block
419      ClassInfo result = null;
420      synchronized ( classInfos )
421      {
422         // Check whether we can return it quickly from cache
423         result = (ClassInfo) classInfos.get(fullClassName);
424         if ( result != null )
425         {
426            if ( logger.isDebugEnabled() )
427               logger.debug("class info for entry '"+entry+"' present, returning.");
428            return result;
429         }
430         logger.debug("class info for entry '"+entry+"' is not present");
431         // If not, then first make sure the superclass exists recursively
432         Class clazz = entry.getSourceClass();
433         if ( entry.getDynamicName() == null  )
434         {
435            // A non-dynamic class has it's superclass as 
436            // superclass (no surprises here)
437            if ( getType(clazz.getSuperclass()) == ClassType.TYPE_PRIMITIVE )
438               throw new StoreException("class "+clazz+" subclassed a primitive type ("+clazz.getSuperclass()+"), this is not allowed.");
439            if ( clazz.getSuperclass() != null )
440            {
441               getClassInfo(clazz.getSuperclass(),null);
442               // Now register all interface classes also, so
443               // we can later select this class with it's interfaces
444               // also
445               Class interfaces[] = clazz.getInterfaces();
446               for ( int i=0;i<interfaces.length; i++ )
447                  getClassInfo(interfaces[i],null);
448            }
449         } else {
450            // Dynamic class has the class with 'null' dynamic name
451            // as superclass. It has no interface classes directly
452            // associated.
453            getClassInfo(clazz,null);
454         }
455         // Now really create the class info, at this point it is guaranteed
456         // that the superclasses and superinterfaces are already there.
457         result = new ClassInfoImpl(this, entry);
458         // Check whether the class is valid. Note, that check valid
459         // assumes the class is already registered. So temporaryly
460         // register it.
461         try
462         {
463            classInfos.put(fullClassName,result);
464            checkValid(result);
465         } finally {
466            classInfos.remove(fullClassName);
467         }
468         // Get the class' id from server. Note, if class is not known, 
469         // server will create it.
470         int classId = registerClassEntry(entry);
471         // If table operation was successful, update our own data model too
472         classInfos.put(fullClassName,result);
473         updateClassEntryRelations(result.getSourceEntry());
474         entriesById.put(new Integer(classId),entry);
475         classIdsByEntry.put(entry,new Integer(classId));
476         if ( ! maxIdByEntry.containsKey(entry) )
477            maxIdByEntry.put(entry,new Long(0));
478      } // End of synchronized block
479      // Return class info
480      logger.debug("returning classinfo for entry '"+entry+"': "+result);
481      return result;
482   }
483 
484   /**
485    * Determine whether a class is valid. The current algorithm checks:
486    * <ul>
487    *    <li>Not an inner of anonymous class</li>
488    *    <li>Class can't be empty (abstract and no attributes) and have dynamic attributes.</li>
489    *    <li>Class can't have an attribute which is contained in a related
490    *    class.</li>
491    * </ul>
492    * If the class is invalid, a StoreException is thrown with the description
493    * of the error.
494    */
495   private void checkValid(ClassInfo info)
496   {
497      // Non-storables are always valid
498      if ( ! info.isStorable() )
499         return;
500      // Check that this is not an inner or anonymous class, which are
501      // not handled, so an error must be generated.
502      Class clazz = info.getSourceEntry().getSourceClass();
503      if ( clazz.getDeclaringClass() != null )
504         throw new StoreException("class '"+clazz+"' is an inner or anonymous class, it won't be handled.");
505      // Check that class has a package
506      if ( clazz.getPackage() == null )
507         throw new StoreException("class '"+clazz+"' had no package given, please define at least one level of package for each class");
508      // Check that the object can be constructed, meaning it has a default
509      // constructor (if it's non-abstract)
510      if ( (!Modifier.isAbstract(clazz.getModifiers())) && 
511               (getType(clazz)==ClassType.TYPE_OBJECT) )
512      {
513         try
514         {
515            clazz.getConstructor(new Class[0]);
516         } catch ( NoSuchMethodException e ) {
517            throw new StoreException("class invalid, it had no default constructor: "+clazz);
518         }
519      }
520      // Check emptyness against dynamic attributes
521      if ( (info.isEmpty()) && (info.hasDynamicAttributes()) )
522         throw new StoreException("class info: "+info+" is empty (abstract and has no member fields attributes), but has dynamic attributes. "+
523               "This is not allowed, because it could be mistaken for a non-storable class. If it should be stored, please declare it non-abstract, "+
524               "or if it has important attributes, declare them as normal fields.");
525      // Search for common attributes
526      List<String> attributeNames = info.getAttributeNames(info.getSourceEntry());
527      // Validate attribute name format
528      for ( String attributeName : attributeNames )
529         if ( attributeName.endsWith("_underscore") )
530            throw new StoreException("attribute can't end with '_underscore', it's reserved, attribute was: "+attributeName);
531      // Search for the root superentry. If there is no storable superentries,
532      // then this class obviously has no common attributes with other classes, except
533      // it's subclasses.
534      ClassEntry entry = info.getSourceEntry();
535      ClassEntry superEntry = entry.getSuperEntry();
536      ClassInfo superInfo = getClassInfo(superEntry);
537      while ( (superEntry!=null) && (superInfo.isStorable()) )
538      {
539         entry = superEntry;
540         superEntry = entry.getSuperEntry();
541         if ( superEntry != null )
542            superInfo = getClassInfo(superEntry);
543      }
544      // Now check all related classes and ensure, that classes with common
545      // storable ancestors don't have common attributes. Only if we have a 
546      // storable super.
547      if ( ! entry.equals(info.getSourceEntry()) )
548      {
549         List relatedEntries = getRelatedClassEntries(entry);
550         logger.debug("making sure, that no common attributes are present for entry: "+info.getSourceEntry()+", with: "+relatedEntries);
551         for ( int i=0; i<relatedEntries.size(); i++ )
552         {
553            ClassEntry relatedEntry = (ClassEntry) relatedEntries.get(i);
554            ClassInfo check = null;
555            synchronized ( classInfos )
556            {
557               check = (ClassInfo) classInfos.get(relatedEntry.getFullName());
558            }
559            if ( check == null )
560               continue; // Classinfo is not yet in, so don't bother
561            if ( ! check.isStorable() )
562               continue;
563            if ( check.equals(info) )
564               continue;
565            for ( int o=0; o<attributeNames.size(); o++ )
566            {
567               String attributeName = (String) attributeNames.get(o);
568               if ( check.getAttributeNames(relatedEntry).contains(attributeName) )
569                  throw new StoreException("class info: "+info+" had a common attribute '"+
570                        attributeName+"' with '"+check+"', and common attributes in related classes are currently not allowed");
571            }
572         }
573      }
574   }
575 
576   /**
577    * Reload the max ids from database. This is called when the connection
578    * to the server is lost, and the state of the ids is unknown.
579    */
580   private synchronized void reload()
581   {
582      // Clear all internal structures
583      entriesById = new HashMap();
584      maxIdByEntry = new HashMap();
585      classIdsByEntry = new HashMap();
586      // Try to load everything from scratch again
587      Transaction transaction = transactionTracker.getTransaction();
588      transaction.begin();
589      try
590      {
591         // First ensure that table exists
592         HashMap tableAttrs = new HashMap();
593         tableAttrs.put("classname",String.class);
594         tableAttrs.put("dynamic",String.class);
595         tableAttrs.put("id",Integer.class);
596         tableAttrs.put("maxid",Long.class);
597         ArrayList tableKeys = new ArrayList();
598         tableKeys.add("id");
599         database.ensureTable(transaction,"persistence_classes",
600               tableAttrs,tableKeys,true);
601         // Load table
602         logger.debug("reloading all max ids");
603         QueryStatement stmt = new QueryStatement("persistence_classes",null,null);
604         SearchResult result = database.search(transaction,stmt,null);
605         for ( int i=0; i<result.getResult().size(); i++ )
606         {
607            Map attributes = (Map) result.getResult().get(i);
608            String className = (String) attributes.get("classname");
609            String dynamicName = (String) attributes.get("dynamic");
610            Integer id = (Integer) attributes.get("id");
611            Long maxId = (Long) attributes.get("maxid");
612            if ( id.intValue() > classMaxId )
613               classMaxId = id.intValue();
614            try
615            {
616               Class clazz = Class.forName(className);
617               ClassEntry entry = new ClassEntry(clazz,dynamicName);
618               entriesById.put(id,entry);
619               classIdsByEntry.put(entry,id);
620               maxIdByEntry.put(entry,maxId);
621               updateClassEntryRelations(entry);
622               logger.debug("class tables loaded known class name: "+className+" ("+dynamicName+"), max id: "+maxId);
623            } catch ( StoreException e ) {
624               throw e;
625            } catch ( Exception e ) {
626               logger.warn("could not find class '"+className+"'. "+
627                     "This may only mean that a class was removed, but there are traces in the database "+
628                     "for it, in this case, you may disregard this message: "+e.getMessage()+" ("+e.getClass().getName()+")");
629            }
630         }
631      } catch ( StoreException e ) {
632         transaction.markRollbackOnly();
633         throw e;
634      } catch ( Throwable e ) {
635         transaction.markRollbackOnly();
636         throw new StoreException("unexpected error.",e);
637      } finally {
638         transaction.commit();
639      }
640   }
641 
642   /**
643    * Update class relations for a given class. This means insert this class
644    * as related to all subclasses, and mark all subclasses related for this
645    * class.
646    */
647   private void updateClassEntryRelations(ClassEntry entry)
648   {
649      // The first superclass differs when handling dynamic classes,
650      // because then, the superclass is the ordinary class of dynamic class
651      ClassEntry superEntry = entry.getSuperEntry();
652      // Enter into graph walker as initial node
653      Stack openEntries = new Stack();
654      if ( superEntry != null )
655         openEntries.push(superEntry);
656      // This class will be potentially a storable root for all it's
657      // interfaces. This is not yet sure, but we will check later.
658      ArrayList storableSuperEntries = new ArrayList();
659      ArrayList nonstorableSuperEntries = new ArrayList();
660      for ( int i=0; i<entry.getSourceClass().getInterfaces().length; i++ )
661         nonstorableSuperEntries.add(
662               new ClassEntry(entry.getSourceClass().getInterfaces()[i],null));
663      // Go through all superclasses and insert this class
664      // as related to them. Insert to interfaces and reserved classes
665      // too, because we need to know if they are related.
666      // The algorithm is a simple graph walker. All not yet visited
667      // nodes are stored in openEntries, and visited one-by-one. Note, that
668      // this works, because there is no cycle in the class hierarchy.
669      while ( ! openEntries.empty() )
670      {
671         // Get top
672         superEntry = (ClassEntry) openEntries.pop();
673         // Add entry to superclass' related entries
674         ArrayList classEntries = (ArrayList) relatedClassEntries.get(superEntry);
675         if ( classEntries == null )
676         {
677            classEntries = new ArrayList();
678            relatedClassEntries.put(superEntry,classEntries);
679         }
680         if ( ! classEntries.contains(entry) )
681            classEntries.add(entry);
682         // Insert into storable super entries if this entry
683         // is storable, to non-storable super if it's not.
684         if ( isStorable(superEntry) )
685         {
686            // Entry is storable, so we will enter this as a related
687            // class.
688            storableSuperEntries.add(superEntry);
689         } else {
690            // Entry is not storable, so potentially it's first
691            // storable root is this entry. Of course, if this entry
692            // has a storable root, then this is not the real root,
693            // but this will be detected later.
694            nonstorableSuperEntries.add(superEntry);
695         }
696         // Add superclass to open nodes
697         if ( superEntry.getSourceClass().getSuperclass() != null )
698            openEntries.push(new ClassEntry(superEntry.getSourceClass().getSuperclass(),null));
699         // Also add all interfaces as not visited
700         for ( int i=0; i<superEntry.getSourceClass().getInterfaces().length; i++ )
701            openEntries.add(new ClassEntry(superEntry.getSourceClass().getInterfaces()[i],null));
702      }
703      if ( logger.isDebugEnabled() )
704         logger.debug("class tracker determined superclasses for "+entry+", non-storable: "+nonstorableSuperEntries+", storable: "+storableSuperEntries);
705      // Now insert all storable superclasses as related to this class.
706      // If this class is not storable, this should be empty anyway.
707      ArrayList classEntries = (ArrayList) relatedClassEntries.get(entry);
708      if ( classEntries == null )
709      {
710         classEntries = new ArrayList();
711         relatedClassEntries.put(entry,classEntries);
712      }
713      classEntries.removeAll(storableSuperEntries);
714      classEntries.addAll(storableSuperEntries);
715      // Now add this class as a storable root to all non-storable
716      // superclasses and superinterfaces, which do not have a storable
717      // root which is a superclass to this entry.
718      // If following condition is false, then this is a non-storable entry, 
719      // so of course non of the non-storable superclasses will have this as storable root.
720      if ( isStorable(entry) )
721      {
722         // This entry is storable, so it's only storable root is itself
723         List rootEntries = new ArrayList();
724         rootEntries.add(entry);
725         rootClassEntries.put(entry,rootEntries);
726         // Now check it's non-storable supers one-by-one to see
727         // if this class is really their storable root.
728         for ( int i=0; i<nonstorableSuperEntries.size(); i++ )
729         {
730            ClassEntry nonstorableSuperEntry = (ClassEntry) nonstorableSuperEntries.get(i);
731            rootEntries = (List) rootClassEntries.get(nonstorableSuperEntry);
732            if ( rootEntries == null )
733            {
734               // Entry did not have a storable root yet, so this is
735               // surely a storable root.
736               rootEntries = new ArrayList();
737               rootEntries.add(entry);
738               rootClassEntries.put(nonstorableSuperEntry,rootEntries);
739            } else {
740               // Entry has some storable roots, so check them.
741               // If a root is a superclass of this class, then this
742               // class is not the storable root.
743               // If a root is a subclass of this class, that should not
744               // happen, because superclasses are always handled first!
745               boolean hasSuperRoot = false;
746               for ( int o=0; (o<rootEntries.size()) && (!hasSuperRoot); o++ )
747               {
748                  ClassEntry rootEntry = (ClassEntry) rootEntries.get(o);
749                  if ( rootEntry.getSourceClass().equals(entry.getSourceClass()) )
750                  {
751                     // The class is present as root. If this entry is
752                     // a dynamic subclass of this class, then it can't
753                     // be the root, because it superclass is already root.
754                     hasSuperRoot = true;
755                  } else {
756                     if ( rootEntry.getSourceClass().isAssignableFrom(
757                              entry.getSourceClass()) )
758                        hasSuperRoot = true; // Real superclass found
759                     if ( entry.getSourceClass().isAssignableFrom(
760                              rootEntry.getSourceClass()) )
761                     {
762                        // Subclass found, so replace it
763                        logger.debug("replacing storable root "+rootEntry+", with: "+entry);
764                        rootEntries.remove(o--); // Remove this, we found a more suitable root
765                     }
766                  }
767               }
768               if ( ! hasSuperRoot )
769               {
770                  rootEntries.add(entry);
771                  if ( logger.isDebugEnabled() )
772                     logger.debug("added "+entry+" as storable root for: "+nonstorableSuperEntry+", roots until now: "+rootEntries);
773               }
774            }
775         }
776      }
777   }
778 
779   /**
780    * This determines whether an entry is storable.
781    * Note: this is also implemented by ClassInfoImpl, but this implementation
782    * can be used during initialization.
783    */
784   private boolean isStorable(ClassEntry entry)
785   {
786      if ( getType(entry.getSourceClass()) == ClassType.TYPE_PRIMITIVE )
787         return true;
788      if ( (entry.getSourceClass().isInterface()) 
789            || (entry.getSourceClass().getName().startsWith("java")) )
790         return false; // Intefaces or java.** classes are non-storable
791      if ( (Modifier.isAbstract(entry.getSourceClass().getModifiers())) 
792            && (!StrictStaticHandler.hasStaticAttributes(entry)) )
793         return false; // Abstract superclasses which have no attributes
794      return true;
795   }
796 
797   /**
798    * Get a Class instance for a class name postfix. The given parameter
799    * is treated as a postfix for a fully qualified class name. The postfix
800    * is considered matching, when it contains whole class of package
801    * qualifiers. For example: "book" matches "hu.netmind.beankeeper.Book"
802    * class, but does not match "hu.netmind.beankeeper.CookBook". Also
803    * "persistence.book" matches "hu.netmind.beankeeper.Book", but 
804    * "tence.book" does not match to previous class.<br>
805    * If no classes are found null is returned. If more than one matching
806    * class is present, then one of them is returned (no guarantees which
807    * one is picked).
808    * @param postfix The class name postfix.
809    * @return The class info for which the postfix applies, or null.
810    */
811   public ClassEntry getMatchingClassEntry(String postfix)
812   {
813      // Get a list of all known classes from database
814      HashSet entries;
815      synchronized ( this )
816      {
817         entries = new HashSet(entriesById.values());
818      }
819      // Search for given prefix
820      postfix = postfix.toLowerCase();
821      Iterator iterator = entries.iterator();
822      while ( iterator.hasNext() )
823      {
824         // Check class
825         ClassEntry entry = (ClassEntry) iterator.next();
826         String className = entry.getFullName().toLowerCase();
827         if ( (className.endsWith(postfix)) && 
828              ( (className.length()==postfix.length()) ||
829                (className.charAt(className.length()-postfix.length()-1)=='.') )
830            )
831            return entry;
832      }
833      // None found
834      return null;
835   }
836 
837   /**
838    * Activate or discard table names added in the transaction.
839    */
840   public void handle(PersistenceEvent event)
841   {
842      if ( (event instanceof NodeStateChangeEvent) &&
843        (((NodeStateChangeEvent)event).getNewState()==NodeManager.NodeState.CONNECTED) )
844      {
845         // On each connect reload the database tables
846         reload();
847      }
848   }
849 
850}
851 
852 

[all classes][hu.netmind.beankeeper.model.impl]
EMMA 2.0.5312debian (C) Vladimir Roubtsov