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