NonzaCallback.java

  1. /**
  2.  *
  3.  * Copyright 2018-2019 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.io.IOException;
  19. import java.util.HashMap;
  20. import java.util.Map;

  21. import javax.xml.namespace.QName;

  22. import org.jivesoftware.smack.SmackException.NoResponseException;
  23. import org.jivesoftware.smack.SmackException.NotConnectedException;
  24. import org.jivesoftware.smack.XMPPException.FailedNonzaException;
  25. import org.jivesoftware.smack.packet.Nonza;
  26. import org.jivesoftware.smack.util.XmppElementUtil;

  27. public class NonzaCallback {

  28.     protected final AbstractXMPPConnection connection;
  29.     protected final Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners;

  30.     private NonzaCallback(Builder builder) {
  31.         this.connection = builder.connection;
  32.         this.filterAndListeners = builder.filterAndListeners;
  33.         install();
  34.     }

  35.     void onNonzaReceived(Nonza nonza) throws IOException {
  36.         QName key = nonza.getQName();
  37.         ClassAndConsumer<? extends Nonza> classAndConsumer = filterAndListeners.get(key);

  38.         classAndConsumer.accept(nonza);
  39.     }

  40.     public void cancel() {
  41.         for (Map.Entry<QName, ClassAndConsumer<? extends Nonza>> entry : filterAndListeners.entrySet()) {
  42.             QName filterKey = entry.getKey();
  43.             synchronized (connection.nonzaCallbacksMap) {
  44.                 connection.nonzaCallbacksMap.removeOne(filterKey, this);
  45.             }
  46.         }
  47.     }

  48.     protected void install() {
  49.         if (filterAndListeners.isEmpty()) {
  50.             return;
  51.         }

  52.         for (QName key : filterAndListeners.keySet()) {
  53.             synchronized (connection.nonzaCallbacksMap) {
  54.                 connection.nonzaCallbacksMap.put(key, this);
  55.             }
  56.         }
  57.     }

  58.     private static final class NonzaResponseCallback<SN extends Nonza, FN extends Nonza> extends NonzaCallback {

  59.         private SN successNonza;
  60.         private FN failedNonza;

  61.         private NonzaResponseCallback(Class<SN> successNonzaClass, Class<FN> failedNonzaClass,
  62.                         Builder builder) {
  63.             super(builder);

  64.             final QName successNonzaKey = XmppElementUtil.getQNameFor(successNonzaClass);
  65.             final QName failedNonzaKey = XmppElementUtil.getQNameFor(failedNonzaClass);

  66.             final NonzaListener<SN> successListener = new NonzaListener<SN>() {
  67.                 @Override
  68.                 public void accept(SN successNonza) {
  69.                     NonzaResponseCallback.this.successNonza = successNonza;
  70.                     notifyResponse();
  71.                 }
  72.             };
  73.             final ClassAndConsumer<SN> successClassAndConsumer = new ClassAndConsumer<>(successNonzaClass,
  74.                             successListener);

  75.             final NonzaListener<FN> failedListener = new NonzaListener<FN>() {
  76.                 @Override
  77.                 public void accept(FN failedNonza) {
  78.                     NonzaResponseCallback.this.failedNonza = failedNonza;
  79.                     notifyResponse();
  80.                 }
  81.             };
  82.             final ClassAndConsumer<FN> failedClassAndConsumer = new ClassAndConsumer<>(failedNonzaClass,
  83.                             failedListener);

  84.             filterAndListeners.put(successNonzaKey, successClassAndConsumer);
  85.             filterAndListeners.put(failedNonzaKey, failedClassAndConsumer);

  86.             install();
  87.         }

  88.         private void notifyResponse() {
  89.             synchronized (this) {
  90.                 notifyAll();
  91.             }
  92.         }

  93.         private boolean hasReceivedSuccessOrFailedNonza() {
  94.             return successNonza != null || failedNonza != null;
  95.         }

  96.         private SN waitForResponse() throws NoResponseException, InterruptedException, FailedNonzaException {
  97.             final long deadline = System.currentTimeMillis() + connection.getReplyTimeout();
  98.             synchronized (this) {
  99.                 while (!hasReceivedSuccessOrFailedNonza()) {
  100.                     final long now = System.currentTimeMillis();
  101.                     if (now >= deadline) break;
  102.                     wait(deadline - now);
  103.                 }
  104.             }

  105.             if (!hasReceivedSuccessOrFailedNonza()) {
  106.                 throw NoResponseException.newWith(connection, "Nonza Listener");
  107.             }

  108.             if (failedNonza != null) {
  109.                 throw new XMPPException.FailedNonzaException(failedNonza);
  110.             }

  111.             assert successNonza != null;
  112.             return successNonza;
  113.         }
  114.     }

  115.     public static final class Builder {
  116.         private final AbstractXMPPConnection connection;

  117.         private Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners = new HashMap<>();

  118.         Builder(AbstractXMPPConnection connection) {
  119.             this.connection = connection;
  120.         }

  121.         public <N extends Nonza> Builder listenFor(Class<N> nonza, NonzaListener<N> nonzaListener) {
  122.             QName key = XmppElementUtil.getQNameFor(nonza);
  123.             ClassAndConsumer<N> classAndConsumer = new ClassAndConsumer<>(nonza, nonzaListener);
  124.             filterAndListeners.put(key, classAndConsumer);
  125.             return this;
  126.         }

  127.         public NonzaCallback install() {
  128.             return new NonzaCallback(this);
  129.         }
  130.     }

  131.     public interface NonzaListener<N extends Nonza> {
  132.         void accept(N nonza) throws IOException;
  133.     }

  134.     private static final class ClassAndConsumer<N extends Nonza> {
  135.         private final Class<N> clazz;
  136.         private final NonzaListener<N> consumer;

  137.         private ClassAndConsumer(Class<N> clazz, NonzaListener<N> consumer) {
  138.             this.clazz = clazz;
  139.             this.consumer = consumer;
  140.         }

  141.         private void accept(Object object) throws IOException {
  142.             N nonza = clazz.cast(object);
  143.             consumer.accept(nonza);
  144.         }
  145.     }

  146.     static <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class<SN> successNonzaClass,
  147.                     Class<FN> failedNonzaClass)
  148.                     throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException {
  149.         NonzaResponseCallback<SN, FN> nonzaCallback = new NonzaResponseCallback<>(successNonzaClass,
  150.                         failedNonzaClass, builder);

  151.         SN successNonza;
  152.         try {
  153.             nonzaCallback.connection.sendNonza(nonza);
  154.             successNonza = nonzaCallback.waitForResponse();
  155.         }
  156.         finally {
  157.             nonzaCallback.cancel();
  158.         }

  159.         return successNonza;
  160.     }
  161. }