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