| 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 | |
| 19 | package hu.netmind.beankeeper.object.impl; |
| 20 | |
| 21 | import hu.netmind.beankeeper.common.StoreException; |
| 22 | import hu.netmind.beankeeper.type.TypeHandler; |
| 23 | import hu.netmind.beankeeper.type.TypeHandlerTracker; |
| 24 | import hu.netmind.beankeeper.model.*; |
| 25 | import hu.netmind.beankeeper.object.ObjectTracker; |
| 26 | import hu.netmind.beankeeper.object.PersistenceMetaData; |
| 27 | import hu.netmind.beankeeper.object.Identifier; |
| 28 | import hu.netmind.beankeeper.logging.SnapshotLogger; |
| 29 | import java.util.*; |
| 30 | import java.lang.ref.WeakReference; |
| 31 | import org.apache.log4j.Logger; |
| 32 | |
| 33 | /** |
| 34 | * This class tracks objects' state for different transactions. |
| 35 | * Basically this tracker can answer questions about the state of a |
| 36 | * previously registered object.<br> |
| 37 | * @author Brautigam Robert |
| 38 | * @version Revision: $Revision$ |
| 39 | */ |
| 40 | public class ObjectTrackerImpl implements ObjectTracker, WeakMapListener |
| 41 | { |
| 42 | private static Logger logger = Logger.getLogger(ObjectTrackerImpl.class); |
| 43 | |
| 44 | private Random random; |
| 45 | private WeakMap objectData; |
| 46 | private HashMap sharedData; |
| 47 | |
| 48 | private ClassTracker classTracker = null; // Injected |
| 49 | private TypeHandlerTracker typeHandlerTracker = null; // Injected |
| 50 | private SnapshotLogger snapshotLogger = null; // Injected |
| 51 | |
| 52 | public void init(Map parameters) |
| 53 | { |
| 54 | random = new Random(); |
| 55 | sharedData = new HashMap(); |
| 56 | objectData = new WeakMap(snapshotLogger); |
| 57 | objectData.setListener(this); |
| 58 | } |
| 59 | |
| 60 | public void release() |
| 61 | { |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * Get the data structure for an object. |
| 66 | */ |
| 67 | public ObjectData getObjectData(Object obj) |
| 68 | { |
| 69 | // Only the objectData needs to be synchronized, because only one |
| 70 | // transaction can write these objects at a given time. So many |
| 71 | // readers may be using the objectdata at a time, but only |
| 72 | // one can write it. |
| 73 | synchronized ( objectData ) |
| 74 | { |
| 75 | return (ObjectData) objectData.get(obj); |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | /** |
| 80 | * Return whether a given attribute of a given object changed. |
| 81 | */ |
| 82 | public boolean hasChanged(ClassInfo info, Object obj, String attributeName, Map dbAttributes, |
| 83 | Long serial) |
| 84 | { |
| 85 | // If object does no exists, then all attributes are changed. If |
| 86 | // object exists, then the dbAttributes contains the current |
| 87 | // attributes, as found in the database. |
| 88 | if ( ! exists(obj) ) |
| 89 | return true; |
| 90 | // Get values |
| 91 | Object value = info.getAttributeValue(obj,attributeName); |
| 92 | Object dbValue = dbAttributes.get(attributeName); |
| 93 | // Check nulls |
| 94 | if ( (dbValue==null) && (value==null) ) |
| 95 | { |
| 96 | logger.debug("both values were null, not changed."); |
| 97 | return false; |
| 98 | } |
| 99 | if ( ((dbValue!=null) && (value==null)) || |
| 100 | ((dbValue==null) && (value!=null)) ) |
| 101 | { |
| 102 | logger.debug("one value was null, and the other non-null, so changed."); |
| 103 | return true; |
| 104 | } |
| 105 | // Check handled types |
| 106 | Class type = info.getAttributeType(attributeName); |
| 107 | if ( typeHandlerTracker.isHandled(type) ) |
| 108 | { |
| 109 | TypeHandler handler = typeHandlerTracker.getHandler(type); |
| 110 | boolean result = handler.hasChanged(info,obj,attributeName,dbAttributes,serial); |
| 111 | logger.debug("value was a tracked object, result will be: "+result); |
| 112 | return result; |
| 113 | } |
| 114 | // Check byte array |
| 115 | if ( dbValue instanceof byte[] ) |
| 116 | { |
| 117 | boolean result = ! Arrays.equals((byte[]) dbValue, (byte[]) value); |
| 118 | logger.debug("values were byte arrays, changed: "+result); |
| 119 | return result; |
| 120 | } |
| 121 | // Check custom objects, in which case the dbValue is a persistence_id, |
| 122 | // and the 'value' is an object. |
| 123 | if ( classTracker.getType(value.getClass()) == ClassTracker.ClassType.TYPE_OBJECT ) |
| 124 | { |
| 125 | boolean result = ! dbValue.equals(getIdentifier(value)); |
| 126 | logger.debug("value was a custom object, it's persistence id changed: "+result); |
| 127 | return result; |
| 128 | |
| 129 | } |
| 130 | // Fallback to equals() |
| 131 | boolean result = !value.equals(dbValue); |
| 132 | logger.debug("comparing values with equals(), changed: "+result+" ("+value.getClass().getName()+" vs. db "+dbValue.getClass().getName()+")"); |
| 133 | return result; |
| 134 | } |
| 135 | |
| 136 | /** |
| 137 | * Determines, whether two objects are of the same database instance. |
| 138 | * Two objects are the same, if their ids equal. Note however, that |
| 139 | * they do not need to contain the same values, or be of same version! |
| 140 | */ |
| 141 | public boolean equals(Object o1, Object o2) |
| 142 | { |
| 143 | Long id1 = getIdentifier(o1); |
| 144 | Long id2 = getIdentifier(o2); |
| 145 | return (o1==o2) || ((id1!=null) && (id2!=null) && (id1.equals(id2))); |
| 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Determine whether an object exists. |
| 150 | * @return True, if object exists in store. This means that searches |
| 151 | * will return the object. If this is false, the object cannot be |
| 152 | * found yet. |
| 153 | */ |
| 154 | public boolean exists(Object obj) |
| 155 | { |
| 156 | ObjectData data = getObjectData(obj); |
| 157 | if ( data == null ) |
| 158 | return false; |
| 159 | return data.currentlyExists(); |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Mark object as existent. If this is inside a transaction, |
| 164 | * the existence will only be permanent, if object is saved inside |
| 165 | * the transaction. |
| 166 | */ |
| 167 | public void makeExist(Object obj) |
| 168 | { |
| 169 | ObjectData data = getObjectData(obj); |
| 170 | if ( data != null ) |
| 171 | data.setCurrentlyExists(true); |
| 172 | } |
| 173 | |
| 174 | /** |
| 175 | * Make the object current at the given serial. If the object is |
| 176 | * more current, nothing is done. It is assumed the object exists. |
| 177 | */ |
| 178 | public void makeCurrent(Object obj, Long serial) |
| 179 | { |
| 180 | PersistenceMetaDataImpl persistenceMeta = getObjectData(obj).getMetaData(); |
| 181 | Long lastCurrentSerial = persistenceMeta.getLastCurrentSerial(); |
| 182 | if ( (serial!=null) && ((lastCurrentSerial==null) || |
| 183 | (lastCurrentSerial.longValue() < serial.longValue() )) ) |
| 184 | persistenceMeta.setLastCurrentSerial(serial); |
| 185 | } |
| 186 | |
| 187 | /** |
| 188 | * Mark an object as non-existent. This happens, if the object gets |
| 189 | * deleted, but there are some object instances that are used. |
| 190 | */ |
| 191 | public void makeUnexist(Object obj) |
| 192 | { |
| 193 | ObjectData data = getObjectData(obj); |
| 194 | if ( data != null ) |
| 195 | { |
| 196 | data.setExists(false); |
| 197 | data.setCurrentlyExists(false); |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | /** |
| 202 | * Register a non-existing object into the tracker. If the object is |
| 203 | * tracked by this tracker, then nothing is done. |
| 204 | * @param obj The object to register. |
| 205 | */ |
| 206 | public void registerObject(Object obj) |
| 207 | { |
| 208 | registerObject(obj,null,null,null,null); |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * Register an object into the tracker. This means, the object will |
| 213 | * get an id, and associated tracking data structure will be allocated. |
| 214 | * If the object given has an attribute named persistenceId, then use |
| 215 | * that to re-attach the object to the tracker. |
| 216 | * @param obj The object to register. |
| 217 | * @param id The id of object. If this is given (not null), the object is |
| 218 | * assumed to exist. |
| 219 | * @param serial The current serial, can be null if unknown. |
| 220 | * @param startSerial The start of this object, can be null, if unknown. |
| 221 | * @param endSerial The ens serial of this object, if it's deleted, can be null. |
| 222 | */ |
| 223 | public void registerObject(Object obj, Long id, Long serial, |
| 224 | Long startSerial, Long endSerial) |
| 225 | { |
| 226 | ObjectData data = getObjectData(obj); |
| 227 | if ( data != null ) |
| 228 | return; |
| 229 | // Create object data |
| 230 | data = new ObjectData(); |
| 231 | if ( id == null ) |
| 232 | { |
| 233 | // Check, whether object has 'persistenceId' given |
| 234 | ClassInfo info = classTracker.getClassInfo(obj.getClass(),obj); |
| 235 | if ( (info.getAttributeNames().contains("persistenceid")) && |
| 236 | (info.getAttributeValue(obj,"persistenceid")!=null) && |
| 237 | (((Long)info.getAttributeValue(obj,"persistenceid")).longValue()!=0) ) |
| 238 | { |
| 239 | Long persistenceId = (Long) info.getAttributeValue(obj,"persistenceid"); |
| 240 | ClassEntry idEntry = classTracker.getClassEntry( |
| 241 | new Identifier(persistenceId).getClassId()); |
| 242 | if ( ! info.getSourceEntry().equals(idEntry) ) |
| 243 | throw new StoreException("trying to re-attach object, but given persistenceid: "+ |
| 244 | persistenceId+" indicates entry: "+idEntry+", object is: "+info.getSourceEntry()); |
| 245 | // Object must be re-attached. Re-attached objects |
| 246 | // are assumed to exists. If the user assigns false ids |
| 247 | // to objects, this would mean the exists() function would |
| 248 | // return false information. |
| 249 | data.setId(persistenceId); |
| 250 | data.setExists(true); |
| 251 | data.setCurrentlyExists(true); |
| 252 | if ( logger.isDebugEnabled() ) |
| 253 | logger.debug("object re-attached as: "+persistenceId); |
| 254 | } else { |
| 255 | // This is a whole new object, so create new id |
| 256 | data.setId(classTracker.getNextId(new ClassEntry(obj))); |
| 257 | data.setExists(false); |
| 258 | data.setCurrentlyExists(false); |
| 259 | if ( logger.isDebugEnabled() ) |
| 260 | logger.debug("aquired new id to new object: "+data.getId()); |
| 261 | } |
| 262 | } else { |
| 263 | data.setId(id); |
| 264 | data.setExists(true); |
| 265 | data.setCurrentlyExists(true); |
| 266 | if ( logger.isDebugEnabled() ) |
| 267 | logger.debug("object loaded with id: "+data.getId()); |
| 268 | } |
| 269 | PersistenceMetaDataImpl metaData = new PersistenceMetaDataImpl(); |
| 270 | data.setMetaData(metaData); |
| 271 | metaData.setPersistenceId(data.getId()); |
| 272 | metaData.setPersistenceStart(startSerial); |
| 273 | metaData.setPersistenceEnd(endSerial); |
| 274 | metaData.setRegistrationSerial(serial); |
| 275 | metaData.setLastCurrentSerial(serial); |
| 276 | metaData.setObjectClass(obj.getClass()); |
| 277 | synchronized ( objectData ) |
| 278 | { |
| 279 | objectData.put(obj,data,data.getId()); |
| 280 | // Create shared state, if not present |
| 281 | SharedData sData = data.getSharedData(); |
| 282 | if ( sData == null ) |
| 283 | sData = new SharedData(); |
| 284 | else |
| 285 | sData.setRefCounter(sData.getRefCounter()+1); |
| 286 | sharedData.put(data.getId(),sData); |
| 287 | } |
| 288 | // Profile |
| 289 | snapshotLogger.log("sharedmap","Shared data size is: "+sharedData.size()); |
| 290 | } |
| 291 | |
| 292 | /** |
| 293 | * Called when an object leaves the weak map. Note: This is thread-safe |
| 294 | * because it is called from WeakMap, which is handled safely in this |
| 295 | * class. |
| 296 | */ |
| 297 | public void notifyValueLeave(Object id) |
| 298 | { |
| 299 | // Object is leaving weakmap, so remove shared state too if nescessary. |
| 300 | SharedData data = (SharedData) sharedData.get(id); |
| 301 | // Decrease count, remove if no object reference it |
| 302 | data.setRefCounter(data.getRefCounter()-1); |
| 303 | if ( data.getRefCounter() <= 0 ) |
| 304 | sharedData.remove(id); |
| 305 | } |
| 306 | |
| 307 | /** |
| 308 | * Get the identifier for a given object. |
| 309 | * @param obj The object to identify. |
| 310 | * @return The identifier, or 0 if the object is not registered. |
| 311 | */ |
| 312 | public Long getIdentifier(Object obj) |
| 313 | { |
| 314 | ObjectData data = getObjectData(obj); |
| 315 | if ( data == null ) |
| 316 | return null; |
| 317 | return data.getId(); |
| 318 | } |
| 319 | |
| 320 | /** |
| 321 | * Get the meta data associated with the object. |
| 322 | */ |
| 323 | public PersistenceMetaData getMetaData(Object obj) |
| 324 | { |
| 325 | // Register to be sure |
| 326 | registerObject(obj); |
| 327 | // Get the metadata |
| 328 | return getObjectData(obj).getMetaData(); |
| 329 | } |
| 330 | |
| 331 | /** |
| 332 | * Get the current attributes of an object. |
| 333 | * @return The current attributes of the object, null |
| 334 | * if it's not known. |
| 335 | */ |
| 336 | public Map getCurrentAttributes(Object obj) |
| 337 | { |
| 338 | ObjectData data = getObjectData(obj); |
| 339 | return data.getSharedData().attributes; |
| 340 | } |
| 341 | |
| 342 | /** |
| 343 | * The object commited, all known changes should be made permanent now. |
| 344 | * @param obj The object to update. |
| 345 | * @param serial The serial of this update. |
| 346 | */ |
| 347 | public void updateCommit(Object obj, Long serial) |
| 348 | { |
| 349 | ObjectData data = (ObjectData) getObjectData(obj); |
| 350 | if ( data == null ) |
| 351 | { |
| 352 | logger.warn("something may be wrong, an object commited which was not tracked, object was: "+obj); |
| 353 | } else { |
| 354 | // Set object transaction exists to stable exists |
| 355 | data.setExists(data.currentlyExists()); |
| 356 | // Commit transactional attributes |
| 357 | SharedData sData = data.getSharedData(); |
| 358 | sData.originalAttributes = null; |
| 359 | } |
| 360 | } |
| 361 | |
| 362 | /** |
| 363 | * Indicate that the object is current at the given serial for sure. |
| 364 | * @param obj The object to update. |
| 365 | * @param serial The serial on which object was sure to be current. |
| 366 | */ |
| 367 | public void updateCurrent(Object obj, Long serial) |
| 368 | { |
| 369 | // Update meta-data |
| 370 | PersistenceMetaDataImpl metaData = (PersistenceMetaDataImpl) getMetaData(obj); |
| 371 | if ( metaData.getPersistenceStart() == null ) |
| 372 | metaData.setPersistenceStart(serial); |
| 373 | metaData.setLastCurrentSerial(serial); |
| 374 | } |
| 375 | |
| 376 | /** |
| 377 | * The object rolled back, all known changes should be rolled back too. |
| 378 | * @param obj The object to update. |
| 379 | * @param serial The serial of this update. |
| 380 | */ |
| 381 | public void updateRollback(Object obj, Long serial) |
| 382 | { |
| 383 | ObjectData data = (ObjectData) getObjectData(obj); |
| 384 | if ( data == null ) |
| 385 | { |
| 386 | logger.warn("something may be wrong, an object rolled back which was not tracked, object was: "+obj); |
| 387 | } else { |
| 388 | // Set current exists flag to stable exists flag |
| 389 | data.setCurrentlyExists(data.exists()); |
| 390 | // Clear transactional attributes |
| 391 | SharedData sData = data.getSharedData(); |
| 392 | sData.attributes = sData.originalAttributes; |
| 393 | sData.originalAttributes = null; |
| 394 | } |
| 395 | } |
| 396 | |
| 397 | /** |
| 398 | * Update the object's data to indicate object is known to be current |
| 399 | * at the given serial. It is assumed that the object exists. |
| 400 | * @param obj The object to update. |
| 401 | * @param serial The serial of this update. |
| 402 | */ |
| 403 | public void updateObject(Object obj, Long serial, Long startSerial, Long endSerial) |
| 404 | { |
| 405 | PersistenceMetaDataImpl metaData = getObjectData(obj).getMetaData(); |
| 406 | if ( serial != null ) |
| 407 | metaData.setLastCurrentSerial(serial); |
| 408 | if ( endSerial != null ) |
| 409 | metaData.setPersistenceEnd(endSerial); |
| 410 | if ( startSerial != null ) |
| 411 | metaData.setPersistenceStart(startSerial); |
| 412 | } |
| 413 | |
| 414 | /** |
| 415 | * Update the current state of the given object. |
| 416 | * @param obj The object to update. |
| 417 | * @param attributes The attributes which indicate the _current_ state of |
| 418 | * the object. If this attribute is null, then this indicates, that |
| 419 | * the object has changed, but the current state is not known. |
| 420 | */ |
| 421 | public void updateObject(Object obj, Map attributes) |
| 422 | { |
| 423 | synchronized ( objectData ) |
| 424 | { |
| 425 | // Get data |
| 426 | ObjectData data = getObjectData(obj); |
| 427 | updateObjectId(data.getId(),attributes); |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | /** |
| 432 | * Update the current state of the given object. |
| 433 | * @param id The id of the object to update. |
| 434 | * @param attributes The attributes which indicate the _current_ state of |
| 435 | * the object. If this attribute is null, then this indicates, that |
| 436 | * the object has changed, but the current state is not known. |
| 437 | */ |
| 438 | public void updateObjectId(Long id, Map attributes) |
| 439 | { |
| 440 | synchronized ( objectData ) |
| 441 | { |
| 442 | SharedData sData = (SharedData) sharedData.get(id); |
| 443 | if ( sData == null ) |
| 444 | return; |
| 445 | // Set state |
| 446 | if ( attributes == null ) |
| 447 | { |
| 448 | sData.attributes = null; |
| 449 | sData.originalAttributes = null; |
| 450 | } else { |
| 451 | sData.attributes=attributes; |
| 452 | if ( sData.originalAttributes == null ) |
| 453 | sData.originalAttributes=attributes; |
| 454 | } |
| 455 | } |
| 456 | } |
| 457 | |
| 458 | /** |
| 459 | * Get an object wrapper for an object which disregards object's |
| 460 | * own equals() and hashCode() methods. |
| 461 | */ |
| 462 | public ObjectWrapper getWrapper(Object obj) |
| 463 | { |
| 464 | return new ObjectWrapperImpl(this,obj); |
| 465 | } |
| 466 | |
| 467 | /** |
| 468 | * Search for an item in a list based on id. |
| 469 | */ |
| 470 | /** |
| 471 | * This is the structure tracked for an object. |
| 472 | */ |
| 473 | public class ObjectData implements Comparable |
| 474 | { |
| 475 | private Long id; |
| 476 | private boolean exists; |
| 477 | private boolean currentlyExists; // Inside transaction it may differ from 'exists'. |
| 478 | private PersistenceMetaDataImpl metaData; |
| 479 | |
| 480 | public ObjectData() |
| 481 | { |
| 482 | } |
| 483 | |
| 484 | public PersistenceMetaDataImpl getMetaData() |
| 485 | { |
| 486 | return metaData; |
| 487 | } |
| 488 | public void setMetaData(PersistenceMetaDataImpl metaData) |
| 489 | { |
| 490 | this.metaData=metaData; |
| 491 | } |
| 492 | |
| 493 | public int compareTo(Object obj) |
| 494 | { |
| 495 | return id.compareTo(((ObjectData)obj).id); |
| 496 | } |
| 497 | |
| 498 | public SharedData getSharedData() |
| 499 | { |
| 500 | return (SharedData) sharedData.get(id); |
| 501 | } |
| 502 | |
| 503 | public Long getId() |
| 504 | { |
| 505 | return id; |
| 506 | } |
| 507 | private void setId(Long id) |
| 508 | { |
| 509 | this.id=id; |
| 510 | } |
| 511 | |
| 512 | public boolean exists() |
| 513 | { |
| 514 | return exists; |
| 515 | } |
| 516 | private void setExists(boolean exists) |
| 517 | { |
| 518 | this.exists=exists; |
| 519 | } |
| 520 | private boolean currentlyExists() |
| 521 | { |
| 522 | return currentlyExists; |
| 523 | } |
| 524 | public void setCurrentlyExists(boolean currentlyExists) |
| 525 | { |
| 526 | this.currentlyExists=currentlyExists; |
| 527 | } |
| 528 | } |
| 529 | |
| 530 | /** |
| 531 | * This is a structure which is shared for objects of the same id. |
| 532 | */ |
| 533 | public class SharedData |
| 534 | { |
| 535 | private Map attributes; // Attributes of _current_ version |
| 536 | private Map originalAttributes; // Original attributes before tx |
| 537 | private int refCounter; |
| 538 | |
| 539 | public SharedData() |
| 540 | { |
| 541 | refCounter=1; |
| 542 | } |
| 543 | |
| 544 | public int getRefCounter() |
| 545 | { |
| 546 | return refCounter; |
| 547 | } |
| 548 | public void setRefCounter(int refCounter) |
| 549 | { |
| 550 | this.refCounter=refCounter; |
| 551 | } |
| 552 | |
| 553 | } |
| 554 | |
| 555 | public class ObjectWrapperImpl implements ObjectWrapper |
| 556 | { |
| 557 | private Object obj; |
| 558 | private Long id; |
| 559 | |
| 560 | public ObjectWrapperImpl(ObjectTracker objectTracker, Object obj) |
| 561 | { |
| 562 | this.obj=obj; |
| 563 | registerObject(obj); |
| 564 | this.id=objectTracker.getIdentifier(obj); |
| 565 | } |
| 566 | |
| 567 | public Long getIdentifier() |
| 568 | { |
| 569 | return id; |
| 570 | } |
| 571 | |
| 572 | public Object getObject() |
| 573 | { |
| 574 | return obj; |
| 575 | } |
| 576 | |
| 577 | public int hashCode() |
| 578 | { |
| 579 | return (int) (id.longValue()>>32); |
| 580 | } |
| 581 | |
| 582 | public boolean equals(Object rhs) |
| 583 | { |
| 584 | if ( ! (rhs instanceof ObjectWrapperImpl) ) |
| 585 | return false; |
| 586 | return id.equals(((ObjectWrapperImpl) rhs).id); |
| 587 | } |
| 588 | } |
| 589 | } |
| 590 | |