001/**
002 *
003 * Copyright the original author or authors
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.smackx.pubsub;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.List;
022
023import org.jivesoftware.smack.SmackException.NoResponseException;
024import org.jivesoftware.smack.SmackException.NotConnectedException;
025import org.jivesoftware.smack.XMPPException.XMPPErrorException;
026import org.jivesoftware.smack.packet.ExtensionElement;
027import org.jivesoftware.smack.packet.IQ.Type;
028
029import org.jivesoftware.smackx.disco.packet.DiscoverItems;
030import org.jivesoftware.smackx.pubsub.form.ConfigureForm;
031import org.jivesoftware.smackx.pubsub.packet.PubSub;
032
033/**
034 * The main class for the majority of PubSub functionality. In general
035 * almost all PubSub capabilities are related to the concept of a node.
036 * All items are published to a node, and typically subscribed to by other
037 * users.  These users then retrieve events based on this subscription.
038 *
039 * @author Robin Collier
040 */
041public class LeafNode extends Node {
042    LeafNode(PubSubManager pubSubManager, String nodeId) {
043        super(pubSubManager, nodeId);
044    }
045
046    /**
047     * Get information on the items in the node in standard
048     * {@link DiscoverItems} format.
049     *
050     * @return The item details in {@link DiscoverItems} format
051     * @throws XMPPErrorException if there was an XMPP error returned.
052     * @throws NoResponseException if there was no response from the server.
053     * @throws NotConnectedException if the XMPP connection is not connected.
054     * @throws InterruptedException if the calling thread was interrupted.
055     */
056    public DiscoverItems discoverItems() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
057        DiscoverItems items = new DiscoverItems();
058        items.setTo(pubSubManager.getServiceJid());
059        items.setNode(getId());
060        return pubSubManager.getConnection().createStanzaCollectorAndSend(items).nextResultOrThrow();
061    }
062
063    /**
064     * Get the current items stored in the node.
065     *
066     * @param <T> type of the items.
067     * @return List of {@link Item} in the node
068     * @throws XMPPErrorException if there was an XMPP error returned.
069     * @throws NoResponseException if there was no response from the server.
070     * @throws NotConnectedException if the XMPP connection is not connected.
071     * @throws InterruptedException if the calling thread was interrupted.
072     */
073    public <T extends Item> List<T> getItems() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
074        return getItems((List<ExtensionElement>) null, null);
075    }
076
077    /**
078     * Get the current items stored in the node based
079     * on the subscription associated with the provided
080     * subscription id.
081     *
082     * @param subscriptionId -  The subscription id for the
083     * associated subscription.
084     * @param <T> type of the items.
085     *
086     * @return List of {@link Item} in the node
087     * @throws XMPPErrorException if there was an XMPP error returned.
088     * @throws NoResponseException if there was no response from the server.
089     * @throws NotConnectedException if the XMPP connection is not connected.
090     * @throws InterruptedException if the calling thread was interrupted.
091     */
092    public <T extends Item> List<T> getItems(String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
093        PubSub request = createPubsubPacket(Type.get, new GetItemsRequest(getId(), subscriptionId));
094        return getItems(request);
095    }
096
097    /**
098     * Get the items specified from the node.  This would typically be
099     * used when the server does not return the payload due to size
100     * constraints.  The user would be required to retrieve the payload
101     * after the items have been retrieved via {@link #getItems()} or an
102     * event, that did not include the payload.
103     *
104     * @param ids Item ids of the items to retrieve
105     * @param <T> type of the items.
106     *
107     * @return The list of {@link Item} with payload
108     * @throws XMPPErrorException if there was an XMPP error returned.
109     * @throws NoResponseException if there was no response from the server.
110     * @throws NotConnectedException if the XMPP connection is not connected.
111     * @throws InterruptedException if the calling thread was interrupted.
112     */
113    public <T extends Item> List<T> getItems(Collection<String> ids) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
114        List<Item> itemList = new ArrayList<>(ids.size());
115
116        for (String id : ids) {
117            itemList.add(new Item(id));
118        }
119        PubSub request = createPubsubPacket(Type.get, new ItemsExtension(ItemsExtension.ItemsElementType.items, getId(), itemList));
120        return getItems(request);
121    }
122
123    /**
124     * Get items persisted on the node, limited to the specified number.
125     *
126     * @param maxItems Maximum number of items to return
127     * @param <T> type of the items.
128     *
129     * @return List of {@link Item}
130     * @throws XMPPErrorException if there was an XMPP error returned.
131     * @throws NoResponseException if there was no response from the server.
132     * @throws NotConnectedException if the XMPP connection is not connected.
133     * @throws InterruptedException if the calling thread was interrupted.
134     */
135    public <T extends Item> List<T> getItems(int maxItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
136        PubSub request = createPubsubPacket(Type.get, new GetItemsRequest(getId(), maxItems));
137        return getItems(request);
138    }
139
140    /**
141     * Get items persisted on the node, limited to the specified number
142     * based on the subscription associated with the provided subscriptionId.
143     *
144     * @param maxItems Maximum number of items to return
145     * @param subscriptionId The subscription which the retrieval is based
146     * on.
147     *
148     * @return List of {@link Item}
149     * @param <T> type of the items.
150     *
151     * @throws XMPPErrorException if there was an XMPP error returned.
152     * @throws NoResponseException if there was no response from the server.
153     * @throws NotConnectedException if the XMPP connection is not connected.
154     * @throws InterruptedException if the calling thread was interrupted.
155     */
156    public <T extends Item> List<T> getItems(int maxItems, String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
157        PubSub request = createPubsubPacket(Type.get, new GetItemsRequest(getId(), subscriptionId, maxItems));
158        return getItems(request);
159    }
160
161    /**
162     * Get items persisted on the node.
163     * <p>
164     * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
165     * {@code returnedExtensions} will be filled with the stanza extensions found in the answer.
166     * </p>
167     *
168     * @param additionalExtensions additional {@code PacketExtensions} to be added to the request.
169     *        This is an optional argument, if provided as null no extensions will be added.
170     * @param returnedExtensions a collection that will be filled with the returned packet
171     *        extensions. This is an optional argument, if provided as null it won't be populated.
172     * @param <T> type of the items.
173     *
174     * @return List of {@link Item}
175     * @throws NoResponseException if there was no response from the remote entity.
176     * @throws XMPPErrorException if there was an XMPP error returned.
177     * @throws NotConnectedException if the XMPP connection is not connected.
178     * @throws InterruptedException if the calling thread was interrupted.
179     */
180    public <T extends Item> List<T> getItems(List<ExtensionElement> additionalExtensions,
181                    List<ExtensionElement> returnedExtensions) throws NoResponseException,
182                    XMPPErrorException, NotConnectedException, InterruptedException {
183        PubSub request = createPubsubPacket(Type.get, new GetItemsRequest(getId()));
184        request.addExtensions(additionalExtensions);
185        return getItems(request, returnedExtensions);
186    }
187
188    private <T extends Item> List<T> getItems(PubSub request) throws NoResponseException,
189                    XMPPErrorException, NotConnectedException, InterruptedException {
190        return getItems(request, null);
191    }
192
193    @SuppressWarnings("unchecked")
194    private <T extends Item> List<T> getItems(PubSub request,
195                    List<ExtensionElement> returnedExtensions) throws NoResponseException,
196                    XMPPErrorException, NotConnectedException, InterruptedException {
197        PubSub result = pubSubManager.getConnection().createStanzaCollectorAndSend(request).nextResultOrThrow();
198        ItemsExtension itemsElem = result.getExtension(PubSubElementType.ITEMS);
199        if (returnedExtensions != null) {
200            returnedExtensions.addAll(result.getExtensions());
201        }
202        return (List<T>) itemsElem.getItems();
203    }
204
205    /**
206     * Publishes an event to the node.  This is an empty event
207     * with no item.
208     *
209     * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false
210     * and {@link ConfigureForm#isDeliverPayloads()}=false.
211     *
212     * @throws NotConnectedException if the XMPP connection is not connected.
213     * @throws InterruptedException if the calling thread was interrupted.
214     * @throws XMPPErrorException if there was an XMPP error returned.
215     * @throws NoResponseException if there was no response from the remote entity.
216     * @deprecated use {@link #publish()} instead.
217     */
218    @Deprecated
219    public void send() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
220        publish();
221    }
222
223    /**
224     * Publishes an event to the node.  This is a simple item
225     * with no payload.
226     *
227     * If the id is null, an empty item (one without an id) will be sent.
228     * Please note that this is not the same as {@link #send()}, which
229     * publishes an event with NO item.
230     *
231     * @param item - The item being sent
232     * @param <T> type of the items.
233     *
234     * @throws NotConnectedException if the XMPP connection is not connected.
235     * @throws InterruptedException if the calling thread was interrupted.
236     * @throws XMPPErrorException if there was an XMPP error returned.
237     * @throws NoResponseException if there was no response from the remote entity.
238     * @deprecated use {@link #publish(Item)} instead.
239     */
240    @Deprecated
241    public <T extends Item> void send(T item) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
242        publish(item);
243    }
244
245    /**
246     * Publishes multiple events to the node.  Same rules apply as in {@link #publish(Item)}.
247     *
248     * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input
249     * list will get stored on the node, assuming it stores the last sent item.
250     *
251     * @param items - The collection of items being sent
252     * @param <T> type of the items.
253     *
254     * @throws NotConnectedException if the XMPP connection is not connected.
255     * @throws InterruptedException if the calling thread was interrupted.
256     * @throws XMPPErrorException if there was an XMPP error returned.
257     * @throws NoResponseException if there was no response from the remote entity.
258     * @deprecated use {@link #publish(Collection)} instead.
259     */
260    @Deprecated
261    public <T extends Item> void send(Collection<T> items) throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
262        publish(items);
263    }
264
265    /**
266     * Publishes an event to the node.  This is an empty event
267     * with no item.
268     *
269     * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false
270     * and {@link ConfigureForm#isDeliverPayloads()}=false.
271     *
272     * @throws XMPPErrorException if there was an XMPP error returned.
273     * @throws NoResponseException if there was no response from the remote entity.
274     * @throws NotConnectedException if the XMPP connection is not connected.
275     * @throws InterruptedException if the calling thread was interrupted.
276     *
277     */
278    public void publish() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
279        PubSub packet = createPubsubPacket(Type.set, new NodeExtension(PubSubElementType.PUBLISH, getId()));
280
281        pubSubManager.getConnection().createStanzaCollectorAndSend(packet).nextResultOrThrow();
282    }
283
284    /**
285     * Publishes an event to the node.  This can be either a simple item
286     * with no payload, or one with it.  This is determined by the Node
287     * configuration.
288     *
289     * If the node has <b>deliver_payload=false</b>, the Item must not
290     * have a payload.
291     *
292     * If the id is null, an empty item (one without an id) will be sent.
293     * Please note that this is not the same as {@link #send()}, which
294     * publishes an event with NO item.
295     *
296     * @param item - The item being sent
297     * @param <T> type of the items.
298     *
299     * @throws XMPPErrorException if there was an XMPP error returned.
300     * @throws NoResponseException if there was no response from the remote entity.
301     * @throws NotConnectedException if the XMPP connection is not connected.
302     * @throws InterruptedException if the calling thread was interrupted.
303     *
304     */
305    @SuppressWarnings("unchecked")
306    public <T extends Item> void publish(T item) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
307        Collection<T> items = new ArrayList<>(1);
308        items.add(item == null ? (T) new Item() : item);
309        publish(items);
310    }
311
312    /**
313     * Publishes multiple events to the node.  Same rules apply as in {@link #send(Item)}.
314     *
315     * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input
316     * list will get stored on the node, assuming it stores the last sent item.
317     *
318     * @param items - The collection of {@link Item} objects being sent
319     * @param <T> type of the items.
320     *
321     * @throws XMPPErrorException if there was an XMPP error returned.
322     * @throws NoResponseException if there was no response from the remote entity.
323     * @throws NotConnectedException if the XMPP connection is not connected.
324     * @throws InterruptedException if the calling thread was interrupted.
325     *
326     */
327    public <T extends Item> void publish(Collection<T> items) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
328        PubSub packet = createPubsubPacket(Type.set, new PublishItem<>(getId(), items));
329
330        pubSubManager.getConnection().createStanzaCollectorAndSend(packet).nextResultOrThrow();
331    }
332
333    /**
334     * Purges the node of all items.
335     *
336     * <p>Note: Some implementations may keep the last item
337     * sent.
338     * @throws XMPPErrorException if there was an XMPP error returned.
339     * @throws NoResponseException if there was no response from the server.
340     * @throws NotConnectedException if the XMPP connection is not connected.
341     * @throws InterruptedException if the calling thread was interrupted.
342     */
343    public void deleteAllItems() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
344        PubSub request = createPubsubPacket(Type.set, new NodeExtension(PubSubElementType.PURGE_OWNER, getId()));
345
346        pubSubManager.getConnection().createStanzaCollectorAndSend(request).nextResultOrThrow();
347    }
348
349    /**
350     * Delete the item with the specified id from the node.
351     *
352     * @param itemId The id of the item
353     * @throws XMPPErrorException if there was an XMPP error returned.
354     * @throws NoResponseException if there was no response from the remote entity.
355     * @throws NotConnectedException if the XMPP connection is not connected.
356     * @throws InterruptedException if the calling thread was interrupted.
357     */
358    public void deleteItem(String itemId) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
359        Collection<String> items = new ArrayList<>(1);
360        items.add(itemId);
361        deleteItem(items);
362    }
363
364    /**
365     * Delete the items with the specified id's from the node.
366     *
367     * @param itemIds The list of id's of items to delete
368     * @throws XMPPErrorException if there was an XMPP error returned.
369     * @throws NoResponseException if there was no response from the server.
370     * @throws NotConnectedException if the XMPP connection is not connected.
371     * @throws InterruptedException if the calling thread was interrupted.
372     */
373    public void deleteItem(Collection<String> itemIds) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
374        List<Item> items = new ArrayList<>(itemIds.size());
375
376        for (String id : itemIds) {
377             items.add(new Item(id));
378        }
379        PubSub request = createPubsubPacket(Type.set, new ItemsExtension(ItemsExtension.ItemsElementType.retract, getId(), items));
380        pubSubManager.getConnection().createStanzaCollectorAndSend(request).nextResultOrThrow();
381    }
382}