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    LeafNode(PubSubManager pubSubManager, String nodeId) {
042        super(pubSubManager, nodeId);
043    }
044
045    /**
046     * Get information on the items in the node in standard
047     * {@link DiscoverItems} format.
048     *
049     * @return The item details in {@link DiscoverItems} format
050     * @throws XMPPErrorException
051     * @throws NoResponseException if there was no response from the server.
052     * @throws NotConnectedException
053     * @throws InterruptedException
054     */
055    public DiscoverItems discoverItems() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
056        DiscoverItems items = new DiscoverItems();
057        items.setTo(pubSubManager.getServiceJid());
058        items.setNode(getId());
059        return pubSubManager.getConnection().createStanzaCollectorAndSend(items).nextResultOrThrow();
060    }
061
062    /**
063     * Get the current items stored in the node.
064     *
065     * @param <T> type of the items.
066     * @return List of {@link Item} in the node
067     * @throws XMPPErrorException
068     * @throws NoResponseException if there was no response from the server.
069     * @throws NotConnectedException
070     * @throws InterruptedException
071     */
072    public <T extends Item> List<T> getItems() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
073        return getItems((List<ExtensionElement>) null, null);
074    }
075
076    /**
077     * Get the current items stored in the node based
078     * on the subscription associated with the provided
079     * subscription id.
080     *
081     * @param subscriptionId -  The subscription id for the
082     * associated subscription.
083     * @param <T> type of the items.
084     *
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        PubSub request = createPubsubPacket(Type.get, new GetItemsRequest(getId(), subscriptionId));
093        return getItems(request);
094    }
095
096    /**
097     * Get the items specified from the node.  This would typically be
098     * used when the server does not return the payload due to size
099     * constraints.  The user would be required to retrieve the payload
100     * after the items have been retrieved via {@link #getItems()} or an
101     * event, that did not include the payload.
102     *
103     * @param ids Item ids of the items to retrieve
104     * @param <T> type of the items.
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        List<Item> itemList = new ArrayList<>(ids.size());
114
115        for (String id : ids) {
116            itemList.add(new Item(id));
117        }
118        PubSub request = createPubsubPacket(Type.get, new ItemsExtension(ItemsExtension.ItemsElementType.items, getId(), itemList));
119        return getItems(request);
120    }
121
122    /**
123     * Get items persisted on the node, limited to the specified number.
124     *
125     * @param maxItems Maximum number of items to return
126     * @param <T> type of the items.
127     *
128     * @return List of {@link Item}
129     * @throws XMPPErrorException
130     * @throws NoResponseException if there was no response from the server.
131     * @throws NotConnectedException
132     * @throws InterruptedException
133     */
134    public <T extends Item> List<T> getItems(int maxItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
135        PubSub request = createPubsubPacket(Type.get, new GetItemsRequest(getId(), maxItems));
136        return getItems(request);
137    }
138
139    /**
140     * Get items persisted on the node, limited to the specified number
141     * based on the subscription associated with the provided subscriptionId.
142     *
143     * @param maxItems Maximum number of items to return
144     * @param subscriptionId The subscription which the retrieval is based
145     * on.
146     *
147     * @return List of {@link Item}
148     * @param <T> type of the items.
149     *
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        PubSub request = createPubsubPacket(Type.get, new GetItemsRequest(getId(), subscriptionId, maxItems));
157        return getItems(request);
158    }
159
160    /**
161     * Get items persisted on the node.
162     * <p>
163     * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
164     * {@code returnedExtensions} will be filled with the stanza extensions found in the answer.
165     * </p>
166     *
167     * @param additionalExtensions additional {@code PacketExtensions} to be added to the request.
168     *        This is an optional argument, if provided as null no extensions will be added.
169     * @param returnedExtensions a collection that will be filled with the returned packet
170     *        extensions. This is an optional argument, if provided as null it won't be populated.
171     * @param <T> type of the items.
172     *
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     * @throws NotConnectedException
212     * @throws InterruptedException
213     * @throws XMPPErrorException
214     * @throws NoResponseException
215     * @deprecated use {@link #publish()} instead.
216     */
217    @Deprecated
218    public void send() throws NotConnectedException, InterruptedException, NoResponseException, XMPPErrorException {
219        publish();
220    }
221
222    /**
223     * Publishes an event to the node.  This is a simple item
224     * with no payload.
225     *
226     * If the id is null, an empty item (one without an id) will be sent.
227     * Please note that this is not the same as {@link #send()}, which
228     * publishes an event with NO item.
229     *
230     * @param item - The item being sent
231     * @param <T> type of the items.
232     *
233     * @throws NotConnectedException
234     * @throws InterruptedException
235     * @throws XMPPErrorException
236     * @throws NoResponseException
237     * @deprecated use {@link #publish(Item)} instead.
238     */
239    @SuppressWarnings("unchecked")
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
255     * @throws InterruptedException
256     * @throws XMPPErrorException
257     * @throws NoResponseException
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
273     * @throws NoResponseException
274     * @throws NotConnectedException
275     * @throws InterruptedException
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
300     * @throws NoResponseException
301     * @throws NotConnectedException
302     * @throws InterruptedException
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
322     * @throws NoResponseException
323     * @throws NotConnectedException
324     * @throws InterruptedException
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
339     * @throws NoResponseException if there was no response from the server.
340     * @throws NotConnectedException
341     * @throws InterruptedException
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
354     * @throws NoResponseException
355     * @throws NotConnectedException
356     * @throws InterruptedException
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
369     * @throws NoResponseException if there was no response from the server.
370     * @throws NotConnectedException
371     * @throws InterruptedException
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}