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}