001/** 002 * 003 * Copyright 2018 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack; 018 019import java.util.HashMap; 020import java.util.Map; 021 022import org.jivesoftware.smack.SmackException.NoResponseException; 023import org.jivesoftware.smack.SmackException.NotConnectedException; 024import org.jivesoftware.smack.XMPPException.FailedNonzaException; 025import org.jivesoftware.smack.packet.Nonza; 026import org.jivesoftware.smack.util.XmppElementUtil; 027 028import org.jxmpp.util.XmppStringUtils; 029 030public class NonzaCallback { 031 032 protected final AbstractXMPPConnection connection; 033 protected final Map<String, GenericElementListener<? extends Nonza>> filterAndListeners; 034 035 private NonzaCallback(Builder builder) { 036 this.connection = builder.connection; 037 this.filterAndListeners = builder.filterAndListeners; 038 install(); 039 } 040 041 void onNonzaReceived(Nonza nonza) { 042 String key = XmppStringUtils.generateKey(nonza.getElementName(), nonza.getNamespace()); 043 GenericElementListener<? extends Nonza> nonzaListener = filterAndListeners.get(key); 044 045 nonzaListener.processElement(nonza); 046 } 047 048 public void cancel() { 049 synchronized (connection.nonzaCallbacks) { 050 for (Map.Entry<String, GenericElementListener<? extends Nonza>> entry : filterAndListeners.entrySet()) { 051 String filterKey = entry.getKey(); 052 NonzaCallback installedCallback = connection.nonzaCallbacks.get(filterKey); 053 if (equals(installedCallback)) { 054 connection.nonzaCallbacks.remove(filterKey); 055 } 056 } 057 } 058 } 059 060 protected void install() { 061 if (filterAndListeners.isEmpty()) { 062 return; 063 } 064 065 synchronized (connection.nonzaCallbacks) { 066 for (String key : filterAndListeners.keySet()) { 067 connection.nonzaCallbacks.put(key, this); 068 } 069 } 070 } 071 072 private static final class NonzaResponseCallback<SN extends Nonza, FN extends Nonza> extends NonzaCallback { 073 074 private SN successNonza; 075 private FN failedNonza; 076 077 private NonzaResponseCallback(Class<? extends SN> successNonzaClass, Class<? extends FN> failedNonzaClass, 078 Builder builder) { 079 super(builder); 080 081 final String successNonzaKey = XmppElementUtil.getKeyFor(successNonzaClass); 082 final String failedNonzaKey = XmppElementUtil.getKeyFor(failedNonzaClass); 083 084 final GenericElementListener<SN> successListener = new GenericElementListener<SN>(successNonzaClass) { 085 @Override 086 public void process(SN successNonza) { 087 NonzaResponseCallback.this.successNonza = successNonza; 088 notifyResponse(); 089 } 090 }; 091 092 final GenericElementListener<FN> failedListener = new GenericElementListener<FN>(failedNonzaClass) { 093 @Override 094 public void process(FN failedNonza) { 095 NonzaResponseCallback.this.failedNonza = failedNonza; 096 notifyResponse(); 097 } 098 }; 099 100 filterAndListeners.put(successNonzaKey, successListener); 101 filterAndListeners.put(failedNonzaKey, failedListener); 102 103 install(); 104 } 105 106 private void notifyResponse() { 107 synchronized (this) { 108 notifyAll(); 109 } 110 } 111 112 private boolean hasReceivedSuccessOrFailedNonza() { 113 return successNonza != null || failedNonza != null; 114 } 115 116 private SN waitForResponse() throws NoResponseException, InterruptedException, FailedNonzaException { 117 final long deadline = System.currentTimeMillis() + connection.getReplyTimeout(); 118 synchronized (this) { 119 while (!hasReceivedSuccessOrFailedNonza()) { 120 final long now = System.currentTimeMillis(); 121 if (now >= deadline) break; 122 wait(deadline - now); 123 } 124 } 125 126 if (!hasReceivedSuccessOrFailedNonza()) { 127 throw NoResponseException.newWith(connection, "Nonza Listener"); 128 } 129 130 if (failedNonza != null) { 131 throw new XMPPException.FailedNonzaException(failedNonza); 132 } 133 134 assert successNonza != null; 135 return successNonza; 136 } 137 } 138 139 public static final class Builder { 140 private final AbstractXMPPConnection connection; 141 142 private Map<String, GenericElementListener<? extends Nonza>> filterAndListeners = new HashMap<>(); 143 144 Builder(AbstractXMPPConnection connection) { 145 this.connection = connection; 146 } 147 148 public <N extends Nonza> Builder listenFor(Class<? extends N> nonza, GenericElementListener<? extends N> nonzaListener) { 149 String key = XmppElementUtil.getKeyFor(nonza); 150 filterAndListeners.put(key, nonzaListener); 151 return this; 152 } 153 154 public NonzaCallback install() { 155 return new NonzaCallback(this); 156 } 157 } 158 159 static <SN extends Nonza, FN extends Nonza> SN sendAndWaitForResponse(NonzaCallback.Builder builder, Nonza nonza, Class<SN> successNonzaClass, 160 Class<FN> failedNonzaClass) 161 throws NoResponseException, NotConnectedException, InterruptedException, FailedNonzaException { 162 NonzaResponseCallback<SN, FN> nonzaCallback = new NonzaResponseCallback<>(successNonzaClass, 163 failedNonzaClass, builder); 164 165 SN successNonza; 166 try { 167 nonzaCallback.connection.sendNonza(nonza); 168 successNonza = nonzaCallback.waitForResponse(); 169 } 170 finally { 171 nonzaCallback.cancel(); 172 } 173 174 return successNonza; 175 } 176}