Home | History | Annotate | Download | only in xmpp
      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 
     44 namespace 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.
     49 struct 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.
     57 template <typename C>
     58 struct 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.
     68 template <typename C>
     69 class 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.
     80 class 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 
     87 class PublishedNickKeySerializer : public PubSubStateKeySerializer {
     88  public:
     89   virtual std::string GetKey(const std::string& publisher_nick,
     90                              const std::string& published_nick);
     91 };
     92 
     93 class 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.
    103 template <typename C>
    104 class 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