View Javadoc

1   package org.xvsm.core;
2   
3   import java.io.IOException;
4   import java.io.Serializable;
5   import java.util.ArrayList;
6   import java.util.Collections;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.TreeMap;
10  import java.util.UUID;
11  
12  import org.apache.log4j.Logger;
13  import org.xvsm.interfaces.ICoordinator;
14  import org.xvsm.internal.exceptions.EntryLockedException;
15  import org.xvsm.selectors.Selector;
16  import org.xvsm.transactions.Transaction;
17  
18  /***
19   * An entry is the basic entity which can be stored in a container.
20   * 
21   * @author Christian Schreiber, Michael Proestler
22   * 
23   */
24  public abstract class Entry implements Serializable {
25  
26  	/***
27  	 * The logger of this class.
28  	 */
29  	private static Logger logger = Logger.getLogger(Entry.class);
30  
31  	/***
32  	 * Defines all possible types of entries (TUPLE and ATOMICENTRY).<br>
33  	 * This is used to avoid the instanceof operator.
34  	 * 
35  	 * @author Christian Schreiber, Michael Proestler
36  	 * 
37  	 */
38  	public static enum EntryTypes {
39  		/***
40  		 * The entry is a {@link Tuple}.
41  		 */
42  		TUPLE,
43  		/***
44  		 * The entry is an {@link AtomicEntry}.
45  		 */
46  		ATOMICENTRY,
47  		/***
48  		 * The entry is an {@link VoidEntry}.
49  		 */
50  		VOID,
51  		/***
52  		 * The entry is an {@link ExceptionEntry}.
53  		 */
54  		EXCEPTION;
55  	}
56  
57  	/***
58  	 * Generated serial version UID.
59  	 */
60  	private static final long serialVersionUID = 7961524563053473111L;
61  
62  	/***
63  	 * A basis uuid string used for the generation of the entry id.
64  	 */
65  	private static String baseUUID = UUID.randomUUID().toString();
66  
67  	/***
68  	 * A Count which is used for the generation of the entry id.
69  	 */
70  	private static long entryCount = 0;
71  
72  	/***
73  	 * The internal id of the entry. This id does not need to be globally unique
74  	 * It needs to be unique on this core instance (site) only.
75  	 */
76  	private String id;
77  
78  	/***
79  	 * The type of this entry.<br>
80  	 * If entryTypes is EntryTypes.TUPLE you can cast this entry to a
81  	 * {@link tuple} otherwise (set to EntryTypes.ATOMICENTRY) this entry can be
82  	 * cast to {@link AtomicEntry}.
83  	 */
84  	private EntryTypes entryType;
85  
86  	/***
87  	 * A List of {@link Selector}s used to write the entry. This field is null
88  	 * when the entry has been written from the container.
89  	 */
90  	private List<Selector> selectors = new ArrayList<Selector>();
91  
92  	/***
93  	 * Stores a {@link List} of coordination types in which this entry is
94  	 * referenced.
95  	 */
96  	private List<ICoordinator> coordinationType = new ArrayList<ICoordinator>();
97  
98  	// ##################################################### LOCKS
99  	/***
100 	 * This lock is set if the entry has been deleted by a transaction which has
101 	 * not been committed or rolled back.
102 	 */
103 	private Transaction deleteLock;
104 
105 	/***
106 	 * This lock is set if the entry has been written by a transaction which has
107 	 * not been committed or rolled back.
108 	 */
109 	private Transaction writeLock;
110 
111 	/***
112 	 * A shared object used to synchronize the locking mechanisms.
113 	 */
114 	private Object lockObject = new String("lockString");
115 
116 	/***
117 	 * A map containing all readLocks.
118 	 */
119 	private Map<String, Transaction> readLocks = Collections
120 			.synchronizedMap(new TreeMap<String, Transaction>());
121 
122 	// ##################################################### LOCKS
123 
124 	/***
125 	 * Creates a new Entry and generates a id for it.
126 	 */
127 	protected Entry() {
128 		this.generateId();
129 	}
130 
131 	/***
132 	 * Creates a new Entry and generates a id for it.
133 	 * 
134 	 * @param sels
135 	 *            the selectors used to write the entry.
136 	 */
137 	protected Entry(Selector... sels) {
138 		this();
139 		this.addSelectors(sels);
140 	}
141 
142 	/***
143 	 * Creates a flat copy of this entry.<br>
144 	 * NOTE: The content will not be copied.
145 	 * 
146 	 * @return a flat copy of this entry.
147 	 */
148 	public abstract Entry getFlatCopy();
149 
150 	// /***
151 	// * Creates a copy of the Entry with the same content.<br>
152 	// * The meta information of the entry (selector, locks, ...) will not be
153 	// * copied.
154 	// *
155 	// * @return a copy of the the entry.
156 	// */
157 	// public abstract Entry getPlainEntry();
158 
159 	/***
160 	 * Get the EntryTypes of this Entry.
161 	 * 
162 	 * @return the entryType
163 	 */
164 	public EntryTypes getEntryType() {
165 		return entryType;
166 	}
167 
168 	/***
169 	 * Set the EntryTypes of this Entry.
170 	 * 
171 	 * @param entryType
172 	 *            the entryType to set
173 	 */
174 	protected void setEntryType(EntryTypes entryType) {
175 		this.entryType = entryType;
176 	}
177 
178 	/***
179 	 * Get the String of this Entry.
180 	 * 
181 	 * @return the id
182 	 */
183 	public String getId() {
184 		return id;
185 	}
186 
187 	/***
188 	 * Set the Id of this Entry.
189 	 * 
190 	 * @param id
191 	 *            the id to set
192 	 */
193 	public void setId(String id) {
194 		this.id = id;
195 	}
196 
197 	/***
198 	 * Generate and set an id for this entry.
199 	 * 
200 	 */
201 	public void generateId() {
202 		synchronized (Entry.baseUUID) {
203 			if (Entry.entryCount == Long.MAX_VALUE) {
204 				// reset the counter and generate a new randomUUID.
205 				Entry.entryCount = 0;
206 				Entry.baseUUID = UUID.randomUUID().toString();
207 			}
208 
209 			this.setId(Entry.baseUUID + "--" + (Entry.entryCount++));
210 		}
211 	}
212 
213 	/***
214 	 * Get the the coordination Types of this Entry.
215 	 * 
216 	 * @return the coordinationType
217 	 */
218 	public ICoordinator[] getCoordinationType() {
219 		synchronized (this.lockObject) {
220 			return coordinationType.toArray(new ICoordinator[0]);
221 		}
222 	}
223 
224 	/***
225 	 * Adds the given coordination types to the the list of coordination types
226 	 * of this entry.
227 	 * 
228 	 * @param cts
229 	 *            the new coordination types.
230 	 */
231 	public void addCoordinationTypes(ICoordinator... cts) {
232 		synchronized (this.lockObject) {
233 			for (ICoordinator ct : cts) {
234 				this.coordinationType.add(ct);
235 			}
236 		}
237 	}
238 
239 	/***
240 	 * Get the delete Lock of this Entry.
241 	 * 
242 	 * @return the deleteLock
243 	 */
244 	public Transaction getDeleteLock() {
245 		synchronized (this.lockObject) {
246 			if (logger.isDebugEnabled()) {
247 				logger.debug("getDeleteLock()");
248 			}
249 			synchronized (this.lockObject) {
250 				if (logger.isDebugEnabled()) {
251 					logger.debug("getDeleteLock() returns: " + this.deleteLock);
252 				}
253 				return deleteLock;
254 			}
255 		}
256 	}
257 
258 	/***
259 	 * <code>true</code> if this entry has been deleted with tx.
260 	 * 
261 	 * @param tx
262 	 *            the transaction to check
263 	 * @return <code>true</code> if this entry has been deleted with tx,
264 	 *         otherwise <code>false</code>.
265 	 */
266 	public boolean hasDeleteLock(Transaction tx) {
267 		synchronized (this.lockObject) {
268 			return this.deleteLock != null && this.deleteLock.isAncestorOf(tx);
269 		}
270 	}
271 
272 	/***
273 	 * Determines if the entry can be read with the given transaction.<br>
274 	 * More formal, the method returns <code>true</code> if the entry has no
275 	 * write or delete lock from another transaction or a delete lock from the
276 	 * same transaction.
277 	 * 
278 	 * @param tx
279 	 *            the transaction to check.
280 	 * @return <code>true</code> if the entry can be read with this tx otherwise
281 	 *         <code>false</code>.
282 	 */
283 	public boolean canBeRead(Transaction tx) {
284 		if (logger.isDebugEnabled()) {
285 			logger.debug("canBeRead()");
286 
287 		}
288 		synchronized (this.lockObject) {
289 			if (this.writeLock != null) {
290 				if (!this.writeLock.isAncestorOf(tx)) {
291 					if (logger.isDebugEnabled()) {
292 						logger.debug("canBeRead() returns: false");
293 					}
294 					return false;
295 				}
296 			}
297 			if (this.deleteLock != null) {
298 				if (logger.isDebugEnabled()) {
299 					logger.debug("canBeRead() returns: false");
300 				}
301 				return false;
302 			}
303 			if (logger.isDebugEnabled()) {
304 				logger.debug("canBeRead() returns: true");
305 			}
306 			return true;
307 		}
308 
309 	}
310 
311 	public boolean canBeWritten(Transaction tx) {
312 		if (logger.isDebugEnabled()) {
313 			logger.debug("canBeWritten()");
314 		}
315 		synchronized (this.lockObject) {
316 			return canBeRead(tx)
317 					&& this.readLocks.size() == 0
318 					| (this.readLocks.size() == 1 && this.readLocks
319 							.containsKey(tx.getId()));
320 		}
321 	}
322 
323 	/***
324 	 * Remove the delete Lock of this Entry.
325 	 * 
326 	 */
327 	public void removeDeleteLock() {
328 		if (logger.isDebugEnabled()) {
329 			logger.debug("removeDeleteLock()");
330 		}
331 		synchronized (this.lockObject) {
332 			this.deleteLock = null;
333 			if (logger.isDebugEnabled()) {
334 				logger.debug("removeDeleteLock() set deleteLock: null");
335 			}
336 		}
337 	}
338 
339 	/***
340 	 * Set the delete Lock of this Entry.
341 	 * 
342 	 * @param deleteLock
343 	 *            the deleteLock to set
344 	 * @throws EntryLockedException
345 	 *             thrown if the lock can not be aquired.
346 	 */
347 	public void setDeleteLock(Transaction deleteLock)
348 			throws EntryLockedException {
349 		if (logger.isDebugEnabled()) {
350 			logger.debug("setDeleteLock()");
351 		}
352 
353 		synchronized (this.lockObject) {
354 			this.checkLocks(deleteLock);
355 			// finally set the lock
356 			this.deleteLock = deleteLock;
357 			if (logger.isDebugEnabled()) {
358 				logger.debug("setDeleteLock() set deleteLock: " + deleteLock);
359 			}
360 		}
361 	}
362 
363 	/***
364 	 * Checks if any lock is set. If delete-, write or read lock is set by
365 	 * another transaction and the other transaction is either father nor child
366 	 * of txn EntryLockedException will be thrown
367 	 * 
368 	 * @param txn
369 	 *            the transaction for which the locks have to be checked.
370 	 * @throws EntryLockedException
371 	 *             thrown if any lock is set and can not be used with txn.
372 	 */
373 	private void checkLocks(Transaction txn) throws EntryLockedException {
374 		try {
375 			if (logger.isDebugEnabled()) {
376 				logger
377 						.debug("checkLocks() " + this.getId() + " for tx: "
378 								+ txn);
379 			}
380 			synchronized (this.lockObject) {
381 				// Check if the delete lock can be set.
382 				if (this.deleteLock != null && txn != null
383 						&& !this.deleteLock.isAncestorOf(txn)
384 						&& !txn.isAncestorOf(this.deleteLock)) {
385 					// if this.deleteLock is set from another transaction we can
386 					// not set the lock.
387 					throw new EntryLockedException(this.deleteLock);
388 				}
389 				if (this.writeLock != null && txn != null
390 						&& !this.writeLock.isAncestorOf(txn)
391 						&& !txn.isAncestorOf(this.writeLock)) {
392 					// if this.writeLock is set from another transaction we can
393 					// not set the lock.
394 					throw new EntryLockedException(this.writeLock);
395 				}
396 				if (this.readLocks.size() != 0 && txn != null) {
397 					// if there is a readLock from another transaction we can
398 					// not set the lock
399 					for (Transaction tx : this.readLocks.values()) {
400 						if (!tx.isAncestorOf(txn) && !txn.isAncestorOf(tx)) {
401 							throw new EntryLockedException(tx);
402 						}
403 					}
404 				}
405 			}
406 		} catch (EntryLockedException e) {
407 			if (logger.isDebugEnabled()) {
408 				logger.debug("setDeleteLock() entry locked with "
409 						+ e.getReason());
410 			}
411 			throw e;
412 		}
413 	}
414 
415 	/***
416 	 * Get the write lock of this Entry.
417 	 * 
418 	 * @return the writeLock
419 	 */
420 	public Transaction getWriteLock() {
421 		if (logger.isDebugEnabled()) {
422 			logger.debug("getWriteLock()");
423 		}
424 		synchronized (this.lockObject) {
425 			if (logger.isDebugEnabled()) {
426 				logger.debug("getWriteLock() returns " + writeLock);
427 			}
428 			return writeLock;
429 		}
430 	}
431 
432 	/***
433 	 * <code>true</code> if this entry has been written with tx and therefore
434 	 * has to be committed or rolled back.
435 	 * 
436 	 * @param tx
437 	 *            the transaction to check
438 	 * @return <code>true</code> if this entry has been written with tx,
439 	 *         otherwise <code>false</code>.
440 	 */
441 	public boolean hasWriteLock(Transaction tx) {
442 		if (logger.isDebugEnabled()) {
443 			logger.debug("hasWriteLock()");
444 		}
445 		synchronized (this.lockObject) {
446 			boolean ret = this.writeLock != null
447 					&& this.writeLock.isAncestorOf(tx);
448 			if (logger.isDebugEnabled()) {
449 				logger.debug("hasWriteLock() returns :" + ret);
450 			}
451 			return ret;
452 		}
453 	}
454 
455 	/***
456 	 * Set the write lock of this Entry.
457 	 * 
458 	 * @param writeLock
459 	 *            the writeLock to set
460 	 * @throws EntryLockedException
461 	 *             thrown if the write lock can not be aquired.
462 	 */
463 	public void setWriteLock(Transaction writeLock) throws EntryLockedException {
464 		if (logger.isDebugEnabled()) {
465 			logger.debug("setWriteLock()");
466 		}
467 		synchronized (this.lockObject) {
468 			this.checkLocks(writeLock);
469 			this.writeLock = writeLock;
470 			if (logger.isDebugEnabled()) {
471 				logger.debug("setWriteLock() set writeLock: " + this.writeLock);
472 			}
473 		}
474 	}
475 
476 	/***
477 	 * Remove the write lock of this Entry.
478 	 * 
479 	 */
480 	public void removeWriteLock() {
481 		if (logger.isDebugEnabled()) {
482 			logger.debug("removeWriteLock()");
483 		}
484 		synchronized (this.lockObject) {
485 			this.writeLock = null;
486 			if (logger.isDebugEnabled()) {
487 				logger.debug("removeWriteLock() set writeLock: null");
488 			}
489 		}
490 	}
491 
492 	/***
493 	 * Adds tx to the readLocks.
494 	 * 
495 	 * @param tx
496 	 *            the new lock.
497 	 * @throws EntryLockedException
498 	 *             if the readLock can not be set because another tx wrote or
499 	 *             deleted this entry.
500 	 */
501 	public void addReadLock(Transaction tx) throws EntryLockedException {
502 		if (logger.isDebugEnabled()) {
503 			logger.debug("addReadLock()");
504 		}
505 		synchronized (this.lockObject) {
506 			if (tx != null) {
507 				// we can not use checkLocks(tx) because the read lock can be
508 				// set when there are already read locks.
509 
510 				// Check if the delete lock can be set.
511 				if (this.deleteLock != null && tx != null
512 						&& !this.deleteLock.isAncestorOf(tx)
513 						&& !tx.isAncestorOf(this.deleteLock)) {
514 					throw new EntryLockedException(tx);
515 				}
516 				if (this.writeLock != null && tx != null
517 						&& !this.writeLock.isAncestorOf(tx)
518 						&& !tx.isAncestorOf(this.writeLock)) {
519 					throw new EntryLockedException(tx);
520 				}
521 				this.readLocks.put(tx.getId(), tx);
522 			}
523 		}
524 		if (logger.isDebugEnabled()) {
525 			logger.debug("addReadLock() added: " + tx);
526 		}
527 	}
528 
529 	/***
530 	 * Checks if the entry has been read with the tx.
531 	 * 
532 	 * @return <code>true</code> if the entry has been read with tx, otherwise
533 	 *         <code>false</code>
534 	 * @param tx
535 	 *            the lock to check.
536 	 */
537 	public boolean hasReadLock(Transaction tx) {
538 		if (logger.isDebugEnabled()) {
539 			logger.debug("hasReadLock()");
540 		}
541 		synchronized (this.lockObject) {
542 			boolean ret = this.readLocks.get(tx.getId()) != null;
543 			if (logger.isDebugEnabled()) {
544 				logger.debug("hasReadLock() returns: " + ret);
545 			}
546 			return ret;
547 		}
548 	}
549 
550 	/***
551 	 * Removes tx from the readLocks.
552 	 * 
553 	 * @param tx
554 	 *            the new lock.
555 	 */
556 	public void removeReadLock(Transaction tx) {
557 		if (logger.isDebugEnabled()) {
558 			logger.debug("removeReadLock()");
559 		}
560 		synchronized (this.lockObject) {
561 			this.readLocks.remove(tx.getId());
562 			if (logger.isDebugEnabled()) {
563 				logger.debug("removeReadLock() removed: " + tx);
564 			}
565 		}
566 	}
567 
568 	// TODO removeOnRollback and addOnRollback good names? can they be used on
569 	// every coordinator? Now used in RandomCoordinator.rollback(Transaction tx)
570 
571 	/***
572 	 * Determines if the entry has to be removed when tx is rolled back.<br>
573 	 * <code>true</code> if this entry has been written with tx and NOT been
574 	 * deleted with the same tx.
575 	 * 
576 	 * @param tx
577 	 *            the transaction to check.
578 	 * @return <code>true</code> if the entry has to be removed on the rollback
579 	 *         of tx.
580 	 */
581 	public boolean removeOnRollback(Transaction tx) {
582 		if (logger.isDebugEnabled()) {
583 			logger.debug("removeOnRollback()");
584 		}
585 		synchronized (this.lockObject) {
586 			boolean ret = this.hasWriteLock(tx) && !this.hasDeleteLock(tx);
587 			if (logger.isDebugEnabled()) {
588 				logger.debug("removeOnRollback() returns: " + ret);
589 			}
590 			return ret;
591 		}
592 	}
593 
594 	/***
595 	 * Determines if the entry has to be added when tx is rolled back.<br>
596 	 * <code>true</code> if the entry has not been written with tx but has been
597 	 * deleted with tx.
598 	 * 
599 	 * @param tx
600 	 *            the transaction to check.
601 	 * @return <code>true</code> if the entry has to be added on the rollback of
602 	 *         tx.
603 	 */
604 	public boolean addOnRollback(Transaction tx) {
605 		if (logger.isDebugEnabled()) {
606 			logger.debug("addOnRollback()");
607 		}
608 		synchronized (this.lockObject) {
609 			boolean ret = !this.hasWriteLock(tx) && this.hasDeleteLock(tx);
610 			if (logger.isDebugEnabled()) {
611 				logger.debug("addOnRollback() returns: " + ret);
612 			}
613 			return ret;
614 		}
615 	}
616 
617 	/***
618 	 * Get the selectors of this Entry.
619 	 * 
620 	 * @return the selectors
621 	 */
622 	public List<Selector> getSelectors() {
623 		return selectors;
624 	}
625 
626 	/***
627 	 * Add the selectors to this Entry.
628 	 * 
629 	 * @param sels
630 	 *            the selectors to add
631 	 */
632 	public void addSelectors(Selector... sels) {
633 		for (Selector s : sels) {
634 			this.selectors.add(s);
635 		}
636 	}
637 
638 	/***
639 	 * Set the selectors to this Entry.
640 	 * 
641 	 * @param sels
642 	 *            the selectors to set
643 	 */
644 	public void setSelectors(List<Selector> sels) {
645 		this.selectors = sels;
646 	}
647 
648 	/***
649 	 * 
650 	 * {@inheritDoc}.
651 	 */
652 	@Override
653 	public String toString() {
654 		return "<selectors: " + this.selectors + ">";
655 	}
656 
657 	/***
658 	 * {@inheritDoc}.
659 	 */
660 	@Override
661 	public int hashCode() {
662 		final int prime = 31;
663 		int result = 1;
664 		result = prime * result
665 				+ ((entryType == null) ? 0 : entryType.hashCode());
666 		result = prime * result + ((id == null) ? 0 : id.hashCode());
667 		return result;
668 	}
669 
670 	/***
671 	 * {@inheritDoc}.
672 	 */
673 	@Override
674 	public boolean equals(Object obj) {
675 		if (obj == null || this.id == null) {
676 			return false;
677 		}
678 		return obj instanceof Entry && this.id.equals(((Entry) obj).id);
679 	}
680 
681 	/***
682 	 * Method is used for serializing the entry.<br>
683 	 * Remove all unnecessary information form the entry before sending to the
684 	 * client.
685 	 * 
686 	 * @param out
687 	 *            the ObjectOutputStream used for writing.
688 	 * @throws IOException
689 	 *             thrown if an error occurs while writing the entry.
690 	 */
691 	private void writeObject(java.io.ObjectOutputStream out) throws IOException {
692 		synchronized (this.lockObject) {
693 			// remove all locks.
694 			Transaction dLock = this.deleteLock;
695 			this.deleteLock = null;
696 			Transaction wLock = this.writeLock;
697 			this.writeLock = null;
698 			Map<String, Transaction> rLocks = this.readLocks;
699 			this.readLocks = null;
700 			List<ICoordinator> myCoordinationTypes = this.coordinationType;
701 			this.coordinationType = new ArrayList<ICoordinator>();
702 
703 			out.defaultWriteObject();
704 
705 			this.coordinationType = myCoordinationTypes;
706 			this.lockObject = new String("lockString");
707 			this.readLocks = rLocks;
708 			this.deleteLock = dLock;
709 			this.writeLock = wLock;
710 		}
711 	}
712 
713 	/***
714 	 * Method is used for deserializing the object.<br>
715 	 * Initializes the readLocks list and the lockObject.
716 	 * 
717 	 * @param in
718 	 *            the ObjectInputStream used for reading.
719 	 * @throws IOException
720 	 *             thrown if an error occurs while reading the entry.
721 	 * @throws ClassNotFoundException
722 	 *             thrown if a class can not be found.
723 	 */
724 	private void readObject(java.io.ObjectInputStream in) throws IOException,
725 			ClassNotFoundException {
726 		in.defaultReadObject();
727 		this.lockObject = new String("lockObject");
728 		this.readLocks = Collections
729 				.synchronizedMap(new TreeMap<String, Transaction>());
730 	}
731 }