| 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.store.impl; |
| 20 | |
| 21 | import org.apache.log4j.Logger; |
| 22 | import java.util.*; |
| 23 | import hu.netmind.beankeeper.common.StoreException; |
| 24 | import hu.netmind.beankeeper.store.StoreService; |
| 25 | import hu.netmind.beankeeper.serial.SerialTracker; |
| 26 | import hu.netmind.beankeeper.lock.LockTracker; |
| 27 | import hu.netmind.beankeeper.config.ConfigurationTracker; |
| 28 | import hu.netmind.beankeeper.parser.*; |
| 29 | import hu.netmind.beankeeper.node.*; |
| 30 | import hu.netmind.beankeeper.event.EventDispatcher; |
| 31 | import hu.netmind.beankeeper.event.PersistenceEvent; |
| 32 | import hu.netmind.beankeeper.event.PersistenceEventListener; |
| 33 | import hu.netmind.beankeeper.model.*; |
| 34 | import hu.netmind.beankeeper.transaction.Transaction; |
| 35 | import hu.netmind.beankeeper.transaction.TransactionStatistics; |
| 36 | import hu.netmind.beankeeper.transaction.TransactionTracker; |
| 37 | import hu.netmind.beankeeper.db.SearchResult; |
| 38 | import hu.netmind.beankeeper.db.Database; |
| 39 | import hu.netmind.beankeeper.object.PersistenceMetaData; |
| 40 | import hu.netmind.beankeeper.object.ObjectTracker; |
| 41 | import hu.netmind.beankeeper.object.Identifier; |
| 42 | import hu.netmind.beankeeper.serial.Serial; |
| 43 | import hu.netmind.beankeeper.type.TypeHandler; |
| 44 | import hu.netmind.beankeeper.type.TypeHandlerTracker; |
| 45 | import hu.netmind.beankeeper.management.ManagementTracker; |
| 46 | import hu.netmind.beankeeper.node.NodeManager; |
| 47 | import hu.netmind.beankeeper.query.QueryService; |
| 48 | import hu.netmind.beankeeper.schema.SchemaManager; |
| 49 | import hu.netmind.beankeeper.store.event.*; |
| 50 | import hu.netmind.beankeeper.transaction.event.*; |
| 51 | import javax.sql.DataSource; |
| 52 | |
| 53 | /** |
| 54 | * This store class is the entry point to the persistence library. To |
| 55 | * store, remove or select given objects, just use the appropriate |
| 56 | * methods. |
| 57 | * @author Brautigam Robert |
| 58 | * @version Revision: $Revision$ |
| 59 | */ |
| 60 | public class StoreServiceImpl implements StoreService |
| 61 | { |
| 62 | private static Logger logger = Logger.getLogger(StoreServiceImpl.class); |
| 63 | private static Logger operationsLogger = Logger.getLogger("hu.netmind.beankeeper.operations"); |
| 64 | |
| 65 | private StoreStatistics storeStatistics = null; |
| 66 | private Map<Transaction,TransactionContentImpl> transactionContent = new HashMap<Transaction,TransactionContentImpl>(); |
| 67 | |
| 68 | private EventDispatcher eventDispatcher = null; // Injected |
| 69 | private ManagementTracker managementTracker = null; // Injected |
| 70 | private TransactionTracker transactionTracker = null; // Injected |
| 71 | private ObjectTracker objectTracker = null; // Injected |
| 72 | private NodeManager nodeManager = null; // Injected |
| 73 | private SerialTracker serialTracker = null; // Injected |
| 74 | private Database database = null; // Injected |
| 75 | private ClassTracker classTracker = null; // Injected |
| 76 | private LockTracker lockTracker = null; // Injected |
| 77 | private TypeHandlerTracker typeHandlerTracker = null; // Injected |
| 78 | private ConfigurationTracker config = null; // Injected |
| 79 | private QueryService queryService = null; // Injected |
| 80 | private SchemaManager schemaManager = null; // Injected |
| 81 | |
| 82 | /** |
| 83 | * Construct this store with the given parameters. |
| 84 | */ |
| 85 | public void init(Map parameters) |
| 86 | { |
| 87 | // {{{ Init the store |
| 88 | // Register as event listener |
| 89 | eventDispatcher.registerListener(new HighTransactionListener(), |
| 90 | EventDispatcher.PRI_SYSTEM_HIGH); |
| 91 | eventDispatcher.registerListener(new LowTransactionListener(), |
| 92 | EventDispatcher.PRI_SYSTEM_LOW); |
| 93 | // Create the operations mbean and register |
| 94 | storeStatistics = new StoreStatistics(); |
| 95 | managementTracker.registerBean("StoreStatistics",storeStatistics); |
| 96 | // }}} |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * Release resources. |
| 101 | */ |
| 102 | public void release() |
| 103 | { |
| 104 | // {{{ |
| 105 | managementTracker.deregisterBean("StoreStatistics"); |
| 106 | // }}} |
| 107 | } |
| 108 | |
| 109 | /** |
| 110 | * Get or allocate the transaction content. |
| 111 | * @return A transaction content for the transaction given. |
| 112 | */ |
| 113 | public TransactionContentImpl getTransactionContent(Transaction transaction) |
| 114 | { |
| 115 | TransactionContentImpl content = null; |
| 116 | synchronized ( transactionContent ) |
| 117 | { |
| 118 | content = transactionContent.get(transaction); |
| 119 | if ( content == null ) |
| 120 | { |
| 121 | content = new TransactionContentImpl(); |
| 122 | transactionContent.put(transaction,content); |
| 123 | } |
| 124 | } |
| 125 | return content; |
| 126 | } |
| 127 | |
| 128 | /** |
| 129 | * Save the given object to the store. |
| 130 | * @param obj The object to save. |
| 131 | * @throws StoreException If save is not successfull. |
| 132 | */ |
| 133 | public void save(Object obj) |
| 134 | { |
| 135 | // {{{ Save |
| 136 | // Transaction |
| 137 | Transaction transaction = transactionTracker.getTransaction(TransactionTracker.TX_REQUIRED); |
| 138 | transaction.begin(); |
| 139 | TransactionStatistics startStats = new TransactionStatistics(); |
| 140 | startStats.add(transaction.getStats()); |
| 141 | long startTime = System.currentTimeMillis(); |
| 142 | try |
| 143 | { |
| 144 | // Get transaction content |
| 145 | TransactionContentImpl content = getTransactionContent(transaction); |
| 146 | // Get serials |
| 147 | Long currentSerial = serialTracker.getNextSerial(); |
| 148 | Long currentTxSerial = transaction.getSerial(); |
| 149 | logger.debug("save called, using serial: "+currentSerial+", tx serial: "+currentTxSerial); |
| 150 | // Save object or insert, possibly recursively. |
| 151 | // The waitingObjects list is an ordered list which |
| 152 | // contains all objects currently waiting for saving. |
| 153 | // First it contains the object to be saved, then if |
| 154 | // it referenced more objects, those will be appended to the |
| 155 | // list. An object can only be saved, when all referencing objects |
| 156 | // are all at least created. |
| 157 | LinkedList events = new LinkedList(); |
| 158 | LinkedList savedHandledObjects = new LinkedList(); |
| 159 | HashSet waitingObjects = new HashSet(); |
| 160 | waitingObjects.add(objectTracker.getWrapper(obj)); |
| 161 | while ( waitingObjects.size() > 0 ) |
| 162 | { |
| 163 | if ( logger.isDebugEnabled() ) |
| 164 | logger.debug("saving object, waiting list: "+waitingObjects.size()+", memory: "+Runtime.getRuntime().freeMemory()); |
| 165 | // {{{ Selecting objects which wait for saving |
| 166 | // Get the last object (the bottom of dependency tree) |
| 167 | ObjectTracker.ObjectWrapper currentWrapper = (ObjectTracker.ObjectWrapper) waitingObjects.iterator().next(); |
| 168 | Object current = currentWrapper.getObject(); |
| 169 | // Now get object's class info. This method does not |
| 170 | // return null. If the class info is not available, it |
| 171 | // creates it, if it is available it checks whether to |
| 172 | // update the info in database (new attibutes, etc) |
| 173 | ClassInfo classInfo = classTracker.getClassInfo(current.getClass(),current); |
| 174 | logger.debug("object class info is: "+classInfo); |
| 175 | // Makeing sure schema is up to date |
| 176 | schemaManager.ensureSchema(classInfo.getSourceEntry()); |
| 177 | // If object does not exists, register it with object |
| 178 | // tracker. Note, that object tracker only leases an id |
| 179 | // and exists() will return false until object commited. |
| 180 | // Also, if object already has an id, this will do nothing. |
| 181 | objectTracker.registerObject(current); |
| 182 | Long currentId = objectTracker.getIdentifier(current); |
| 183 | logger.debug("saving object with id: "+currentId); |
| 184 | // Lock object |
| 185 | lockTracker.lock(current); |
| 186 | content.addSavedObject(currentWrapper); |
| 187 | // Assemble changed attributes of this object into a Map. |
| 188 | // If an attribute is not a primitive type it is checked, |
| 189 | // if it exists. If it does not, it is appended to waitingObjects |
| 190 | // and we start from beginning with this object. If it exists, |
| 191 | // then the reference id will be inserted into the change Map. |
| 192 | Map changedAttributes = new HashMap(); |
| 193 | // }}} |
| 194 | // {{{ Assemble previous state of object |
| 195 | Map originalAttributes = objectTracker.getCurrentAttributes(current); |
| 196 | TimeControl originalTimeControl = new TimeControl(currentSerial,currentTxSerial,true); |
| 197 | if ( (objectTracker.exists(current)) && (originalAttributes==null) ) |
| 198 | { |
| 199 | // First, get all original attributes from |
| 200 | // the database, because we'll have to create a full row, |
| 201 | // even when a single attribute changed. |
| 202 | ClassEntry selectClassEntry = classInfo.getSourceEntry(); |
| 203 | SpecifiedTableTerm selectTerm = new SpecifiedTableTerm(schemaManager.getTableName(selectClassEntry),null); |
| 204 | List relatedClassEntries = classTracker.getRelatedClassEntries(selectClassEntry); |
| 205 | for ( int u=0; u<relatedClassEntries.size(); u++ ) |
| 206 | { |
| 207 | ClassEntry relatedClassEntry = (ClassEntry) relatedClassEntries.get(u); |
| 208 | ClassInfo relatedClassInfo = classTracker.getClassInfo(relatedClassEntry); |
| 209 | if ( relatedClassInfo == null ) |
| 210 | throw new ParserException(ParserException.ABORT,"object class not found for loading: '"+relatedClassEntry+"'"); |
| 211 | // Create the term and add to mainterm |
| 212 | TableTerm leftTerm = new TableTerm(schemaManager.getTableName(relatedClassEntry),null); |
| 213 | SpecifiedTableTerm.LeftjoinEntry joinEntry = new SpecifiedTableTerm.LeftjoinEntry(); |
| 214 | Expression joinExpr = new Expression(); |
| 215 | joinExpr.add(new ReferenceTerm(selectTerm,"persistence_id")); |
| 216 | joinExpr.add("="); |
| 217 | joinExpr.add(new ReferenceTerm(leftTerm,"persistence_id")); |
| 218 | joinExpr.add("and"); |
| 219 | originalTimeControl.apply(joinExpr,leftTerm); |
| 220 | joinEntry.expression=joinExpr; |
| 221 | joinEntry.term=leftTerm; |
| 222 | selectTerm.getRelatedLeftTerms().add(joinEntry); |
| 223 | } |
| 224 | Expression expr = new Expression(); |
| 225 | expr.add(new ReferenceTerm(selectTerm,"persistence_id")); |
| 226 | expr.add("="); |
| 227 | expr.add(new ConstantTerm(currentId)); |
| 228 | expr.add("and"); |
| 229 | originalTimeControl.apply(expr,selectTerm); |
| 230 | QueryStatement referredStatement = new QueryStatement(selectTerm,expr,null); |
| 231 | referredStatement.setStaticRepresentation("FIND "+selectClassEntry+" where persistence_id = "+currentId); |
| 232 | referredStatement.setTimeControl(originalTimeControl); |
| 233 | SearchResult referredResult = queryService.find(referredStatement,null); |
| 234 | if ( referredResult.getResultSize() > 1 ) |
| 235 | { |
| 236 | throw new StoreException("object's last state was ambigous, results for object id '"+currentId+"' was: "+referredResult.getResult()); |
| 237 | } else if ( referredResult.getResultSize() == 1 ) { |
| 238 | // Set original attributes from this last state of object |
| 239 | originalAttributes = (Map) referredResult.getResult().get(0); |
| 240 | if ( logger.isDebugEnabled() ) |
| 241 | logger.debug("object original attributes: "+originalAttributes); |
| 242 | } else { |
| 243 | // Object was not found, it most likely was deleted |
| 244 | // so mark object as non-existent. This is a trick to |
| 245 | // again save all attributes to database. For example, |
| 246 | // when selecting an old instance, it is possible, the |
| 247 | // instance is deleted in present time, so we must save |
| 248 | // all attributes. |
| 249 | objectTracker.makeUnexist(current); |
| 250 | } |
| 251 | } else { |
| 252 | if ( logger.isDebugEnabled() ) |
| 253 | logger.debug("object did not exist, or original attributes known: "+originalAttributes); |
| 254 | } |
| 255 | // Set attribute maps correctly: |
| 256 | // - nonchangedAttributes will be modified to contain only modified attrs |
| 257 | // - originalAttributes won't be modified |
| 258 | // - newAttributes will be the current state |
| 259 | Map nonchangedAttributes = new HashMap(); |
| 260 | Map newAttributes = new HashMap(); |
| 261 | if ( originalAttributes != null ) |
| 262 | { |
| 263 | nonchangedAttributes = new HashMap(originalAttributes); |
| 264 | newAttributes = new HashMap(originalAttributes); |
| 265 | } else { |
| 266 | originalAttributes = new HashMap(); |
| 267 | } |
| 268 | // }}} |
| 269 | // {{{ Assembling changed attributes |
| 270 | List attributeNames = classInfo.getAttributeNames(); |
| 271 | if ( logger.isDebugEnabled() ) |
| 272 | logger.debug("class tracker reported object to save has following attributes: "+ |
| 273 | attributeNames+", it has id: "+currentId+", object tracker says it exists: "+objectTracker.exists(current)); |
| 274 | for ( int i=0; i<attributeNames.size(); i++ ) |
| 275 | { |
| 276 | String attributeName = (String) attributeNames.get(i); |
| 277 | String attributeNameLowerCase = attributeName.toLowerCase(); |
| 278 | // Do not handle special attributes |
| 279 | if ( ("persistence_id".equals(attributeNameLowerCase)) || |
| 280 | ("persistenceid".equals(attributeNameLowerCase)) ) |
| 281 | { |
| 282 | // We found a persistence id, fill it with id |
| 283 | classInfo.setAttributeValue(current,attributeName,currentId); |
| 284 | continue; |
| 285 | } |
| 286 | // Skip reserved prefix'd attributes |
| 287 | if ( attributeNameLowerCase.startsWith("persistence") ) |
| 288 | continue; |
| 289 | // Handle non-special attributes |
| 290 | Object attributeValue = classInfo.getAttributeValue(current,attributeName); |
| 291 | logger.debug("saving attribute: "+attributeName+", if changed."); |
| 292 | if ( objectTracker.hasChanged(classInfo,current,attributeName,nonchangedAttributes,currentSerial) ) |
| 293 | { |
| 294 | // Current object's current attribute has changed, |
| 295 | // so arrange it's save. |
| 296 | // This is a switch for the type, this should be |
| 297 | // replaced with something more adequate. For |
| 298 | // example a type manager who will handle types |
| 299 | // and is extensible. |
| 300 | Class attributeType = classInfo.getAttributeType(attributeName); |
| 301 | switch ( classTracker.getType(attributeType) ) |
| 302 | { |
| 303 | case TYPE_PRIMITIVE: |
| 304 | logger.debug("changing primitive attribute: "+attributeName+", value: "+current); |
| 305 | // Primitive type, just add to attributes |
| 306 | changedAttributes.put(attributeName,attributeValue); |
| 307 | break; |
| 308 | case TYPE_HANDLED: |
| 309 | logger.debug("changing handled attribute: "+attributeName+", type: "+attributeType); |
| 310 | TypeHandler handler = typeHandlerTracker.getHandler(attributeType); |
| 311 | HashSet saveTables = new HashSet(); |
| 312 | HashSet removeTables = new HashSet(); |
| 313 | // Call handler save |
| 314 | Object newValue = handler.save(classInfo,current,attributeName, |
| 315 | transaction,currentSerial, |
| 316 | attributeValue,waitingObjects,saveTables,removeTables,events, |
| 317 | changedAttributes,nonchangedAttributes); |
| 318 | // Save the handled objects for later use, they need to |
| 319 | // be notified when all objects are saved |
| 320 | if ( newValue != null ) |
| 321 | { |
| 322 | savedHandledObjects.add(handler); |
| 323 | savedHandledObjects.add(newValue); |
| 324 | } |
| 325 | // Add it's saved and remove tables to the transaction content |
| 326 | for ( Object table : saveTables ) |
| 327 | content.addSaveTable((String) table); |
| 328 | for ( Object table : removeTables ) |
| 329 | content.addRemoveTable((String) table); |
| 330 | break; |
| 331 | case TYPE_OBJECT: |
| 332 | logger.debug("attribute decided as custom object '"+attributeName+ |
| 333 | "', type is: "+classInfo.getAttributeType(attributeName)); |
| 334 | // Object type |
| 335 | if ( attributeValue == null ) |
| 336 | { |
| 337 | // Object type, but null |
| 338 | changedAttributes.put(attributeName,null); |
| 339 | } else { |
| 340 | if ( ! objectTracker.exists(attributeValue) ) |
| 341 | { |
| 342 | // Object referred does not exists, so it will |
| 343 | // have to be created after we're done |
| 344 | waitingObjects.add(objectTracker.getWrapper(attributeValue)); |
| 345 | } |
| 346 | objectTracker.registerObject(attributeValue); |
| 347 | Long objectId = objectTracker.getIdentifier(attributeValue); |
| 348 | // Got id, so give it to the man |
| 349 | changedAttributes.put(attributeName,objectId); |
| 350 | } |
| 351 | break; |
| 352 | default: |
| 353 | throw new StoreException("unknown type in attribute: "+attributeName+", value was: "+attributeValue); |
| 354 | } |
| 355 | } |
| 356 | } // End of iterating over attributes |
| 357 | newAttributes.putAll(changedAttributes); |
| 358 | // }}} |
| 359 | // {{{ Saving the object's changed attributes |
| 360 | // Now do the database thing on the assembled changed |
| 361 | // attributes, since the waitingObjects list did not |
| 362 | // change, meaning no new dependencies were discovered. |
| 363 | // All changes are saved in multiple save/inserts according |
| 364 | // to class structure. |
| 365 | if ( logger.isDebugEnabled() ) |
| 366 | logger.debug("object (of entry: "+classInfo+") will be saved into entries: "+classInfo.getClassEntries()); |
| 367 | Iterator strictClassEntriesIterator = classInfo.getClassEntries().iterator(); |
| 368 | while ( strictClassEntriesIterator.hasNext() ) |
| 369 | { |
| 370 | // Assemble changes for this strict class |
| 371 | ClassEntry entry = (ClassEntry) strictClassEntriesIterator.next(); |
| 372 | HashMap strictChanges = new HashMap(); |
| 373 | HashMap strictNonChanges = new HashMap(); |
| 374 | List strictAttributeNames = classInfo.getAttributeNames(entry); |
| 375 | if ( logger.isDebugEnabled() ) |
| 376 | logger.debug("assembling changed for class: "+entry+", strict attributes: "+strictAttributeNames); |
| 377 | for ( int i=0; i<strictAttributeNames.size() ; i++ ) |
| 378 | { |
| 379 | String attributeName = (String) strictAttributeNames.get(i); |
| 380 | Class attributeType = classInfo.getAttributeType(attributeName); |
| 381 | TypeHandler handler = typeHandlerTracker.getHandler(attributeType); |
| 382 | if ( handler == null ) |
| 383 | { |
| 384 | // No handler, this is a simple attribute |
| 385 | if ( changedAttributes.containsKey(attributeName) ) |
| 386 | strictChanges.put(attributeName,changedAttributes.remove(attributeName)); |
| 387 | if ( nonchangedAttributes.containsKey(attributeName) ) |
| 388 | strictNonChanges.put(attributeName,nonchangedAttributes.remove(attributeName)); |
| 389 | } else { |
| 390 | // Got handler, then iterate on it's attributes |
| 391 | Map embeddedAttributes = handler.getAttributeTypes(attributeName); |
| 392 | if ( logger.isDebugEnabled() ) |
| 393 | logger.debug("attribute: "+attributeName+", was an embedded attribute, adding: "+embeddedAttributes); |
| 394 | Iterator embeddedAttributeNamesIterator = embeddedAttributes.keySet().iterator(); |
| 395 | while ( embeddedAttributeNamesIterator.hasNext() ) |
| 396 | { |
| 397 | attributeName = (String) embeddedAttributeNamesIterator.next(); |
| 398 | if ( changedAttributes.containsKey(attributeName) ) |
| 399 | strictChanges.put(attributeName,changedAttributes.remove(attributeName)); |
| 400 | if ( nonchangedAttributes.containsKey(attributeName) ) |
| 401 | strictNonChanges.put(attributeName,nonchangedAttributes.remove(attributeName)); |
| 402 | } |
| 403 | } |
| 404 | } |
| 405 | // Make changes |
| 406 | if ( objectTracker.exists(current) ) |
| 407 | { |
| 408 | // Save |
| 409 | logger.debug("changing object with following attributes: "+strictChanges+", not changed: "+strictNonChanges); |
| 410 | if ( strictChanges.size() > 0 ) |
| 411 | { |
| 412 | // First set enddate on used entry |
| 413 | HashMap removeChanges = new HashMap(); |
| 414 | HashMap keys = new HashMap(); |
| 415 | keys.put("persistence_id",currentId); |
| 416 | keys.put("persistence_end",Serial.getMaxSerial().getValue()); |
| 417 | keys.put("persistence_txend",Serial.getMaxSerial().getValue()); |
| 418 | removeChanges.put("persistence_txend",currentSerial); |
| 419 | removeChanges.put("persistence_txendid",currentTxSerial); |
| 420 | database.save(transaction,schemaManager.getTableName(entry), |
| 421 | keys, removeChanges); |
| 422 | content.addRemoveTable(schemaManager.getTableName(entry)); |
| 423 | // Create new entry |
| 424 | strictNonChanges.putAll(strictChanges); |
| 425 | strictNonChanges.put("persistence_id", currentId); |
| 426 | strictNonChanges.put("persistence_start", Serial.getMaxSerial().getValue()); |
| 427 | strictNonChanges.put("persistence_end", Serial.getMaxSerial().getValue()); |
| 428 | strictNonChanges.put("persistence_txendid",new Long(0)); |
| 429 | strictNonChanges.put("persistence_txstart", currentSerial); |
| 430 | strictNonChanges.put("persistence_txstartid", currentTxSerial); |
| 431 | strictNonChanges.put("persistence_txend", Serial.getMaxSerial().getValue()); |
| 432 | database.insert(transaction,schemaManager.getTableName(entry), |
| 433 | strictNonChanges); |
| 434 | // Add to modified tables list |
| 435 | content.addSaveTable(schemaManager.getTableName(entry)); |
| 436 | } |
| 437 | } else { |
| 438 | // Insert |
| 439 | logger.debug("inserting object with following attributes: "+strictChanges); |
| 440 | strictChanges.put("persistence_id", currentId); |
| 441 | strictChanges.put("persistence_start", Serial.getMaxSerial().getValue()); |
| 442 | strictChanges.put("persistence_end", Serial.getMaxSerial().getValue()); |
| 443 | strictChanges.put("persistence_txendid",new Long(0)); |
| 444 | strictChanges.put("persistence_txstart", currentSerial); |
| 445 | strictChanges.put("persistence_txstartid", currentTxSerial); |
| 446 | strictChanges.put("persistence_txend", Serial.getMaxSerial().getValue()); |
| 447 | database.insert(transaction,schemaManager.getTableName(entry), |
| 448 | strictChanges); |
| 449 | // Add to modified tables list |
| 450 | content.addSaveTable(schemaManager.getTableName(entry)); |
| 451 | } |
| 452 | } |
| 453 | if ( changedAttributes.size() != 0 ) |
| 454 | logger.warn("there are attributes that do not belong in any superclass of object to be saved, classinfo: "+ |
| 455 | classInfo+", attributes: "+changedAttributes); |
| 456 | logger.debug("saving object with id: "+currentId+" updating meta-data."); |
| 457 | // Remove object from waiting list |
| 458 | waitingObjects.remove(currentWrapper); |
| 459 | // Notify event listeners |
| 460 | if ( objectTracker.exists(current) ) |
| 461 | { |
| 462 | // Send modify event. For this, we have to re-create |
| 463 | // the original state of the object. |
| 464 | events.add(new ModifyObjectEvent(classTracker,objectTracker,typeHandlerTracker, |
| 465 | queryService,originalAttributes,originalTimeControl,current)); |
| 466 | } else { |
| 467 | // Create |
| 468 | events.add(new CreateObjectEvent(current)); |
| 469 | } |
| 470 | // Update object tracker |
| 471 | objectTracker.makeExist(current); // Exists for this transaction |
| 472 | objectTracker.updateObject(current,newAttributes); |
| 473 | // }}} |
| 474 | logger.debug("saving object with id: "+currentId+" finished."); |
| 475 | } |
| 476 | // {{{ Go through handled objects, and notify that save ended |
| 477 | for ( int i=0; i<savedHandledObjects.size(); ) |
| 478 | { |
| 479 | TypeHandler handler = (TypeHandler) savedHandledObjects.get(i++); |
| 480 | Object value = (Object) savedHandledObjects.get(i++); |
| 481 | handler.postSave(value); |
| 482 | } |
| 483 | // }}} |
| 484 | // Debug code |
| 485 | if ( operationsLogger.isDebugEnabled() ) |
| 486 | { |
| 487 | TransactionStatistics stats = new TransactionStatistics(); |
| 488 | stats.add(transaction.getStats()); |
| 489 | stats.substract(startStats); |
| 490 | operationsLogger.debug("operation save: "+obj+", "+stats); |
| 491 | if ( operationsLogger.isTraceEnabled() ) |
| 492 | operationsLogger.trace("previous operation trace:",new Exception("trace")); |
| 493 | } |
| 494 | // {{{ Notify dispatcher of events occured during save |
| 495 | for ( int i=0; i<events.size(); i++ ) |
| 496 | eventDispatcher.notify((PersistenceEvent) events.get(i)); |
| 497 | // }}} |
| 498 | // "Happy, happy, joy, joy". Object saved. |
| 499 | } catch ( StoreException e ) { |
| 500 | transaction.markRollbackOnly(); |
| 501 | logger.error("throwing store exception",e); |
| 502 | throw e; |
| 503 | } catch ( Throwable e ) { |
| 504 | transaction.markRollbackOnly(); |
| 505 | logger.error("throwing unexpected exception",e); |
| 506 | throw new StoreException("unexpected exception",e); |
| 507 | } finally { |
| 508 | transaction.commit(); |
| 509 | // Add to statistics |
| 510 | long endTime = System.currentTimeMillis(); |
| 511 | synchronized ( storeStatistics ) |
| 512 | { |
| 513 | storeStatistics.setSaveCount(storeStatistics.getSaveCount()+1); |
| 514 | storeStatistics.setSaveTime(storeStatistics.getSaveTime()+(endTime-startTime)); |
| 515 | } |
| 516 | } |
| 517 | /// }}} |
| 518 | } |
| 519 | |
| 520 | /** |
| 521 | * Remove the object given. If the object is not stored yet, no |
| 522 | * operation will take place. |
| 523 | * @param obj The object to remove. |
| 524 | * @throws StoreException If remove is not successfull. |
| 525 | */ |
| 526 | public void remove(Object obj) |
| 527 | { |
| 528 | // {{{ Remove object |
| 529 | // Transaction |
| 530 | Transaction transaction = transactionTracker.getTransaction(TransactionTracker.TX_REQUIRED); |
| 531 | transaction.begin(); |
| 532 | TransactionStatistics startStats = new TransactionStatistics(); |
| 533 | startStats.add(transaction.getStats()); |
| 534 | long startTime = System.currentTimeMillis(); |
| 535 | try |
| 536 | { |
| 537 | // Get transaction content |
| 538 | TransactionContentImpl content = getTransactionContent(transaction); |
| 539 | // Get id, and at same time register into object tracker, |
| 540 | // later attempt remove only if object really exists |
| 541 | Long id = objectTracker.getMetaData(obj).getPersistenceId(); |
| 542 | if ( ! objectTracker.exists(obj) ) |
| 543 | { |
| 544 | logger.debug("object does not exists, so remove will not be attempted"); |
| 545 | return; |
| 546 | } |
| 547 | Long currentSerial = serialTracker.getNextSerial(); |
| 548 | Long currentTxSerial = transaction.getSerial(); |
| 549 | // Lock object |
| 550 | lockTracker.lock(obj); |
| 551 | content.addRemovedObject(objectTracker.getWrapper(obj)); |
| 552 | // Get class info |
| 553 | ClassInfo classInfo = classTracker.getClassInfo(obj.getClass(),obj); |
| 554 | // Assemble attributes |
| 555 | Map removeChanges = new HashMap(); |
| 556 | logger.debug("remove called, using serial: "+currentSerial); |
| 557 | removeChanges.put("persistence_txend", currentSerial); |
| 558 | removeChanges.put("persistence_txendid", currentTxSerial); |
| 559 | Map keys = new HashMap(); |
| 560 | keys.put("persistence_id",id); |
| 561 | keys.put("persistence_end",Serial.getMaxSerial().getValue()); |
| 562 | // Execute remove on class and all superclasses, et voila' |
| 563 | for ( int i=0; i<classInfo.getClassEntries().size(); i++ ) |
| 564 | { |
| 565 | ClassEntry entry = (ClassEntry) classInfo.getClassEntries().get(i); |
| 566 | database.save(transaction,schemaManager.getTableName(entry),keys , removeChanges); |
| 567 | // Add changed table |
| 568 | content.addRemoveTable(schemaManager.getTableName(entry)); |
| 569 | } |
| 570 | // Notify object tracker |
| 571 | objectTracker.makeUnexist(obj); |
| 572 | objectTracker.updateObject(obj,null); // Update to null cached attributes |
| 573 | // Notify event listeners |
| 574 | eventDispatcher.notify(new DeleteObjectEvent(obj)); |
| 575 | // Debug code |
| 576 | if ( operationsLogger.isDebugEnabled() ) |
| 577 | { |
| 578 | TransactionStatistics stats = new TransactionStatistics(); |
| 579 | stats.add(transaction.getStats()); |
| 580 | stats.substract(startStats); |
| 581 | operationsLogger.debug("operation remove: "+obj+" (id: "+id+"), "+stats); |
| 582 | if ( operationsLogger.isTraceEnabled() ) |
| 583 | operationsLogger.trace("previous operation trace:",new Exception("trace")); |
| 584 | } |
| 585 | } catch ( StoreException e ) { |
| 586 | transaction.markRollbackOnly(); |
| 587 | logger.error("throwing store exception",e); |
| 588 | throw e; |
| 589 | } catch ( Throwable e ) { |
| 590 | transaction.markRollbackOnly(); |
| 591 | logger.error("throwing unexpected exception",e); |
| 592 | throw new StoreException("unexpected exception",e); |
| 593 | } finally { |
| 594 | transaction.commit(); |
| 595 | // Add to statistics |
| 596 | long endTime = System.currentTimeMillis(); |
| 597 | synchronized ( storeStatistics ) |
| 598 | { |
| 599 | storeStatistics.setRemoveCount(storeStatistics.getRemoveCount()+1); |
| 600 | storeStatistics.setRemoveTime(storeStatistics.getRemoveTime()+(endTime-startTime)); |
| 601 | } |
| 602 | } |
| 603 | /// }}} |
| 604 | } |
| 605 | |
| 606 | // {{{ Helper classes |
| 607 | /** |
| 608 | * This listener finishes a transaction by unlocking all objects. |
| 609 | */ |
| 610 | private class HighTransactionListener implements PersistenceEventListener |
| 611 | { |
| 612 | public void handle(PersistenceEvent event) |
| 613 | { |
| 614 | if ( ! (event instanceof TransactionEvent) ) |
| 615 | return; // Quick exit |
| 616 | Transaction transaction = ((TransactionEvent) event).getTransaction(); |
| 617 | if ( (event instanceof TransactionRolledbackEvent) || |
| 618 | (event instanceof TransactionCommittedEvent) ) |
| 619 | { |
| 620 | // Remove transaction contents. |
| 621 | TransactionContentImpl content = getTransactionContent(transaction); |
| 622 | if ( content.isEmpty() ) |
| 623 | return; // No need to do anything, if there were no modifications |
| 624 | // Unlock objects. Unlock event must come after everything about a |
| 625 | // transaction is complete, because as soon as objects are unlocked, |
| 626 | // other threads or nodes might cause interference with commit or |
| 627 | // committed listeners. |
| 628 | // If this event does not reach the server, the communication |
| 629 | // error will cause the server to unlock all objects anyway. |
| 630 | // TODO: do NOT send update event to object tracker, let it listen |
| 631 | // for events. |
| 632 | ArrayList objects = new ArrayList(); |
| 633 | Iterator removedObjectsIterator = content.getRemovedObjects().iterator(); |
| 634 | while ( removedObjectsIterator.hasNext() ) |
| 635 | { |
| 636 | Object obj = ((ObjectTracker.ObjectWrapper)removedObjectsIterator.next()).getObject(); |
| 637 | objectTracker.updateObject( |
| 638 | obj,transaction.getEndSerial(),null,transaction.getEndSerial()); |
| 639 | objects.add(obj); |
| 640 | try |
| 641 | { |
| 642 | eventDispatcher.notify(new FinishedObjectEvent(obj)); |
| 643 | } catch ( Exception e ) { |
| 644 | logger.warn("exception while notifying finished object",e); |
| 645 | } |
| 646 | } |
| 647 | Iterator savedObjectsIterator = content.getSavedObjects().iterator(); |
| 648 | while ( savedObjectsIterator.hasNext() ) |
| 649 | { |
| 650 | Object obj = ((ObjectTracker.ObjectWrapper)savedObjectsIterator.next()).getObject(); |
| 651 | objectTracker.updateObject( |
| 652 | obj,transaction.getEndSerial(),transaction.getEndSerial(),null); |
| 653 | objects.add(obj); |
| 654 | try |
| 655 | { |
| 656 | eventDispatcher.notify(new FinishedObjectEvent(obj)); |
| 657 | } catch ( Exception e ) { |
| 658 | logger.warn("exception while notifying finished object",e); |
| 659 | } |
| 660 | } |
| 661 | lockTracker.unlock(objects.toArray()); |
| 662 | } |
| 663 | } |
| 664 | } |
| 665 | |
| 666 | /** |
| 667 | * This listener is here for finishing a committed transactions by |
| 668 | * finalizing changes to the ens serial, and modifying their metadata when successful. |
| 669 | */ |
| 670 | private class LowTransactionListener implements PersistenceEventListener |
| 671 | { |
| 672 | public void handle(PersistenceEvent event) |
| 673 | throws Exception |
| 674 | { |
| 675 | if ( ! (event instanceof TransactionEvent) ) |
| 676 | return; // Quick exit |
| 677 | Transaction transaction = ((TransactionEvent) event).getTransaction(); |
| 678 | // Get transaction contents. |
| 679 | TransactionContentImpl content = getTransactionContent(transaction); |
| 680 | if ( content.isEmpty() ) |
| 681 | return; // No need to do anything, if there were no modifications |
| 682 | List objects = new ArrayList(); |
| 683 | objects.addAll(content.getRemovedObjects()); |
| 684 | objects.addAll(content.getSavedObjects()); |
| 685 | Long serial = transaction.getEndSerial(); |
| 686 | // Commit object updates |
| 687 | if ( event instanceof TransactionCommittedEvent ) |
| 688 | { |
| 689 | // We must update objects to reflect the changes we made |
| 690 | Iterator objectsIterator = objects.iterator(); |
| 691 | while ( objectsIterator.hasNext() ) |
| 692 | { |
| 693 | Object obj = ((ObjectTracker.ObjectWrapper)objectsIterator.next()).getObject(); |
| 694 | objectTracker.updateCommit(obj,serial); |
| 695 | } |
| 696 | } |
| 697 | if ( event instanceof TransactionRolledbackEvent ) |
| 698 | { |
| 699 | // We must update objects to reflect the changes we made |
| 700 | Iterator objectsIterator = objects.iterator(); |
| 701 | while ( objectsIterator.hasNext() ) |
| 702 | { |
| 703 | Object obj = ((ObjectTracker.ObjectWrapper)objectsIterator.next()).getObject(); |
| 704 | objectTracker.updateRollback(obj,serial); |
| 705 | } |
| 706 | } |
| 707 | // Make all changes permanent and visible here |
| 708 | if ( event instanceof TransactionCommitEndingEvent ) |
| 709 | { |
| 710 | // All operations done must be finalized now. If an exception occurs, |
| 711 | // the transaction tracker will roll back the transaction anyway, |
| 712 | // so we don't have to worry about that. |
| 713 | // Save: set startdates where startdate is maxdate |
| 714 | List saveTables = content.getSaveTables(); |
| 715 | HashMap keys = new HashMap(); |
| 716 | keys.put("persistence_txstartid",transaction.getSerial()); |
| 717 | HashMap changes = new HashMap(); |
| 718 | changes.put("persistence_start",transaction.getEndSerial()); |
| 719 | for ( int i=0; i<saveTables.size(); i++ ) |
| 720 | { |
| 721 | String tableName = (String) saveTables.get(i); |
| 722 | logger.debug("fixing save table: "+tableName); |
| 723 | database.save(transaction,tableName,keys,changes); |
| 724 | } |
| 725 | // Remove: set enddates where txenddate is not maxdate |
| 726 | List removeTables = content.getRemoveTables(); |
| 727 | keys = new HashMap(); |
| 728 | keys.put("persistence_txendid",transaction.getSerial()); |
| 729 | changes = new HashMap(); |
| 730 | changes.put("persistence_end",transaction.getEndSerial()); |
| 731 | for ( int i=0; i<removeTables.size(); i++ ) |
| 732 | { |
| 733 | String tableName = (String) removeTables.get(i); |
| 734 | logger.debug("fixing remove table: "+tableName); |
| 735 | database.save(transaction,tableName,keys,changes); |
| 736 | } |
| 737 | // Notify the server of all objects that changed. This operation |
| 738 | // must be before the commit physically occurs, because this notification |
| 739 | // will cause the server to know which objects are modified. |
| 740 | List metas = new ArrayList(); |
| 741 | for ( int i=0; i<objects.size(); i++ ) |
| 742 | metas.add(objectTracker.getMetaData(((ObjectTracker.ObjectWrapper)objects.get(i)).getObject())); |
| 743 | if ( logger.isDebugEnabled() ) |
| 744 | logger.debug("sending changed objects' meta to all: "+metas); |
| 745 | // TODO (DODGY): We should ensure that this call does not return until |
| 746 | // all nodes finished processing of event. If we don't, it's possible that |
| 747 | // a client does not clear cache for example but after the commit lock is |
| 748 | // freed. Which would mean it might allow for an inconsistent query. |
| 749 | nodeManager.callAll(StoreService.class.getName(),"notifyChange", |
| 750 | new Class[] { List.class, Long.class, Long.class }, |
| 751 | new Object[] { metas, transaction.getEndSerial(), transaction.getSerial() }); |
| 752 | logger.debug("sending changed objects finished."); |
| 753 | } |
| 754 | } |
| 755 | } |
| 756 | |
| 757 | public void notifyChange(List<PersistenceMetaData> metas, Long serial, Long txSerial) |
| 758 | { |
| 759 | eventDispatcher.notifyAll(new ObjectsFinalizationEvent(metas,serial,txSerial)); |
| 760 | } |
| 761 | |
| 762 | public static class TransactionContentImpl |
| 763 | { |
| 764 | // Store's attributes |
| 765 | private LinkedList savedObjectList; |
| 766 | private LinkedList removedObjectList; |
| 767 | private HashSet saveTables; |
| 768 | private HashSet removeTables; |
| 769 | |
| 770 | private TransactionContentImpl() |
| 771 | { |
| 772 | savedObjectList = new LinkedList(); |
| 773 | removedObjectList = new LinkedList(); |
| 774 | saveTables = new HashSet(); |
| 775 | removeTables = new HashSet(); |
| 776 | } |
| 777 | |
| 778 | private void addSavedObject(Object obj) |
| 779 | { |
| 780 | savedObjectList.add(obj); |
| 781 | } |
| 782 | |
| 783 | /** |
| 784 | * Get the objects saved in this transaction as a list. |
| 785 | */ |
| 786 | public List getSavedObjects() |
| 787 | { |
| 788 | return Collections.unmodifiableList(savedObjectList); |
| 789 | } |
| 790 | |
| 791 | private void addRemovedObject(Object obj) |
| 792 | { |
| 793 | removedObjectList.add(obj); |
| 794 | } |
| 795 | |
| 796 | /** |
| 797 | * Get the objects removed in this transaction as a list. |
| 798 | */ |
| 799 | public List getRemovedObjects() |
| 800 | { |
| 801 | return Collections.unmodifiableList(removedObjectList); |
| 802 | } |
| 803 | |
| 804 | public boolean isEmpty() |
| 805 | { |
| 806 | return savedObjectList.isEmpty() && removedObjectList.isEmpty(); |
| 807 | } |
| 808 | |
| 809 | private void addSaveTable(String table) |
| 810 | { |
| 811 | saveTables.add(table); |
| 812 | } |
| 813 | private void setSaveTables(List saveTables) |
| 814 | { |
| 815 | this.saveTables = new HashSet(saveTables); |
| 816 | } |
| 817 | private List getSaveTables() |
| 818 | { |
| 819 | return new ArrayList(saveTables); |
| 820 | } |
| 821 | private void addRemoveTable(String table) |
| 822 | { |
| 823 | removeTables.add(table); |
| 824 | } |
| 825 | private void setRemoveTables(List removeTables) |
| 826 | { |
| 827 | this.removeTables = new HashSet(removeTables); |
| 828 | } |
| 829 | private List getRemoveTables() |
| 830 | { |
| 831 | return new ArrayList(removeTables); |
| 832 | } |
| 833 | |
| 834 | } |
| 835 | // }}} |
| 836 | } |
| 837 | |
| 838 | |
| 839 | |