PrivacyListManager.java

  1. /**
  2.  *
  3.  * Copyright 2006-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.privacy;

  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import java.util.Map;
  21. import java.util.Set;
  22. import java.util.WeakHashMap;
  23. import java.util.concurrent.CopyOnWriteArraySet;

  24. import org.jivesoftware.smack.AbstractConnectionListener;
  25. import org.jivesoftware.smack.SmackException.NoResponseException;
  26. import org.jivesoftware.smack.SmackException.NotConnectedException;
  27. import org.jivesoftware.smack.XMPPConnection;
  28. import org.jivesoftware.smack.ConnectionCreationListener;
  29. import org.jivesoftware.smack.Manager;
  30. import org.jivesoftware.smack.StanzaListener;
  31. import org.jivesoftware.smack.XMPPConnectionRegistry;
  32. import org.jivesoftware.smack.XMPPException.XMPPErrorException;
  33. import org.jivesoftware.smack.filter.AndFilter;
  34. import org.jivesoftware.smack.filter.IQResultReplyFilter;
  35. import org.jivesoftware.smack.filter.IQTypeFilter;
  36. import org.jivesoftware.smack.filter.StanzaFilter;
  37. import org.jivesoftware.smack.filter.StanzaTypeFilter;
  38. import org.jivesoftware.smack.iqrequest.AbstractIqRequestHandler;
  39. import org.jivesoftware.smack.iqrequest.IQRequestHandler.Mode;
  40. import org.jivesoftware.smack.packet.IQ;
  41. import org.jivesoftware.smack.packet.Stanza;
  42. import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
  43. import org.jivesoftware.smackx.privacy.filter.SetActiveListFilter;
  44. import org.jivesoftware.smackx.privacy.filter.SetDefaultListFilter;
  45. import org.jivesoftware.smackx.privacy.packet.Privacy;
  46. import org.jivesoftware.smackx.privacy.packet.PrivacyItem;

  47. /**
  48.  * A PrivacyListManager is used by XMPP clients to block or allow communications from other
  49.  * users. Use the manager to:
  50.  * <ul>
  51.  *      <li>Retrieve privacy lists.
  52.  *      <li>Add, remove, and edit privacy lists.
  53.  *      <li>Set, change, or decline active lists.
  54.  *      <li>Set, change, or decline the default list (i.e., the list that is active by default).
  55.  * </ul>
  56.  * Privacy Items can handle different kind of permission communications based on JID, group,
  57.  * subscription type or globally (see {@link PrivacyItem}).
  58.  *
  59.  * @author Francisco Vives
  60.  * @see <a href="http://xmpp.org/extensions/xep-0016.html">XEP-16: Privacy Lists</a>
  61.  */
  62. public class PrivacyListManager extends Manager {
  63.     public static final String NAMESPACE = Privacy.NAMESPACE;

  64.     public static final StanzaFilter PRIVACY_FILTER = new StanzaTypeFilter(Privacy.class);

  65.     private static final StanzaFilter PRIVACY_RESULT = new AndFilter(IQTypeFilter.RESULT, PRIVACY_FILTER);

  66.     // Keep the list of instances of this class.
  67.     private static final Map<XMPPConnection, PrivacyListManager> INSTANCES = new WeakHashMap<XMPPConnection, PrivacyListManager>();

  68.     private final Set<PrivacyListListener> listeners = new CopyOnWriteArraySet<PrivacyListListener>();

  69.     static {
  70.         // Create a new PrivacyListManager on every established connection.
  71.         XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() {
  72.             public void connectionCreated(XMPPConnection connection) {
  73.                 getInstanceFor(connection);
  74.             }
  75.         });
  76.     }

  77.     // TODO implement: private final Map<String, PrivacyList> cachedPrivacyLists = new HashMap<>();
  78.     private volatile String cachedActiveListName;
  79.     private volatile String cachedDefaultListName;

  80.     /**
  81.      * Creates a new privacy manager to maintain the communication privacy. Note: no
  82.      * information is sent to or received from the server until you attempt to
  83.      * get or set the privacy communication.<p>
  84.      *
  85.      * @param connection the XMPP connection.
  86.      */
  87.     private PrivacyListManager(XMPPConnection connection) {
  88.         super(connection);

  89.         connection.registerIQRequestHandler(new AbstractIqRequestHandler(Privacy.ELEMENT, Privacy.NAMESPACE,
  90.                         IQ.Type.set, Mode.sync) {
  91.             @Override
  92.             public IQ handleIQRequest(IQ iqRequest) {
  93.                 Privacy privacy = (Privacy) iqRequest;

  94.                 // Notifies the event to the listeners.
  95.                 for (PrivacyListListener listener : listeners) {
  96.                     // Notifies the created or updated privacy lists
  97.                     for (Map.Entry<String, List<PrivacyItem>> entry : privacy.getItemLists().entrySet()) {
  98.                         String listName = entry.getKey();
  99.                         List<PrivacyItem> items = entry.getValue();
  100.                         if (items.isEmpty()) {
  101.                             listener.updatedPrivacyList(listName);
  102.                         }
  103.                         else {
  104.                             listener.setPrivacyList(listName, items);
  105.                         }
  106.                     }
  107.                 }

  108.                 return IQ.createResultIQ(privacy);
  109.             }
  110.         });

  111.         // cached(Active|Default)ListName handling
  112.         connection.addPacketSendingListener(new StanzaListener() {
  113.             @Override
  114.             public void processPacket(Stanza packet) throws NotConnectedException {
  115.                 XMPPConnection connection = connection();
  116.                 Privacy privacy = (Privacy) packet;
  117.                 StanzaFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
  118.                 final String activeListName = privacy.getActiveName();
  119.                 final boolean declinceActiveList = privacy.isDeclineActiveList();
  120.                 connection.addOneTimeSyncCallback(new StanzaListener() {
  121.                     @Override
  122.                     public void processPacket(Stanza packet) throws NotConnectedException {
  123.                             if (declinceActiveList) {
  124.                                 cachedActiveListName = null;
  125.                             }
  126.                             else {
  127.                                 cachedActiveListName = activeListName;
  128.                             }
  129.                             return;
  130.                     }
  131.                 }, iqResultReplyFilter);
  132.             }
  133.         }, SetActiveListFilter.INSTANCE);
  134.         connection.addPacketSendingListener(new StanzaListener() {
  135.             @Override
  136.             public void processPacket(Stanza packet) throws NotConnectedException {
  137.                 XMPPConnection connection = connection();
  138.                 Privacy privacy = (Privacy) packet;
  139.                 StanzaFilter iqResultReplyFilter = new IQResultReplyFilter(privacy, connection);
  140.                 final String defaultListName = privacy.getDefaultName();
  141.                 final boolean declinceDefaultList = privacy.isDeclineDefaultList();
  142.                 connection.addOneTimeSyncCallback(new StanzaListener() {
  143.                     @Override
  144.                     public void processPacket(Stanza packet) throws NotConnectedException {
  145.                             if (declinceDefaultList) {
  146.                                 cachedDefaultListName = null;
  147.                             }
  148.                             else {
  149.                                 cachedDefaultListName = defaultListName;
  150.                             }
  151.                             return;
  152.                     }
  153.                 }, iqResultReplyFilter);
  154.             }
  155.         }, SetDefaultListFilter.INSTANCE);
  156.         connection.addSyncStanzaListener(new StanzaListener() {
  157.             @Override
  158.             public void processPacket(Stanza packet) throws NotConnectedException {
  159.                 Privacy privacy = (Privacy) packet;
  160.                 // If a privacy IQ result stanza has an active or default list name set, then we use that
  161.                 // as cached list name.
  162.                 String activeList = privacy.getActiveName();
  163.                 if (activeList != null) {
  164.                     cachedActiveListName = activeList;
  165.                 }
  166.                 String defaultList = privacy.getDefaultName();
  167.                 if (defaultList != null) {
  168.                     cachedDefaultListName = defaultList;
  169.                 }
  170.             }
  171.         }, PRIVACY_RESULT);
  172.         connection.addConnectionListener(new AbstractConnectionListener() {
  173.             @Override
  174.             public void authenticated(XMPPConnection connection, boolean resumed) {
  175.                 // No need to reset the cache if the connection got resumed.
  176.                 if (resumed) {
  177.                     return;
  178.                 }
  179.                 cachedActiveListName = cachedDefaultListName = null;
  180.             }
  181.         });

  182.         // XEP-0016 ยง 3.
  183.         ServiceDiscoveryManager.getInstanceFor(connection).addFeature(NAMESPACE);
  184.     }

  185.     /**
  186.      * Returns the PrivacyListManager instance associated with a given XMPPConnection.
  187.      *
  188.      * @param connection the connection used to look for the proper PrivacyListManager.
  189.      * @return the PrivacyListManager associated with a given XMPPConnection.
  190.      */
  191.     public static synchronized PrivacyListManager getInstanceFor(XMPPConnection connection) {
  192.         PrivacyListManager plm = INSTANCES.get(connection);
  193.         if (plm == null) {
  194.             plm = new PrivacyListManager(connection);
  195.             // Register the new instance and associate it with the connection
  196.             INSTANCES.put(connection, plm);
  197.         }
  198.         return plm;
  199.     }

  200.     /**
  201.      * Send the {@link Privacy} packet to the server in order to know some privacy content and then
  202.      * waits for the answer.
  203.      *
  204.      * @param requestPrivacy is the {@link Privacy} packet configured properly whose XML
  205.      *      will be sent to the server.
  206.      * @return a new {@link Privacy} with the data received from the server.
  207.      * @throws XMPPErrorException
  208.      * @throws NoResponseException
  209.      * @throws NotConnectedException
  210.      * @throws InterruptedException
  211.      */
  212.     private Privacy getRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  213.         // The request is a get iq type
  214.         requestPrivacy.setType(Privacy.Type.get);

  215.         return connection().createPacketCollectorAndSend(requestPrivacy).nextResultOrThrow();
  216.     }

  217.     /**
  218.      * Send the {@link Privacy} packet to the server in order to modify the server privacy and waits
  219.      * for the answer.
  220.      *
  221.      * @param requestPrivacy is the {@link Privacy} packet configured properly whose xml will be
  222.      *        sent to the server.
  223.      * @return a new {@link Privacy} with the data received from the server.
  224.      * @throws XMPPErrorException
  225.      * @throws NoResponseException
  226.      * @throws NotConnectedException
  227.      * @throws InterruptedException
  228.      */
  229.     private Stanza setRequest(Privacy requestPrivacy) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  230.         // The request is a get iq type
  231.         requestPrivacy.setType(Privacy.Type.set);

  232.         return connection().createPacketCollectorAndSend(requestPrivacy).nextResultOrThrow();
  233.     }

  234.     /**
  235.      * Answer a privacy containing the list structure without {@link PrivacyItem}.
  236.      *
  237.      * @return a Privacy with the list names.
  238.      * @throws XMPPErrorException
  239.      * @throws NoResponseException
  240.      * @throws NotConnectedException
  241.      * @throws InterruptedException
  242.      */
  243.     private Privacy getPrivacyWithListNames() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  244.         // The request of the list is an empty privacy message
  245.         Privacy request = new Privacy();
  246.        
  247.         // Send the package to the server and get the answer
  248.         return getRequest(request);
  249.     }

  250.     /**
  251.      * Answer the active privacy list.
  252.      *
  253.      * @return the privacy list of the active list.
  254.      * @throws XMPPErrorException
  255.      * @throws NoResponseException
  256.      * @throws NotConnectedException
  257.      * @throws InterruptedException
  258.      */
  259.     public PrivacyList getActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  260.         Privacy privacyAnswer = this.getPrivacyWithListNames();
  261.         String listName = privacyAnswer.getActiveName();
  262.         boolean isDefaultAndActive = listName != null && listName.equals(privacyAnswer.getDefaultName());
  263.         return new PrivacyList(true, isDefaultAndActive, listName, getPrivacyListItems(listName));
  264.     }

  265.     /**
  266.      * Get the name of the active list.
  267.      *
  268.      * @return the name of the active list or null if there is none set.
  269.      * @throws NoResponseException
  270.      * @throws XMPPErrorException
  271.      * @throws NotConnectedException
  272.      * @throws InterruptedException
  273.      * @since 4.1
  274.      */
  275.     public String getActiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  276.         if (cachedActiveListName != null) {
  277.             return cachedActiveListName;
  278.         }
  279.         return getPrivacyWithListNames().getActiveName();
  280.     }

  281.     /**
  282.      * Answer the default privacy list.
  283.      *
  284.      * @return the privacy list of the default list.
  285.      * @throws XMPPErrorException
  286.      * @throws NoResponseException
  287.      * @throws NotConnectedException
  288.      * @throws InterruptedException
  289.      */
  290.     public PrivacyList getDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  291.         Privacy privacyAnswer = this.getPrivacyWithListNames();
  292.         String listName = privacyAnswer.getDefaultName();
  293.         boolean isDefaultAndActive = listName != null && listName.equals(privacyAnswer.getActiveName());
  294.         return new PrivacyList(isDefaultAndActive, true, listName, getPrivacyListItems(listName));
  295.     }

  296.     /**
  297.      * Get the name of the default list.
  298.      *
  299.      * @return the name of the default list or null if there is none set.
  300.      * @throws NoResponseException
  301.      * @throws XMPPErrorException
  302.      * @throws NotConnectedException
  303.      * @throws InterruptedException
  304.      * @since 4.1
  305.      */
  306.     public String getDefaultListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  307.         if (cachedDefaultListName != null) {
  308.             return cachedDefaultListName;
  309.         }
  310.         return getPrivacyWithListNames().getDefaultName();
  311.     }

  312.     /**
  313.      * Returns the name of the effective privacy list.
  314.      * <p>
  315.      * The effective privacy list is the one that is currently enforced on the connection. It's either the active
  316.      * privacy list, or, if the active privacy list is not set, the default privacy list.
  317.      * </p>
  318.      *
  319.      * @return the name of the effective privacy list or null if there is none set.
  320.      * @throws NoResponseException
  321.      * @throws XMPPErrorException
  322.      * @throws NotConnectedException
  323.      * @throws InterruptedException
  324.      * @since 4.1
  325.      */
  326.     public String getEffectiveListName() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  327.         String activeListName = getActiveListName();
  328.         if (activeListName != null) {
  329.             return activeListName;
  330.         }
  331.         return getDefaultListName();
  332.     }

  333.     /**
  334.      * Answer the privacy list items under listName with the allowed and blocked permissions.
  335.      *
  336.      * @param listName the name of the list to get the allowed and blocked permissions.
  337.      * @return a list of privacy items under the list listName.
  338.      * @throws XMPPErrorException
  339.      * @throws NoResponseException
  340.      * @throws NotConnectedException
  341.      * @throws InterruptedException
  342.      */
  343.     private List<PrivacyItem> getPrivacyListItems(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  344.         // The request of the list is an privacy message with an empty list
  345.         Privacy request = new Privacy();
  346.         request.setPrivacyList(listName, new ArrayList<PrivacyItem>());
  347.        
  348.         // Send the package to the server and get the answer
  349.         Privacy privacyAnswer = getRequest(request);
  350.        
  351.         return privacyAnswer.getPrivacyList(listName);
  352.     }

  353.     /**
  354.      * Answer the privacy list items under listName with the allowed and blocked permissions.
  355.      *
  356.      * @param listName the name of the list to get the allowed and blocked permissions.
  357.      * @return a privacy list under the list listName.
  358.      * @throws XMPPErrorException
  359.      * @throws NoResponseException
  360.      * @throws NotConnectedException
  361.      * @throws InterruptedException
  362.      */
  363.     public PrivacyList getPrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  364.         return new PrivacyList(false, false, listName, getPrivacyListItems(listName));
  365.     }

  366.     /**
  367.      * Answer every privacy list with the allowed and blocked permissions.
  368.      *
  369.      * @return an array of privacy lists.
  370.      * @throws XMPPErrorException
  371.      * @throws NoResponseException
  372.      * @throws NotConnectedException
  373.      * @throws InterruptedException
  374.      */
  375.     public List<PrivacyList> getPrivacyLists() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  376.         Privacy privacyAnswer = getPrivacyWithListNames();
  377.         Set<String> names = privacyAnswer.getPrivacyListNames();
  378.         List<PrivacyList> lists = new ArrayList<>(names.size());
  379.         for (String listName : names) {
  380.             boolean isActiveList = listName.equals(privacyAnswer.getActiveName());
  381.             boolean isDefaultList = listName.equals(privacyAnswer.getDefaultName());
  382.             lists.add(new PrivacyList(isActiveList, isDefaultList, listName,
  383.                             getPrivacyListItems(listName)));
  384.         }
  385.         return lists;
  386.     }

  387.     /**
  388.      * Set or change the active list to listName.
  389.      *
  390.      * @param listName the list name to set as the active one.
  391.      * @throws XMPPErrorException
  392.      * @throws NoResponseException
  393.      * @throws NotConnectedException
  394.      * @throws InterruptedException
  395.      */
  396.     public void setActiveListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  397.         // The request of the list is an privacy message with an empty list
  398.         Privacy request = new Privacy();
  399.         request.setActiveName(listName);
  400.        
  401.         // Send the package to the server
  402.         setRequest(request);
  403.     }

  404.     /**
  405.      * Client declines the use of active lists.
  406.      * @throws XMPPErrorException
  407.      * @throws NoResponseException
  408.      * @throws NotConnectedException
  409.      * @throws InterruptedException
  410.      */
  411.     public void declineActiveList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  412.         // The request of the list is an privacy message with an empty list
  413.         Privacy request = new Privacy();
  414.         request.setDeclineActiveList(true);
  415.        
  416.         // Send the package to the server
  417.         setRequest(request);
  418.     }

  419.     /**
  420.      * Set or change the default list to listName.
  421.      *
  422.      * @param listName the list name to set as the default one.
  423.      * @throws XMPPErrorException
  424.      * @throws NoResponseException
  425.      * @throws NotConnectedException
  426.      * @throws InterruptedException
  427.      */
  428.     public void setDefaultListName(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  429.         // The request of the list is an privacy message with an empty list
  430.         Privacy request = new Privacy();
  431.         request.setDefaultName(listName);
  432.        
  433.         // Send the package to the server
  434.         setRequest(request);
  435.     }

  436.     /**
  437.      * Client declines the use of default lists.
  438.      * @throws XMPPErrorException
  439.      * @throws NoResponseException
  440.      * @throws NotConnectedException
  441.      * @throws InterruptedException
  442.      */
  443.     public void declineDefaultList() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  444.         // The request of the list is an privacy message with an empty list
  445.         Privacy request = new Privacy();
  446.         request.setDeclineDefaultList(true);
  447.        
  448.         // Send the package to the server
  449.         setRequest(request);
  450.     }

  451.     /**
  452.      * The client has created a new list. It send the new one to the server.
  453.      *
  454.      * @param listName the list that has changed its content.
  455.      * @param privacyItems a List with every privacy item in the list.
  456.      * @throws XMPPErrorException
  457.      * @throws NoResponseException
  458.      * @throws NotConnectedException
  459.      * @throws InterruptedException
  460.      */
  461.     public void createPrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  462.         updatePrivacyList(listName, privacyItems);
  463.     }

  464.     /**
  465.      * The client has edited an existing list. It updates the server content with the resulting
  466.      * list of privacy items. The {@link PrivacyItem} list MUST contain all elements in the
  467.      * list (not the "delta").
  468.      *
  469.      * @param listName the list that has changed its content.
  470.      * @param privacyItems a List with every privacy item in the list.
  471.      * @throws XMPPErrorException
  472.      * @throws NoResponseException
  473.      * @throws NotConnectedException
  474.      * @throws InterruptedException
  475.      */
  476.     public void updatePrivacyList(String listName, List<PrivacyItem> privacyItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException  {
  477.         // Build the privacy package to add or update the new list
  478.         Privacy request = new Privacy();
  479.         request.setPrivacyList(listName, privacyItems);

  480.         // Send the package to the server
  481.         setRequest(request);
  482.     }

  483.     /**
  484.      * Remove a privacy list.
  485.      *
  486.      * @param listName the list that has changed its content.
  487.      * @throws XMPPErrorException
  488.      * @throws NoResponseException
  489.      * @throws NotConnectedException
  490.      * @throws InterruptedException
  491.      */
  492.     public void deletePrivacyList(String listName) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
  493.         // The request of the list is an privacy message with an empty list
  494.         Privacy request = new Privacy();
  495.         request.setPrivacyList(listName, new ArrayList<PrivacyItem>());

  496.         // Send the package to the server
  497.         setRequest(request);
  498.     }

  499.     /**
  500.      * Adds a privacy list listener that will be notified of any new update in the user
  501.      * privacy communication.
  502.      *
  503.      * @param listener a privacy list listener.
  504.      * @return true, if the listener was not already added.
  505.      */
  506.     public boolean addListener(PrivacyListListener listener) {
  507.         return listeners.add(listener);
  508.     }

  509.     /**
  510.      * Removes the privacy list listener.
  511.      *
  512.      * @param listener
  513.      * @return true, if the listener was removed.
  514.      */
  515.     public boolean removeListener(PrivacyListListener listener) {
  516.         return listeners.remove(listener);
  517.     }

  518.     /**
  519.      * Check if the user's server supports privacy lists.
  520.      *
  521.      * @return true, if the server supports privacy lists, false otherwise.
  522.      * @throws XMPPErrorException
  523.      * @throws NoResponseException
  524.      * @throws NotConnectedException
  525.      * @throws InterruptedException
  526.      */
  527.     public boolean isSupported() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException{
  528.         return ServiceDiscoveryManager.getInstanceFor(connection()).serverSupportsFeature(NAMESPACE);
  529.     }
  530. }