001 /* 002 Copyright (C) 2001-2004 Laurent Martelli <laurent@aopsys.com> 003 Renaud Pawlak <renaud@aopsys.com> 004 005 This program is free software; you can redistribute it and/or modify 006 it under the terms of the GNU Lesser General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This program is distributed in the hope that it will be useful, 011 but WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 GNU Lesser General Public License for more details. 014 015 You should have received a copy of the GNU Lesser General Public License 016 along with this program; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ 018 019 package org.objectweb.jac.aspects.persistence; 020 021 import gnu.regexp.RE; 022 import gnu.regexp.REException; 023 import java.util.Arrays; 024 import java.util.Collection; 025 import java.util.Date; 026 import java.util.HashMap; 027 import java.util.Hashtable; 028 import java.util.Iterator; 029 import java.util.Map; 030 import java.util.Vector; 031 import org.apache.log4j.Logger; 032 import org.objectweb.jac.core.AspectComponent; 033 import org.objectweb.jac.core.BaseProgramListener; 034 import org.objectweb.jac.core.Collaboration; 035 import org.objectweb.jac.core.MethodPointcut; 036 import org.objectweb.jac.core.NameRepository; 037 import org.objectweb.jac.core.Naming; 038 import org.objectweb.jac.core.ObjectRepository; 039 import org.objectweb.jac.core.SerializedJacObject; 040 import org.objectweb.jac.core.Wrappee; 041 import org.objectweb.jac.core.Wrapper; 042 import org.objectweb.jac.core.Wrapping; 043 import org.objectweb.jac.core.rtti.ClassItem; 044 import org.objectweb.jac.core.rtti.CollectionItem; 045 import org.objectweb.jac.core.rtti.FieldItem; 046 import org.objectweb.jac.util.ExtArrays; 047 048 /** 049 * This AC defines a generic and configurable persistence aspect.<p> 050 */ 051 052 public class PersistenceAC extends AspectComponent implements PersistenceConf { 053 054 static Logger logger = Logger.getLogger("persistence"); 055 static Logger loggerCache = Logger.getLogger("persistence.cache"); 056 static Logger loggerNaming = Logger.getLogger("persistence.naming"); 057 058 /** The global identifier for a persistence root class in the 059 RTTI. */ 060 public static final String ROOT = "root"; 061 062 /** The global identifier for a persistent class in the RTTI. */ 063 public static final String PERSISTENT = "persistent"; 064 065 public static final String VALUE_CONVERTER = "valueConverter"; 066 public static final String PRELOAD_FIELD = "preloadField"; 067 068 public static final String DISABLE_PRELOAD = "PersistenceAC.DISABLE_PRELOAD"; 069 public static final String NO_CACHE = "PersistenceAC.NO_CACHE"; 070 public static final String RESTORE = "PersistenceAC.RESTORE"; 071 072 // Object -> OID 073 Hashtable oids = new Hashtable(); 074 // OID -> Object 075 Hashtable objects = new Hashtable(); 076 077 private boolean connected = false; 078 079 /** 080 * The default storage. */ 081 protected StorageSpec defaultStorage = null; 082 /** List of StorageSpec */ 083 protected Vector storages = new Vector(); 084 class StorageSpec { 085 StorageSpec(String id, 086 ClassItem storageClass, String[] parameters) 087 { 088 this.id = id; 089 this.storageClass = storageClass; 090 this.parameters = parameters; 091 } 092 public void addClasses(String classExpr) throws REException { 093 classExprs = (RE[]) 094 ExtArrays.add( 095 MethodPointcut.buildRegexp(classExpr), 096 classExprs); 097 } 098 private RE[] classExprs = new RE[0]; 099 private Storage storage; 100 private String[] parameters; 101 private ClassItem storageClass; 102 private String id; 103 synchronized Storage getStorage() { 104 if (storage == null) { 105 if (storageClass == null) { 106 throw new RuntimeException("Persistence: storage is not configured"); 107 } 108 logger.debug( 109 getClass().getName() + ".connectStorage(" 110 + storageClass + "," 111 + (parameters != null 112 ? Arrays.asList(parameters).toString() 113 : "null") 114 + ")"); 115 try { 116 storage = 117 (Storage)storageClass.newInstance( 118 ExtArrays.add(0,PersistenceAC.this,parameters,Object.class)); 119 } catch (Exception e) { 120 logger.error("Failed to connect to storage ", e); 121 } 122 } 123 return storage; 124 } 125 /** 126 * Tells wether instances of class should be stored on this storage. 127 */ 128 boolean match(ClassItem cli) { 129 if (classExprs==null) 130 return false; 131 String className = cli.getName(); 132 for (int i=0; i<classExprs.length; i++) { 133 if (classExprs[i].isMatch(className)) 134 return true; 135 } 136 return false; 137 } 138 String getId() { 139 return id; 140 } 141 } 142 143 /** 144 * Gets the storage with a given id, or null. 145 * @param id id of the storage to get. If null, returns the defaultStorage. 146 */ 147 protected Storage getStorage(String id) { 148 if (id==null) { 149 return defaultStorage.getStorage(); 150 } else { 151 Iterator it = storages.iterator(); 152 while (it.hasNext()) { 153 StorageSpec storageSpec = (StorageSpec)it.next(); 154 if (id.equals(storageSpec.getId())) 155 return storageSpec.getStorage(); 156 } 157 logger.error("No such storage: "+id); 158 return null; 159 } 160 } 161 162 protected Storage[] getStorages() { 163 Storage[] result = 164 new Storage[storages.size()+ (defaultStorage!=null ? 1 : 0)]; 165 Iterator it = storages.iterator(); 166 int i=0; 167 while (it.hasNext()) { 168 StorageSpec storageSpec = (StorageSpec)it.next(); 169 result[i] = storageSpec.getStorage(); 170 i++; 171 } 172 if (defaultStorage!=null) 173 result[i] = defaultStorage.getStorage(); 174 return result; 175 } 176 177 /** 178 * The loaded objects from the storage. */ 179 // protected Hashtable objects = new Hashtable(); 180 181 /** 182 * Close every storage 183 */ 184 public void onExit() { 185 Storage[] storages = getStorages(); 186 for (int i=0; i<storages.length; i++){ 187 storages[i].close(); 188 } 189 } 190 191 //private HashSet statics = new HashSet(); 192 193 // ---- PersistenceConf interface 194 195 public void setValueConverter(ClassItem cl, ClassItem converterClass) { 196 StringConverter converter; 197 try { 198 converter = (StringConverter)converterClass.newInstance(); 199 } catch (ClassCastException e) { 200 error("Converter class "+converterClass.getName()+ 201 " does not implement StringConverter"); 202 return; 203 } catch (Exception e) { 204 error("Failed to instantiate value converter "+converterClass.getName()); 205 return; 206 } 207 cl.setAttribute(VALUE_CONVERTER, converter); 208 } 209 210 /** 211 * This method is a callback for the timer that defines the max 212 * idle time. 213 * 214 * <p>For all the collections that have a max idle time, it checks 215 * that this time is not reached. If it is reached, it unloads the 216 * collection. 217 * 218 * @see #defineMaxIdleCheckPeriod(long) 219 * @see #maxIdle(CollectionItem,long) */ 220 221 public void checkUnload() { 222 223 Iterator it = collectionIdles.entrySet().iterator(); 224 while (it.hasNext()) { 225 Map.Entry entry = (Map.Entry) it.next(); 226 227 CollectionItem coll = (CollectionItem) entry.getKey(); 228 long idle = ((Long) entry.getValue()).longValue(); 229 230 loggerCache.debug("checking collection " + coll + " (idle=" + idle + ")"); 231 ClassItem cl = coll.getClassItem(); 232 Object[] objects = ObjectRepository.getMemoryObjects(cl); 233 for (int i = 0; i < objects.length; i++) { 234 Wrappee wrappee = (Wrappee) coll.get(objects[i]); 235 loggerCache.debug("checking wrappee " 236 + NameRepository.get().getName(objects[i]) 237 + "." + wrappee); 238 Date useDate = 239 (Date) Wrapping.invokeRoleMethod( 240 wrappee, 241 CollectionWrapper.class, 242 "getUseDate", 243 ExtArrays.emptyObjectArray); 244 Date now = new Date(); 245 long lastUsed = now.getTime() - useDate.getTime(); 246 loggerCache.debug("last used=" + lastUsed + " ms ago"); 247 if (lastUsed > idle) { 248 loggerCache.debug("unloading collection " 249 + NameRepository.get().getName(objects[i]) 250 + "." + coll); 251 Wrapping.invokeRoleMethod( 252 wrappee, 253 CollectionWrapper.class, 254 "unload", 255 ExtArrays.emptyObjectArray); 256 } 257 } 258 } 259 } 260 261 long checkPeriod = -1; 262 263 public void defineMaxIdleCheckPeriod(long period) { 264 checkPeriod = period; 265 } 266 267 HashMap collectionIdles = new HashMap(); 268 269 public void maxIdle(CollectionItem collection, long maxIdle) { 270 collectionIdles.put(collection, new Long(maxIdle)); 271 } 272 273 public void whenConfigured() { 274 if (checkPeriod == -1) { 275 checkPeriod = 200000; 276 } 277 defineTimer( 278 checkPeriod, 279 cr.getClass(PersistenceAC.class).getMethod( 280 "checkUnload"), 281 new Object[] { 282 }); 283 } 284 285 public void configureStorage(ClassItem storageClass, 286 String[] storageParameters) 287 { 288 try { 289 this.defaultStorage = 290 new StorageSpec(null,storageClass,storageParameters); 291 this.defaultStorage.addClasses("ALL"); 292 } catch (REException e) { 293 error(e.toString()); 294 } 295 } 296 297 public void configureStorage(String id, 298 ClassItem storageClass, 299 String[] storageParameters) 300 { 301 storages.add( 302 new StorageSpec(id,storageClass,storageParameters)); 303 } 304 305 public void setStorage(String classExpr, String storageId) { 306 try { 307 getStorageSpec(storageId).addClasses(classExpr); 308 } catch (REException e) { 309 error(e.toString()); 310 } 311 } 312 313 /** MethodPointcuts for static objects */ 314 Vector staticPointcuts = new Vector(); 315 316 public void registerStatics(String classExpr, String nameExpr) { 317 /* Wrap constructors with PersistenceWrapper.handleStatic() */ 318 logger.debug("registerStatics " + classExpr + " " + nameExpr); 319 staticPointcuts.add( 320 pointcut( 321 nameExpr, 322 classExpr, 323 "CONSTRUCTORS", 324 PersistenceWrapper.class.getName(), 325 null, 326 SHARED)); 327 } 328 329 /** Pointcuts for persistent objects */ 330 Vector persistentPointcuts = new Vector(); 331 332 public void makePersistent(String classExpr, String nameExpr) { 333 // Wrap modifiers, collection accessors, reference accessors 334 // (but not constructors) with PersistenceWrapper.applyPersistence() 335 persistentPointcuts.add( 336 pointcut( 337 nameExpr, 338 classExpr, 339 "MODIFIERS({!transient}) || COLACCESSORS({!transient}) || REFACCESSORS({!transient}) && !CONSTRUCTORS", 340 PersistenceWrapper.class.getName(), 341 null, 342 NOT_SHARED)); 343 } 344 345 /** 346 * Tells wether a wrappee is persistent or not. 347 * @param wrappee the wrappee 348 * @see #makePersistent(String,String) 349 */ 350 boolean isPersistent(Wrappee wrappee) { 351 Iterator it = persistentPointcuts.iterator(); 352 ClassItem cli = cr.getClass(wrappee); 353 while (it.hasNext()) { 354 MethodPointcut pointcut = (MethodPointcut) it.next(); 355 if (pointcut.isClassMatching(wrappee, cli)) 356 return true; 357 } 358 return false; 359 } 360 361 /** 362 * Tells wether a wrappee is static or not. 363 * @param wrappee the wrapee 364 * @param objName name of the wrappee 365 * @see #makePersistent(String,String) 366 */ 367 boolean isStatic(Wrappee wrappee, String objName) { 368 ClassItem cli = cr.getClass(wrappee); 369 // check the statics 370 Iterator it = staticPointcuts.iterator(); 371 while (it.hasNext()) { 372 MethodPointcut pointcut = (MethodPointcut) it.next(); 373 if (pointcut.isClassMatching(wrappee, cli) 374 && pointcut.isNameMatching(wrappee, objName)) 375 return true; 376 } 377 return false; 378 } 379 380 // ---- end of PersistenceConf 381 382 /** 383 * Returns the storage for a given class 384 * 385 * @param cli a class 386 * @return the storage of the class 387 */ 388 public Storage getStorage(ClassItem cli) { 389 Iterator it = storages.iterator(); 390 while (it.hasNext()) { 391 StorageSpec storageSpec = (StorageSpec)it.next(); 392 if (storageSpec.match(cli)) { 393 return storageSpec.getStorage(); 394 } 395 } 396 if (defaultStorage!=null) 397 return defaultStorage.getStorage(); 398 throw new RuntimeException( 399 "Cannot find storage for class "+cli.getName()); 400 } 401 402 public StorageSpec getStorageSpec(String storageId) { 403 Iterator it = storages.iterator(); 404 while (it.hasNext()) { 405 StorageSpec storageSpec = (StorageSpec)it.next(); 406 if (storageSpec.getId().equals(storageId)) { 407 return storageSpec; 408 } 409 } 410 throw new RuntimeException(); 411 } 412 413 /** 414 * Returns the storage for a given object 415 * 416 * @param cli a class 417 * @return the storage of the class 418 */ 419 public Storage getStorage(Object obj) { 420 return getStorage(cr.getClass(obj)); 421 } 422 423 /** 424 * The persistence aspect checks whether an object was in the 425 * storage when a <code>NameRepository.getObject</code> call 426 * failed. 427 * @see BaseProgramListener#whenObjectMiss(String) 428 */ 429 public void whenObjectMiss(String name) { 430 loggerNaming.debug("whenObjectMiss " + name); 431 // Loop through all the storages 432 Storage[] storages = getStorages(); 433 for (int i=0; i<storages.length; i++){ 434 try { 435 OID oid = storages[i].getOIDFromName(name); 436 if (oid != null) { 437 loggerNaming.debug("found name " + name + " -> " + oid); 438 attrdef(BaseProgramListener.FOUND_OBJECT, getObject(oid, null)); 439 return; 440 } 441 } catch (Exception e) { 442 logger.error("whenObjectMiss "+name,e); 443 } 444 } 445 } 446 447 /** 448 * Delegates naming to the storage 449 */ 450 public String whenNameObject(Object object, String name) { 451 loggerNaming.debug("whenNameObject " + object + " <- " + name); 452 if (!(object instanceof Wrappee)) 453 return name; 454 Wrappee wrappee = (Wrappee) object; 455 if (isPersistent(wrappee) && !isStatic(wrappee, name)) { 456 try { 457 name = getStorage(object).newName(object.getClass().getName()); 458 } catch (Exception e) { 459 logger.error("Failed to name object "+object,e); 460 } 461 loggerNaming.debug(" -> " + name); 462 } 463 return name; 464 } 465 466 public void getNameCounters(Map counters) { 467 try { 468 Storage[] storages = getStorages(); 469 for(int i=0; i<storages.length; i++) { 470 counters.putAll(storages[i].getNameCounters()); 471 } 472 } catch (Exception e) { 473 logger.error("getNameCounters failed",e); 474 } 475 } 476 477 public synchronized void updateNameCounters(Map counters) { 478 try { 479 Storage[] storages = getStorages(); 480 for(int i=0; i<storages.length; i++) { 481 storages[i].updateNameCounters(counters); 482 } 483 } catch (Exception e) { 484 logger.error("updateNameCounters failed",e); 485 } 486 } 487 488 /** 489 * Add an object in the list of persistent objects. 490 * 491 * @param oid the OID of the object 492 * @param object the object 493 */ 494 protected void registerObject(OID oid, Object object) { 495 logger.debug("registerObject(" + oid + "," + object.getClass() + ")"); 496 Object currentObject = objects.get(oid); 497 if (currentObject!=null) { 498 if (currentObject!=object) { 499 logger.error("registerObject "+oid+","+object,new Exception()); 500 throw new Error( 501 "PersistenceAC.registerObject("+oid+","+object+"): an object "+ 502 currentObject+" is already registered with this oid"); 503 } else { 504 logger.warn("PersistenceAC.registerObject("+oid+","+object+ 505 "): already registered"); 506 } 507 } 508 objects.put(oid, object); 509 oids.put(object, oid); 510 logger.debug("object " + oid + " added"); 511 } 512 513 /** 514 * Returns a reference to an object with a given OID. 515 * 516 * <p>Loads the object from a storage if necessary, or returns a 517 * cached object.</p> 518 * 519 * @param oid OID of the object 520 * @param newObject use this object instead of instanciating a new one 521 */ 522 synchronized Object getObject(OID oid, Object newObject) { 523 Object result = objects.get(oid); 524 try { 525 if (result != null) { 526 logger.debug("Object " + oid + " found in cache -> " + result); 527 return result; 528 } else { 529 logger.debug(this + ".Object " + oid 530 + " NOT found in cache; Loading from storage\n"); 531 Storage storage = oid.getStorage(); 532 String lClassID = storage.getClassID(oid); 533 if (lClassID == null) 534 logger.error("getClassID(" + oid + ") -> NULL"); 535 ClassItem lClass = cr.getClass(lClassID); 536 logger.debug("Class = " + lClass.getName()); 537 if (newObject == null) { 538 Naming.setName(storage.getNameFromOID(oid)); 539 Collaboration collab = Collaboration.get(); 540 collab.addAttribute(RESTORE, Boolean.TRUE); 541 try { 542 newObject = lClass.newInstance(); 543 } finally { 544 collab.removeAttribute(RESTORE); 545 } 546 } 547 Wrappee wrappee = (Wrappee) newObject; 548 //PersistenceWrapper wrapper = wrap(wrappee,oid); 549 registerObject(oid, wrappee); 550 // load the new object's fields 551 Wrapping 552 .invokeRoleMethod( 553 wrappee, 554 PersistenceWrapper.class, 555 "loadAllFields", 556 new Object[] {oid}); 557 // wrap its collections 558 Wrapping.invokeRoleMethod( 559 wrappee, 560 PersistenceWrapper.class, 561 "wrapCollections", 562 new Object[] { oid, Boolean.FALSE }); 563 logger.debug("New object " + oid + " : " + newObject); 564 logger.debug("Object loaded"); 565 result = newObject; 566 } 567 } catch (Exception e) { 568 logger.error("getObject "+oid,e); 569 } 570 return result; 571 } 572 573 public OID getOID(Wrappee wrappee) { 574 return (OID) oids.get(wrappee); 575 } 576 577 /** 578 * This method allows the deserialization of the OID of a 579 * persistent object.<p> 580 * 581 * @param orgObject the JAC object structure that is beeing 582 * deserialized 583 * @see #whenSerialized(SerializedJacObject) */ 584 585 public void whenDeserialized(SerializedJacObject orgObject) { 586 /* 587 OID oid = (OID) orgObject.getACInfos( "persistence" ); 588 if ( oid != null ) { 589 Wrappee finalObject = (Wrappee)attr("finalObject"); 590 PersistenceWrapper pw = wrap( finalObject, oid, false ); 591 //pw.setOid( oid ); 592 } 593 */ 594 } 595 596 /** 597 * This method add the OID info to the serialized JAC object when a 598 * serialization is requested by another aspect.<p> 599 * 600 * This adding allows, for instance, the OID to be transmitted to 601 * remote containers.<p> 602 * 603 * @param finalObject the object that is being serialized */ 604 605 public void whenSerialized(SerializedJacObject finalObject) { 606 /* 607 Wrappee orgObject = (Wrappee)attr("orgObject"); 608 if ( orgObject.isExtendedBy( PersistenceWrapper.class ) ) { 609 finalObject.setACInfos( 610 "persistence", orgObject.invokeRoleMethod( "getOid", ExtArrays.emptyObjectArray ) ); 611 } 612 */ 613 } 614 615 /** 616 * Load objects from the storage when required. 617 */ 618 public void whenGetObjects(Collection objects, ClassItem cl) { 619 logger.debug("PersistenceAC.whenGetObjects " + cl); 620 if (cl == null) 621 return; 622 try { 623 logger.debug("PersistenceAC.whenGetObjects " + cl); 624 Collection oids = getStorage(cl).getObjects(cl); 625 Iterator i = oids.iterator(); 626 while (i.hasNext()) { 627 OID oid = (OID) i.next(); 628 Object object = getObject(oid, null); 629 if (!objects.contains(object)) 630 objects.add(object); 631 } 632 } catch (Exception e) { 633 e.printStackTrace(); 634 } 635 } 636 637 public boolean beforeRunningWrapper( 638 Wrapper wrapper, 639 String wrappingMethod) { 640 if (attr("Persistence.disabled") != null) { 641 return false; 642 } else { 643 return true; 644 } 645 } 646 647 public void whenDeleted(Wrappee object) { 648 try { 649 OID oid = getOID(object); 650 Storage storage = oid.getStorage(); 651 if (storage != null) { 652 if (oid != null) 653 storage.deleteObject(oid); 654 whenFree(object); 655 } 656 } catch (Exception e) { 657 logger.error("whenDeleted "+object+" failed",e); 658 } 659 } 660 661 public void whenFree(Wrappee object) { 662 try { 663 OID oid = getOID(object); 664 Storage storage = oid.getStorage(); 665 if (storage != null) { 666 if (oid != null) 667 oids.remove(oid); 668 if (objects.contains(object)) 669 objects.remove(object); 670 } 671 } catch (Exception e) { 672 logger.error("whenFree "+object+" failed",e); 673 } 674 } 675 676 public void preloadField(FieldItem field) { 677 field.setAttribute(PRELOAD_FIELD, Boolean.TRUE); 678 } 679 680 public void preloadAllFields(ClassItem cl) { 681 FieldItem[] fields = cl.getFields(); 682 if (fields != null) { 683 for (int i = 0; i < fields.length; i++) { 684 fields[i].setAttribute(PRELOAD_FIELD, Boolean.TRUE); 685 } 686 } 687 } 688 689 public static boolean isFieldPreloaded(FieldItem field) { 690 Boolean value = (Boolean) field.getAttribute(PRELOAD_FIELD); 691 return value != null && value.booleanValue(); 692 } 693 694 public void disableCache(CollectionItem collection) { 695 collection.setAttribute(NO_CACHE, "true"); 696 } 697 698 public String[] getDefaultConfigs() { 699 return new String[] { 700 "org/objectweb/jac/aspects/persistence/persistence.acc", 701 "org/objectweb/jac/aspects/user/persistence.acc" }; 702 } 703 704 public static class NoSuchWrapperException extends RuntimeException { 705 } 706 707 /** 708 * Converts a String into a LongOID. 709 * @param str the string representing the OID. If it's like 710 * "<number>@<string>", the "<string>" must be a storage 711 * id. Otherwise, the defaultStorage is used 712 * @param defaultStorage storage to use to build the OID if the 713 * string does not contain an OID part. 714 */ 715 public LongOID parseLongOID(String str, Storage defaultStorage) { 716 int index = str.indexOf('@'); 717 if (index==-1) { 718 return 719 new LongOID(defaultStorage,Long.parseLong(str)); 720 } else { 721 return 722 new LongOID( 723 getStorage(str.substring(index+1)), 724 Long.parseLong(str.substring(0,index))); 725 } 726 } 727 }