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