Home | History | Annotate | Download | only in impl
      1 // Licensed under Apache License version 2.0
      2 package javax.jmdns.impl;
      3 
      4 import java.util.Collection;
      5 import java.util.concurrent.ConcurrentHashMap;
      6 import java.util.concurrent.ConcurrentMap;
      7 import java.util.concurrent.Semaphore;
      8 import java.util.concurrent.TimeUnit;
      9 import java.util.concurrent.locks.ReentrantLock;
     10 import java.util.logging.Level;
     11 import java.util.logging.Logger;
     12 
     13 import javax.jmdns.impl.constants.DNSState;
     14 import javax.jmdns.impl.tasks.DNSTask;
     15 
     16 /**
     17  * Sets of methods to manage the state machine.<br/>
     18  * <b>Implementation note:</b> This interface is accessed from multiple threads. The implementation must be thread safe.
     19  *
     20  * @author Pierre Frisch
     21  */
     22 public interface DNSStatefulObject {
     23 
     24     /**
     25      * This class define a semaphore. On this multiple threads can wait the arrival of one event. Thread wait for a maximum defined by the timeout.
     26      * <p>
     27      * Implementation note: this class is based on {@link java.util.concurrent.Semaphore} so that they can be released by the timeout timer.
     28      * </p>
     29      *
     30      * @author Pierre Frisch
     31      */
     32     public static final class DNSStatefulObjectSemaphore {
     33         private static Logger                          logger = Logger.getLogger(DNSStatefulObjectSemaphore.class.getName());
     34 
     35         private final String                           _name;
     36 
     37         private final ConcurrentMap<Thread, Semaphore> _semaphores;
     38 
     39         /**
     40          * @param name
     41          *            Semaphore name for debugging purposes.
     42          */
     43         public DNSStatefulObjectSemaphore(String name) {
     44             super();
     45             _name = name;
     46             _semaphores = new ConcurrentHashMap<Thread, Semaphore>(50);
     47         }
     48 
     49         /**
     50          * Blocks the current thread until the event arrives or the timeout expires.
     51          *
     52          * @param timeout
     53          *            wait period for the event
     54          */
     55         public void waitForEvent(long timeout) {
     56             Thread thread = Thread.currentThread();
     57             Semaphore semaphore = _semaphores.get(thread);
     58             if (semaphore == null) {
     59                 semaphore = new Semaphore(1, true);
     60                 semaphore.drainPermits();
     61                 _semaphores.putIfAbsent(thread, semaphore);
     62             }
     63             semaphore = _semaphores.get(thread);
     64             try {
     65                 semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS);
     66             } catch (InterruptedException exception) {
     67                 logger.log(Level.FINER, "Exception ", exception);
     68             }
     69         }
     70 
     71         /**
     72          * Signals the semaphore when the event arrives.
     73          */
     74         public void signalEvent() {
     75             Collection<Semaphore> semaphores = _semaphores.values();
     76             for (Semaphore semaphore : semaphores) {
     77                 semaphore.release();
     78                 semaphores.remove(semaphore);
     79             }
     80         }
     81 
     82         @Override
     83         public String toString() {
     84             StringBuilder aLog = new StringBuilder(1000);
     85             aLog.append("Semaphore: ");
     86             aLog.append(this._name);
     87             if (_semaphores.size() == 0) {
     88                 aLog.append(" no semaphores.");
     89             } else {
     90                 aLog.append(" semaphores:\n");
     91                 for (Thread thread : _semaphores.keySet()) {
     92                     aLog.append("\tThread: ");
     93                     aLog.append(thread.getName());
     94                     aLog.append(' ');
     95                     aLog.append(_semaphores.get(thread));
     96                     aLog.append('\n');
     97                 }
     98             }
     99             return aLog.toString();
    100         }
    101 
    102     }
    103 
    104     public static class DefaultImplementation extends ReentrantLock implements DNSStatefulObject {
    105         private static Logger                    logger           = Logger.getLogger(DefaultImplementation.class.getName());
    106 
    107         private static final long                serialVersionUID = -3264781576883412227L;
    108 
    109         private volatile JmDNSImpl               _dns;
    110 
    111         protected volatile DNSTask               _task;
    112 
    113         protected volatile DNSState              _state;
    114 
    115         private final DNSStatefulObjectSemaphore _announcing;
    116 
    117         private final DNSStatefulObjectSemaphore _canceling;
    118 
    119         public DefaultImplementation() {
    120             super();
    121             _dns = null;
    122             _task = null;
    123             _state = DNSState.PROBING_1;
    124             _announcing = new DNSStatefulObjectSemaphore("Announce");
    125             _canceling = new DNSStatefulObjectSemaphore("Cancel");
    126         }
    127 
    128         /**
    129          * {@inheritDoc}
    130          */
    131         @Override
    132         public JmDNSImpl getDns() {
    133             return this._dns;
    134         }
    135 
    136         protected void setDns(JmDNSImpl dns) {
    137             this._dns = dns;
    138         }
    139 
    140         /**
    141          * {@inheritDoc}
    142          */
    143         @Override
    144         public void associateWithTask(DNSTask task, DNSState state) {
    145             if (this._task == null && this._state == state) {
    146                 this.lock();
    147                 try {
    148                     if (this._task == null && this._state == state) {
    149                         this.setTask(task);
    150                     }
    151                 } finally {
    152                     this.unlock();
    153                 }
    154             }
    155         }
    156 
    157         /**
    158          * {@inheritDoc}
    159          */
    160         @Override
    161         public void removeAssociationWithTask(DNSTask task) {
    162             if (this._task == task) {
    163                 this.lock();
    164                 try {
    165                     if (this._task == task) {
    166                         this.setTask(null);
    167                     }
    168                 } finally {
    169                     this.unlock();
    170                 }
    171             }
    172         }
    173 
    174         /**
    175          * {@inheritDoc}
    176          */
    177         @Override
    178         public boolean isAssociatedWithTask(DNSTask task, DNSState state) {
    179             this.lock();
    180             try {
    181                 return this._task == task && this._state == state;
    182             } finally {
    183                 this.unlock();
    184             }
    185         }
    186 
    187         protected void setTask(DNSTask task) {
    188             this._task = task;
    189         }
    190 
    191         /**
    192          * @param state
    193          *            the state to set
    194          */
    195         protected void setState(DNSState state) {
    196             this.lock();
    197             try {
    198                 this._state = state;
    199                 if (this.isAnnounced()) {
    200                     _announcing.signalEvent();
    201                 }
    202                 if (this.isCanceled()) {
    203                     _canceling.signalEvent();
    204                     // clear any waiting announcing
    205                     _announcing.signalEvent();
    206                 }
    207             } finally {
    208                 this.unlock();
    209             }
    210         }
    211 
    212         /**
    213          * {@inheritDoc}
    214          */
    215         @Override
    216         public boolean advanceState(DNSTask task) {
    217             boolean result = true;
    218             if (this._task == task) {
    219                 this.lock();
    220                 try {
    221                     if (this._task == task) {
    222                         this.setState(this._state.advance());
    223                     } else {
    224                         logger.warning("Trying to advance state whhen not the owner. owner: " + this._task + " perpetrator: " + task);
    225                     }
    226                 } finally {
    227                     this.unlock();
    228                 }
    229             }
    230             return result;
    231         }
    232 
    233         /**
    234          * {@inheritDoc}
    235          */
    236         @Override
    237         public boolean revertState() {
    238             boolean result = true;
    239             if (!this.willCancel()) {
    240                 this.lock();
    241                 try {
    242                     if (!this.willCancel()) {
    243                         this.setState(this._state.revert());
    244                         this.setTask(null);
    245                     }
    246                 } finally {
    247                     this.unlock();
    248                 }
    249             }
    250             return result;
    251         }
    252 
    253         /**
    254          * {@inheritDoc}
    255          */
    256         @Override
    257         public boolean cancelState() {
    258             boolean result = false;
    259             if (!this.willCancel()) {
    260                 this.lock();
    261                 try {
    262                     if (!this.willCancel()) {
    263                         this.setState(DNSState.CANCELING_1);
    264                         this.setTask(null);
    265                         result = true;
    266                     }
    267                 } finally {
    268                     this.unlock();
    269                 }
    270             }
    271             return result;
    272         }
    273 
    274         /**
    275          * {@inheritDoc}
    276          */
    277         @Override
    278         public boolean closeState() {
    279             boolean result = false;
    280             if (!this.willClose()) {
    281                 this.lock();
    282                 try {
    283                     if (!this.willClose()) {
    284                         this.setState(DNSState.CLOSING);
    285                         this.setTask(null);
    286                         result = true;
    287                     }
    288                 } finally {
    289                     this.unlock();
    290                 }
    291             }
    292             return result;
    293         }
    294 
    295         /**
    296          * {@inheritDoc}
    297          */
    298         @Override
    299         public boolean recoverState() {
    300             boolean result = false;
    301             this.lock();
    302             try {
    303                 this.setState(DNSState.PROBING_1);
    304                 this.setTask(null);
    305             } finally {
    306                 this.unlock();
    307             }
    308             return result;
    309         }
    310 
    311         /**
    312          * {@inheritDoc}
    313          */
    314         @Override
    315         public boolean isProbing() {
    316             return this._state.isProbing();
    317         }
    318 
    319         /**
    320          * {@inheritDoc}
    321          */
    322         @Override
    323         public boolean isAnnouncing() {
    324             return this._state.isAnnouncing();
    325         }
    326 
    327         /**
    328          * {@inheritDoc}
    329          */
    330         @Override
    331         public boolean isAnnounced() {
    332             return this._state.isAnnounced();
    333         }
    334 
    335         /**
    336          * {@inheritDoc}
    337          */
    338         @Override
    339         public boolean isCanceling() {
    340             return this._state.isCanceling();
    341         }
    342 
    343         /**
    344          * {@inheritDoc}
    345          */
    346         @Override
    347         public boolean isCanceled() {
    348             return this._state.isCanceled();
    349         }
    350 
    351         /**
    352          * {@inheritDoc}
    353          */
    354         @Override
    355         public boolean isClosing() {
    356             return this._state.isClosing();
    357         }
    358 
    359         /**
    360          * {@inheritDoc}
    361          */
    362         @Override
    363         public boolean isClosed() {
    364             return this._state.isClosed();
    365         }
    366 
    367         private boolean willCancel() {
    368             return this._state.isCanceled() || this._state.isCanceling();
    369         }
    370 
    371         private boolean willClose() {
    372             return this._state.isClosed() || this._state.isClosing();
    373         }
    374 
    375         /**
    376          * {@inheritDoc}
    377          */
    378         @Override
    379         public boolean waitForAnnounced(long timeout) {
    380             if (!this.isAnnounced() && !this.willCancel()) {
    381                 _announcing.waitForEvent(timeout);
    382             }
    383             if (!this.isAnnounced()) {
    384                 if (this.willCancel() || this.willClose()) {
    385                     logger.fine("Wait for announced cancelled: " + this);
    386                 } else {
    387                     logger.warning("Wait for announced timed out: " + this);
    388                 }
    389             }
    390             return this.isAnnounced();
    391         }
    392 
    393         /**
    394          * {@inheritDoc}
    395          */
    396         @Override
    397         public boolean waitForCanceled(long timeout) {
    398             if (!this.isCanceled()) {
    399                 _canceling.waitForEvent(timeout);
    400             }
    401             if (!this.isCanceled() && !this.willClose()) {
    402                 logger.warning("Wait for canceled timed out: " + this);
    403             }
    404             return this.isCanceled();
    405         }
    406 
    407         /**
    408          * {@inheritDoc}
    409          */
    410         @Override
    411         public String toString() {
    412             return (_dns != null ? "DNS: " + _dns.getName() : "NO DNS") + " state: " + _state + " task: " + _task;
    413         }
    414 
    415     }
    416 
    417     /**
    418      * Returns the DNS associated with this object.
    419      *
    420      * @return DNS resolver
    421      */
    422     public JmDNSImpl getDns();
    423 
    424     /**
    425      * Sets the task associated with this Object.
    426      *
    427      * @param task
    428      *            associated task
    429      * @param state
    430      *            state of the task
    431      */
    432     public void associateWithTask(DNSTask task, DNSState state);
    433 
    434     /**
    435      * Remove the association of the task with this Object.
    436      *
    437      * @param task
    438      *            associated task
    439      */
    440     public void removeAssociationWithTask(DNSTask task);
    441 
    442     /**
    443      * Checks if this object is associated with the task and in the same state.
    444      *
    445      * @param task
    446      *            associated task
    447      * @param state
    448      *            state of the task
    449      * @return <code>true</code> is the task is associated with this object, <code>false</code> otherwise.
    450      */
    451     public boolean isAssociatedWithTask(DNSTask task, DNSState state);
    452 
    453     /**
    454      * Sets the state and notifies all objects that wait on the ServiceInfo.
    455      *
    456      * @param task
    457      *            associated task
    458      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
    459      * @see DNSState#advance()
    460      */
    461     public boolean advanceState(DNSTask task);
    462 
    463     /**
    464      * Sets the state and notifies all objects that wait on the ServiceInfo.
    465      *
    466      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
    467      * @see DNSState#revert()
    468      */
    469     public boolean revertState();
    470 
    471     /**
    472      * Sets the state and notifies all objects that wait on the ServiceInfo.
    473      *
    474      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
    475      */
    476     public boolean cancelState();
    477 
    478     /**
    479      * Sets the state and notifies all objects that wait on the ServiceInfo.
    480      *
    481      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
    482      */
    483     public boolean closeState();
    484 
    485     /**
    486      * Sets the state and notifies all objects that wait on the ServiceInfo.
    487      *
    488      * @return <code>true</code if the state was changed by this thread, <code>false</code> otherwise.
    489      */
    490     public boolean recoverState();
    491 
    492     /**
    493      * Returns true, if this is a probing state.
    494      *
    495      * @return <code>true</code> if probing state, <code>false</code> otherwise
    496      */
    497     public boolean isProbing();
    498 
    499     /**
    500      * Returns true, if this is an announcing state.
    501      *
    502      * @return <code>true</code> if announcing state, <code>false</code> otherwise
    503      */
    504     public boolean isAnnouncing();
    505 
    506     /**
    507      * Returns true, if this is an announced state.
    508      *
    509      * @return <code>true</code> if announced state, <code>false</code> otherwise
    510      */
    511     public boolean isAnnounced();
    512 
    513     /**
    514      * Returns true, if this is a canceling state.
    515      *
    516      * @return <code>true</code> if canceling state, <code>false</code> otherwise
    517      */
    518     public boolean isCanceling();
    519 
    520     /**
    521      * Returns true, if this is a canceled state.
    522      *
    523      * @return <code>true</code> if canceled state, <code>false</code> otherwise
    524      */
    525     public boolean isCanceled();
    526 
    527     /**
    528      * Returns true, if this is a closing state.
    529      *
    530      * @return <code>true</code> if closing state, <code>false</code> otherwise
    531      */
    532     public boolean isClosing();
    533 
    534     /**
    535      * Returns true, if this is a closed state.
    536      *
    537      * @return <code>true</code> if closed state, <code>false</code> otherwise
    538      */
    539     public boolean isClosed();
    540 
    541     /**
    542      * Waits for the object to be announced.
    543      *
    544      * @param timeout
    545      *            the maximum time to wait in milliseconds.
    546      * @return <code>true</code> if the object is announced, <code>false</code> otherwise
    547      */
    548     public boolean waitForAnnounced(long timeout);
    549 
    550     /**
    551      * Waits for the object to be canceled.
    552      *
    553      * @param timeout
    554      *            the maximum time to wait in milliseconds.
    555      * @return <code>true</code> if the object is canceled, <code>false</code> otherwise
    556      */
    557     public boolean waitForCanceled(long timeout);
    558 
    559 }
    560