NonzaCallback.java
/**
*
* Copyright 2018-2019 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.SmackException.NoResponseException;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.XMPPException.FailedNonzaException;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.util.XmppElementUtil;
public class NonzaCallback {
protected final AbstractXMPPConnection connection;
protected final Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners;
private NonzaCallback(Builder builder) {
this.connection = builder.connection;
this.filterAndListeners = builder.filterAndListeners;
install();
}
void onNonzaReceived(Nonza nonza) throws IOException {
QName key = nonza.getQName();
ClassAndConsumer<? extends Nonza> classAndConsumer = filterAndListeners.get(key);
classAndConsumer.accept(nonza);
}
public void cancel() {
for (Map.Entry<QName, ClassAndConsumer<? extends Nonza>> entry : filterAndListeners.entrySet()) {
QName filterKey = entry.getKey();
synchronized (connection.nonzaCallbacksMap) {
connection.nonzaCallbacksMap.removeOne(filterKey, this);
}
}
}
protected void install() {
if (filterAndListeners.isEmpty()) {
return;
}
for (QName key : filterAndListeners.keySet()) {
synchronized (connection.nonzaCallbacksMap) {
connection.nonzaCallbacksMap.put(key, this);
}
}
}
private static final class NonzaResponseCallback<SN extends Nonza, FN extends Nonza> extends NonzaCallback {
private SN successNonza;
private FN failedNonza;
private NonzaResponseCallback(Class<SN> successNonzaClass, Class<FN> failedNonzaClass,
Builder builder) {
super(builder);
final QName successNonzaKey = XmppElementUtil.getQNameFor(successNonzaClass);
final QName failedNonzaKey = XmppElementUtil.getQNameFor(failedNonzaClass);
final NonzaListener<SN> successListener = new NonzaListener<SN>() {
@Override
public void accept(SN successNonza) {
NonzaResponseCallback.this.successNonza = successNonza;
notifyResponse();
}
};
final ClassAndConsumer<SN> successClassAndConsumer = new ClassAndConsumer<>(successNonzaClass,
successListener);
final NonzaListener<FN> failedListener = new NonzaListener<FN>() {
@Override
public void accept(FN failedNonza) {
NonzaResponseCallback.this.failedNonza = failedNonza;
notifyResponse();
}
};
final ClassAndConsumer<FN> failedClassAndConsumer = new ClassAndConsumer<>(failedNonzaClass,
failedListener);
filterAndListeners.put(successNonzaKey, successClassAndConsumer);
filterAndListeners.put(failedNonzaKey, failedClassAndConsumer);
install();
}
private void notifyResponse() {
synchronized (this) {
notifyAll();
}
}
private boolean hasReceivedSuccessOrFailedNonza() {
return successNonza != null || failedNonza != null;
}
private SN waitForResponse() throws NoResponseException, InterruptedException, FailedNonzaException {
final long deadline = System.currentTimeMillis() + connection.getReplyTimeout();
synchronized (this) {
while (!hasReceivedSuccessOrFailedNonza()) {
final long now = System.currentTimeMillis();
if (now >= deadline) break;
wait(deadline - now);
}
}
if (!hasReceivedSuccessOrFailedNonza()) {
throw NoResponseException.newWith(connection, "Nonza Listener");
}
if (failedNonza != null) {
throw new XMPPException.FailedNonzaException(failedNonza);
}
assert successNonza != null;
return successNonza;
}
}
public static final class Builder {
private final AbstractXMPPConnection connection;
private Map<QName, ClassAndConsumer<? extends Nonza>> filterAndListeners = new HashMap<>();
Builder(AbstractXMPPConnection connection) {
this.connection = connection;
}
public <N extends Nonza> Builder listenFor(Class<N> nonza, NonzaListener<N> nonzaListener) {
QName key = XmppElementUtil.getQNameFor(nonza);
ClassAndConsumer<N> classAndConsumer = new ClassAndConsumer<>(nonza, nonzaListener);
filterAndListeners.put(key, classAndConsumer);
return this;
}
public NonzaCallback install() {
return new NonzaCallback(this);
}
}
public interface NonzaListener<N extends Nonza> {
void accept(N nonza) throws IOException;
}
private static final class ClassAndConsumer<N extends Nonza> {
private final Class<N> clazz;
private final NonzaListener<N> consumer;
private ClassAndConsumer(Class<N> clazz, NonzaListener<N> consumer) {
this.clazz = clazz;
this.consumer = consumer;
}
private void accept(Object object) throws IOException {
N nonza = clazz.cast(object);
consumer.accept(nonza);
}
}
static <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class<SN> successNonzaClass,
Class<FN> failedNonzaClass)
throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException {
NonzaResponseCallback<SN, FN> nonzaCallback = new NonzaResponseCallback<>(successNonzaClass,
failedNonzaClass, builder);
SN successNonza;
try {
nonzaCallback.connection.sendNonza(nonza);
successNonza = nonzaCallback.waitForResponse();
}
finally {
nonzaCallback.cancel();
}
return successNonza;
}
}