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
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
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
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
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
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
382 if (this.deleteLock != null && txn != null
383 && !this.deleteLock.isAncestorOf(txn)
384 && !txn.isAncestorOf(this.deleteLock)) {
385
386
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
393
394 throw new EntryLockedException(this.writeLock);
395 }
396 if (this.readLocks.size() != 0 && txn != null) {
397
398
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
508
509
510
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
569
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
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 }