1/*
2 * libjingle
3 * Copyright 2011, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 *  1. Redistributions of source code must retain the above copyright notice,
9 *     this list of conditions and the following disclaimer.
10 *  2. Redistributions in binary form must reproduce the above copyright notice,
11 *     this list of conditions and the following disclaimer in the documentation
12 *     and/or other materials provided with the distribution.
13 *  3. The name of the author may not be used to endorse or promote products
14 *     derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28#ifndef TALK_XMPP_PUBSUBSTATECLIENT_H_
29#define TALK_XMPP_PUBSUBSTATECLIENT_H_
30
31#include <map>
32#include <string>
33#include <vector>
34
35#include "webrtc/libjingle/xmllite/qname.h"
36#include "webrtc/libjingle/xmllite/xmlelement.h"
37#include "talk/xmpp/constants.h"
38#include "talk/xmpp/jid.h"
39#include "talk/xmpp/pubsubclient.h"
40#include "webrtc/base/scoped_ptr.h"
41#include "webrtc/base/sigslot.h"
42#include "webrtc/base/sigslotrepeater.h"
43
44namespace buzz {
45
46// To handle retracts correctly, we need to remember certain details
47// about an item.  We could just cache the entire XML element, but
48// that would take more memory and require re-parsing.
49struct StateItemInfo {
50  std::string published_nick;
51  std::string publisher_nick;
52};
53
54// Represents a PubSub state change.  Usually, the key is the nick,
55// but not always.  It's a per-state-type thing.  Look below on how keys are
56// computed.
57template <typename C>
58struct PubSubStateChange {
59  // The nick of the user changing the state.
60  std::string publisher_nick;
61  // The nick of the user whose state is changing.
62  std::string published_nick;
63  C old_state;
64  C new_state;
65};
66
67// Knows how to handle specific states and XML.
68template <typename C>
69class PubSubStateSerializer {
70 public:
71  virtual ~PubSubStateSerializer() {}
72  virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
73  virtual void Parse(const XmlElement* state_elem, C* state_out) = 0;
74};
75
76// Knows how to create "keys" for states, which determines their
77// uniqueness.  Most states are per-nick, but block is
78// per-blocker-and-blockee.  This is independent of itemid, especially
79// in the case of presenter state.
80class PubSubStateKeySerializer {
81 public:
82  virtual ~PubSubStateKeySerializer() {}
83  virtual std::string GetKey(const std::string& publisher_nick,
84                             const std::string& published_nick) = 0;
85};
86
87class PublishedNickKeySerializer : public PubSubStateKeySerializer {
88 public:
89  virtual std::string GetKey(const std::string& publisher_nick,
90                             const std::string& published_nick);
91};
92
93class PublisherAndPublishedNicksKeySerializer
94  : public PubSubStateKeySerializer {
95 public:
96  virtual std::string GetKey(const std::string& publisher_nick,
97                             const std::string& published_nick);
98};
99
100// Adapts PubSubClient to be specifically suited for pub sub call
101// states.  Signals state changes and keeps track of keys, which are
102// normally nicks.
103template <typename C>
104class PubSubStateClient : public sigslot::has_slots<> {
105 public:
106  // Gets ownership of the serializers, but not the client.
107  PubSubStateClient(const std::string& publisher_nick,
108                    PubSubClient* client,
109                    const QName& state_name,
110                    C default_state,
111                    PubSubStateKeySerializer* key_serializer,
112                    PubSubStateSerializer<C>* state_serializer)
113      : publisher_nick_(publisher_nick),
114        client_(client),
115        state_name_(state_name),
116        default_state_(default_state) {
117    key_serializer_.reset(key_serializer);
118    state_serializer_.reset(state_serializer);
119    client_->SignalItems.connect(
120        this, &PubSubStateClient<C>::OnItems);
121    client_->SignalPublishResult.connect(
122        this, &PubSubStateClient<C>::OnPublishResult);
123    client_->SignalPublishError.connect(
124        this, &PubSubStateClient<C>::OnPublishError);
125    client_->SignalRetractResult.connect(
126        this, &PubSubStateClient<C>::OnRetractResult);
127    client_->SignalRetractError.connect(
128        this, &PubSubStateClient<C>::OnRetractError);
129  }
130
131  virtual ~PubSubStateClient() {}
132
133  virtual void Publish(const std::string& published_nick,
134                       const C& state,
135                       std::string* task_id_out) {
136    std::string key = key_serializer_->GetKey(publisher_nick_, published_nick);
137    std::string itemid = state_name_.LocalPart() + ":" + key;
138    if (StatesEqual(state, default_state_)) {
139      client_->RetractItem(itemid, task_id_out);
140    } else {
141      XmlElement* state_elem = state_serializer_->Write(state_name_, state);
142      state_elem->AddAttr(QN_NICK, published_nick);
143      client_->PublishItem(itemid, state_elem, task_id_out);
144    }
145  }
146
147  sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
148  // Signal (task_id, item).  item is NULL for retract.
149  sigslot::signal2<const std::string&,
150  const XmlElement*> SignalPublishResult;
151  // Signal (task_id, item, error stanza).  item is NULL for retract.
152  sigslot::signal3<const std::string&,
153  const XmlElement*,
154  const XmlElement*> SignalPublishError;
155
156 protected:
157  // return false if retracted item (no info or state given)
158  virtual bool ParseStateItem(const PubSubItem& item,
159                              StateItemInfo* info_out,
160                              C* state_out) {
161    const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
162    if (state_elem == NULL) {
163      return false;
164    }
165
166    info_out->publisher_nick =
167        client_->GetPublisherNickFromPubSubItem(item.elem);
168    info_out->published_nick = state_elem->Attr(QN_NICK);
169    state_serializer_->Parse(state_elem, state_out);
170    return true;
171  }
172
173  virtual bool StatesEqual(const C& state1, const C& state2) {
174    return state1 == state2;
175  }
176
177  PubSubClient* client() { return client_; }
178  const QName& state_name() { return state_name_; }
179
180 private:
181  void OnItems(PubSubClient* pub_sub_client,
182               const std::vector<PubSubItem>& items) {
183    for (std::vector<PubSubItem>::const_iterator item = items.begin();
184         item != items.end(); ++item) {
185      OnItem(*item);
186    }
187  }
188
189  void OnItem(const PubSubItem& item) {
190    const std::string& itemid = item.itemid;
191    StateItemInfo info;
192    C new_state;
193
194    bool retracted = !ParseStateItem(item, &info, &new_state);
195    if (retracted) {
196      bool known_itemid =
197          (info_by_itemid_.find(itemid) != info_by_itemid_.end());
198      if (!known_itemid) {
199        // Nothing to retract, and nothing to publish.
200        // Probably a different state type.
201        return;
202      } else {
203        info = info_by_itemid_[itemid];
204        info_by_itemid_.erase(itemid);
205        new_state = default_state_;
206      }
207    } else {
208      // TODO: Assert new key matches the known key. It
209      // shouldn't change!
210      info_by_itemid_[itemid] = info;
211    }
212
213    std::string key = key_serializer_->GetKey(
214        info.publisher_nick, info.published_nick);
215    bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
216    C old_state = has_old_state ? state_by_key_[key] : default_state_;
217    if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
218      // Nothing change, so don't bother signalling.
219      return;
220    }
221
222    if (retracted || StatesEqual(new_state, default_state_)) {
223      // We treat a default state similar to a retract.
224      state_by_key_.erase(key);
225    } else {
226      state_by_key_[key] = new_state;
227    }
228
229    PubSubStateChange<C> change;
230    if (!retracted) {
231      // Retracts do not have publisher information.
232      change.publisher_nick = info.publisher_nick;
233    }
234    change.published_nick = info.published_nick;
235    change.old_state = old_state;
236    change.new_state = new_state;
237    SignalStateChange(change);
238  }
239
240  void OnPublishResult(PubSubClient* pub_sub_client,
241                       const std::string& task_id,
242                       const XmlElement* item) {
243    SignalPublishResult(task_id, item);
244  }
245
246  void OnPublishError(PubSubClient* pub_sub_client,
247                      const std::string& task_id,
248                      const buzz::XmlElement* item,
249                      const buzz::XmlElement* stanza) {
250    SignalPublishError(task_id, item, stanza);
251  }
252
253  void OnRetractResult(PubSubClient* pub_sub_client,
254                       const std::string& task_id) {
255    // There's no point in differentiating between publish and retract
256    // errors, so we simplify by making them both signal a publish
257    // result.
258    const XmlElement* item = NULL;
259    SignalPublishResult(task_id, item);
260  }
261
262  void OnRetractError(PubSubClient* pub_sub_client,
263                      const std::string& task_id,
264                      const buzz::XmlElement* stanza) {
265    // There's no point in differentiating between publish and retract
266    // errors, so we simplify by making them both signal a publish
267    // error.
268    const XmlElement* item = NULL;
269    SignalPublishError(task_id, item, stanza);
270  }
271
272  std::string publisher_nick_;
273  PubSubClient* client_;
274  const QName state_name_;
275  C default_state_;
276  rtc::scoped_ptr<PubSubStateKeySerializer> key_serializer_;
277  rtc::scoped_ptr<PubSubStateSerializer<C> > state_serializer_;
278  // key => state
279  std::map<std::string, C> state_by_key_;
280  // itemid => StateItemInfo
281  std::map<std::string, StateItemInfo> info_by_itemid_;
282
283  DISALLOW_COPY_AND_ASSIGN(PubSubStateClient);
284};
285}  // namespace buzz
286
287#endif  // TALK_XMPP_PUBSUBSTATECLIENT_H_
288