SynchronizationPoint.java

  1. /**
  2.  *
  3.  * Copyright © 2014 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack;

  18. import java.util.concurrent.TimeUnit;
  19. import java.util.concurrent.locks.Condition;
  20. import java.util.concurrent.locks.Lock;
  21. import java.util.logging.Level;
  22. import java.util.logging.Logger;

  23. import org.jivesoftware.smack.SmackException.NoResponseException;
  24. import org.jivesoftware.smack.SmackException.NotConnectedException;
  25. import org.jivesoftware.smack.packet.TopLevelStreamElement;
  26. import org.jivesoftware.smack.packet.Stanza;
  27. import org.jivesoftware.smack.packet.PlainStreamElement;

  28. public class SynchronizationPoint<E extends Exception> {

  29.     private static final Logger LOGGER = Logger.getLogger(SynchronizationPoint.class.getName());

  30.     private final AbstractXMPPConnection connection;
  31.     private final Lock connectionLock;
  32.     private final Condition condition;

  33.     // Note that there is no need to make 'state' and 'failureException' volatile. Since 'lock' and 'unlock' have the
  34.     // same memory synchronization effects as synchronization block enter and leave.
  35.     private State state;
  36.     private E failureException;

  37.     public SynchronizationPoint(AbstractXMPPConnection connection) {
  38.         this.connection = connection;
  39.         this.connectionLock = connection.getConnectionLock();
  40.         this.condition = connection.getConnectionLock().newCondition();
  41.         init();
  42.     }

  43.     public void init() {
  44.         connectionLock.lock();
  45.         state = State.Initial;
  46.         failureException = null;
  47.         connectionLock.unlock();
  48.     }

  49.     public void sendAndWaitForResponse(TopLevelStreamElement request) throws NoResponseException,
  50.                     NotConnectedException, InterruptedException {
  51.         assert (state == State.Initial);
  52.         connectionLock.lock();
  53.         try {
  54.             if (request != null) {
  55.                 if (request instanceof Stanza) {
  56.                     connection.sendStanza((Stanza) request);
  57.                 }
  58.                 else if (request instanceof PlainStreamElement){
  59.                     connection.send((PlainStreamElement) request);
  60.                 } else {
  61.                     throw new IllegalStateException("Unsupported element type");
  62.                 }
  63.                 state = State.RequestSent;
  64.             }
  65.             waitForConditionOrTimeout();
  66.         }
  67.         finally {
  68.             connectionLock.unlock();
  69.         }
  70.         checkForResponse();
  71.     }

  72.     public void sendAndWaitForResponseOrThrow(PlainStreamElement request) throws E, NoResponseException,
  73.                     NotConnectedException, InterruptedException {
  74.         sendAndWaitForResponse(request);
  75.         switch (state) {
  76.         case Failure:
  77.             if (failureException != null) {
  78.                 throw failureException;
  79.             }
  80.             break;
  81.         default:
  82.             // Success, do nothing
  83.         }
  84.     }

  85.     public void checkIfSuccessOrWaitOrThrow() throws NoResponseException, E {
  86.         checkIfSuccessOrWait();
  87.         if (state == State.Failure) {
  88.             throw failureException;
  89.         }
  90.     }

  91.     public void checkIfSuccessOrWait() throws NoResponseException {
  92.         connectionLock.lock();
  93.         try {
  94.             if (state == State.Success) {
  95.                 // Return immediately
  96.                 return;
  97.             }
  98.             waitForConditionOrTimeout();
  99.         } finally {
  100.             connectionLock.unlock();
  101.         }
  102.         checkForResponse();
  103.     }

  104.     public void reportSuccess() {
  105.         connectionLock.lock();
  106.         try {
  107.             state = State.Success;
  108.             condition.signal();
  109.         }
  110.         finally {
  111.             connectionLock.unlock();
  112.         }
  113.     }

  114.     public void reportFailure() {
  115.         reportFailure(null);
  116.     }

  117.     public void reportFailure(E failureException) {
  118.         connectionLock.lock();
  119.         try {
  120.             state = State.Failure;
  121.             this.failureException = failureException;
  122.             condition.signal();
  123.         }
  124.         finally {
  125.             connectionLock.unlock();
  126.         }
  127.     }

  128.     public boolean wasSuccessful() {
  129.         connectionLock.lock();
  130.         try {
  131.             return state == State.Success;
  132.         }
  133.         finally {
  134.             connectionLock.unlock();
  135.         }
  136.     }

  137.     public boolean requestSent() {
  138.         connectionLock.lock();
  139.         try {
  140.             return state == State.RequestSent;
  141.         }
  142.         finally {
  143.             connectionLock.unlock();
  144.         }
  145.     }

  146.     private void waitForConditionOrTimeout() {
  147.         long remainingWait = TimeUnit.MILLISECONDS.toNanos(connection.getPacketReplyTimeout());
  148.         while (state == State.RequestSent || state == State.Initial) {
  149.             try {
  150.                 remainingWait = condition.awaitNanos(
  151.                                 remainingWait);
  152.                 if (remainingWait <= 0) {
  153.                     state = State.NoResponse;
  154.                     break;
  155.                 }
  156.             } catch (InterruptedException e) {
  157.                 // This InterruptedException could be "spurious wakeups", see javadoc of awaitNanos()
  158.                 LOGGER.log(Level.WARNING, "Thread interrupt while waiting for condition or timeout ignored", e);
  159.             }
  160.         }
  161.     }

  162.     /**
  163.      * Check for a response and throw a {@link NoResponseException} if there was none.
  164.      * <p>
  165.      * The exception is thrown, if state is one of 'Initial', 'NoResponse' or 'RequestSent'
  166.      * </p>
  167.      * @throws NoResponseException
  168.      */
  169.     private void checkForResponse() throws NoResponseException {
  170.         switch (state) {
  171.         case Initial:
  172.         case NoResponse:
  173.         case RequestSent:
  174.             throw NoResponseException.newWith(connection);
  175.         default:
  176.             // Do nothing
  177.             break;
  178.         }
  179.     }

  180.     private enum State {
  181.         Initial,
  182.         RequestSent,
  183.         NoResponse,
  184.         Success,
  185.         Failure,
  186.     }
  187. }