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