001/**
002 *
003 * Copyright 2009 Robin Collier.
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;
022import java.util.concurrent.ConcurrentHashMap;
023
024import org.jivesoftware.smack.StanzaListener;
025import org.jivesoftware.smack.SmackException.NoResponseException;
026import org.jivesoftware.smack.SmackException.NotConnectedException;
027import org.jivesoftware.smack.XMPPConnection;
028import org.jivesoftware.smack.XMPPException.XMPPErrorException;
029import org.jivesoftware.smack.filter.OrFilter;
030import org.jivesoftware.smack.filter.StanzaFilter;
031import org.jivesoftware.smack.packet.Message;
032import org.jivesoftware.smack.packet.Stanza;
033import org.jivesoftware.smack.packet.ExtensionElement;
034import org.jivesoftware.smack.packet.IQ.Type;
035import org.jivesoftware.smackx.delay.DelayInformationManager;
036import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
037import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener;
038import org.jivesoftware.smackx.pubsub.listener.ItemEventListener;
039import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener;
040import org.jivesoftware.smackx.pubsub.packet.PubSub;
041import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace;
042import org.jivesoftware.smackx.pubsub.util.NodeUtils;
043import org.jivesoftware.smackx.shim.packet.Header;
044import org.jivesoftware.smackx.shim.packet.HeadersExtension;
045import org.jivesoftware.smackx.xdata.Form;
046
047abstract public class Node
048{
049        protected XMPPConnection con;
050        protected String id;
051        protected String to;
052        
053        protected ConcurrentHashMap<ItemEventListener<Item>, StanzaListener> itemEventToListenerMap = new ConcurrentHashMap<ItemEventListener<Item>, StanzaListener>();
054        protected ConcurrentHashMap<ItemDeleteListener, StanzaListener> itemDeleteToListenerMap = new ConcurrentHashMap<ItemDeleteListener, StanzaListener>();
055        protected ConcurrentHashMap<NodeConfigListener, StanzaListener> configEventToListenerMap = new ConcurrentHashMap<NodeConfigListener, StanzaListener>();
056        
057        /**
058         * Construct a node associated to the supplied connection with the specified 
059         * node id.
060         * 
061         * @param connection The connection the node is associated with
062         * @param nodeName The node id
063         */
064        Node(XMPPConnection connection, String nodeName)
065        {
066                con = connection;
067                id = nodeName;
068        }
069
070        /**
071         * Some XMPP servers may require a specific service to be addressed on the 
072         * server.
073         * 
074         *   For example, OpenFire requires the server to be prefixed by <b>pubsub</b>
075         */
076        void setTo(String toAddress)
077        {
078                to = toAddress;
079        }
080
081        /**
082         * Get the NodeId
083         * 
084         * @return the node id
085         */
086        public String getId() 
087        {
088                return id;
089        }
090        /**
091         * Returns a configuration form, from which you can create an answer form to be submitted
092         * via the {@link #sendConfigurationForm(Form)}.
093         * 
094         * @return the configuration form
095         * @throws XMPPErrorException 
096         * @throws NoResponseException 
097         * @throws NotConnectedException 
098         */
099        public ConfigureForm getNodeConfiguration() throws NoResponseException, XMPPErrorException, NotConnectedException
100        {
101        PubSub pubSub = createPubsubPacket(Type.get, new NodeExtension(
102                        PubSubElementType.CONFIGURE_OWNER, getId()), PubSubNamespace.OWNER);
103                Stanza reply = sendPubsubPacket(pubSub);
104                return NodeUtils.getFormFromPacket(reply, PubSubElementType.CONFIGURE_OWNER);
105        }
106        
107        /**
108         * Update the configuration with the contents of the new {@link Form}
109         * 
110         * @param submitForm
111         * @throws XMPPErrorException 
112         * @throws NoResponseException 
113         * @throws NotConnectedException 
114         */
115        public void sendConfigurationForm(Form submitForm) throws NoResponseException, XMPPErrorException, NotConnectedException
116        {
117        PubSub packet = createPubsubPacket(Type.set, new FormNode(FormNodeType.CONFIGURE_OWNER,
118                        getId(), submitForm), PubSubNamespace.OWNER);
119                con.createPacketCollectorAndSend(packet).nextResultOrThrow();
120        }
121        
122        /**
123         * Discover node information in standard {@link DiscoverInfo} format.
124         * 
125         * @return The discovery information about the node.
126         * @throws XMPPErrorException 
127         * @throws NoResponseException if there was no response from the server.
128         * @throws NotConnectedException 
129         */
130        public DiscoverInfo discoverInfo() throws NoResponseException, XMPPErrorException, NotConnectedException
131        {
132                DiscoverInfo info = new DiscoverInfo();
133                info.setTo(to);
134                info.setNode(getId());
135                return (DiscoverInfo) con.createPacketCollectorAndSend(info).nextResultOrThrow();
136        }
137        
138        /**
139         * Get the subscriptions currently associated with this node.
140         * 
141         * @return List of {@link Subscription}
142         * @throws XMPPErrorException 
143         * @throws NoResponseException 
144         * @throws NotConnectedException 
145         * 
146         */
147        public List<Subscription> getSubscriptions() throws NoResponseException, XMPPErrorException, NotConnectedException
148        {
149        return getSubscriptions(null, null);
150        }
151
152    /**
153     * Get the subscriptions currently associated with this node.
154     * <p>
155     * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
156     * {@code returnedExtensions} will be filled with the stanza(/packet) extensions found in the answer.
157     * </p>
158     *
159     * @param additionalExtensions
160     * @param returnedExtensions a collection that will be filled with the returned packet
161     *        extensions
162     * @return List of {@link Subscription}
163     * @throws NoResponseException
164     * @throws XMPPErrorException
165     * @throws NotConnectedException
166     */
167    public List<Subscription> getSubscriptions(List<ExtensionElement> additionalExtensions, Collection<ExtensionElement> returnedExtensions)
168                    throws NoResponseException, XMPPErrorException, NotConnectedException {
169        return getSubscriptions(additionalExtensions, returnedExtensions, null);
170    }
171
172    /**
173     * Get the subscriptions currently associated with this node as owner.
174     *
175     * @return List of {@link Subscription}
176     * @throws XMPPErrorException
177     * @throws NoResponseException
178     * @throws NotConnectedException
179     * @see #getSubscriptionsAsOwner(List, Collection)
180     * @since 4.1
181     */
182    public List<Subscription> getSubscriptionsAsOwner() throws NoResponseException, XMPPErrorException,
183                    NotConnectedException {
184        return getSubscriptionsAsOwner(null, null);
185    }
186
187    /**
188     * Get the subscriptions currently associated with this node as owner.
189     * <p>
190     * Unlike {@link #getSubscriptions(List, Collection)}, which only retrieves the subscriptions of the current entity
191     * ("user"), this method returns a list of <b>all</b> subscriptions. This requires the entity to have the sufficient
192     * privileges to manage subscriptions.
193     * </p>
194     * <p>
195     * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
196     * {@code returnedExtensions} will be filled with the stanza(/packet) extensions found in the answer.
197     * </p>
198     *
199     * @param additionalExtensions
200     * @param returnedExtensions a collection that will be filled with the returned stanza(/packet) extensions
201     * @return List of {@link Subscription}
202     * @throws NoResponseException
203     * @throws XMPPErrorException
204     * @throws NotConnectedException
205     * @see <a href="http://www.xmpp.org/extensions/xep-0060.html#owner-subscriptions-retrieve">XEP-60 ยง 8.8.1 -
206     *      Retrieve Subscriptions List</a>
207     * @since 4.1
208     */
209    public List<Subscription> getSubscriptionsAsOwner(List<ExtensionElement> additionalExtensions,
210                    Collection<ExtensionElement> returnedExtensions) throws NoResponseException, XMPPErrorException,
211                    NotConnectedException {
212        return getSubscriptions(additionalExtensions, returnedExtensions, PubSubNamespace.OWNER);
213    }
214
215    private List<Subscription> getSubscriptions(List<ExtensionElement> additionalExtensions,
216                    Collection<ExtensionElement> returnedExtensions, PubSubNamespace pubSubNamespace)
217                    throws NoResponseException, XMPPErrorException, NotConnectedException {
218        PubSub pubSub = createPubsubPacket(Type.get, new NodeExtension(PubSubElementType.SUBSCRIPTIONS, getId()), pubSubNamespace);
219        if (additionalExtensions != null) {
220            for (ExtensionElement pe : additionalExtensions) {
221                pubSub.addExtension(pe);
222            }
223        }
224        PubSub reply = sendPubsubPacket(pubSub);
225        if (returnedExtensions != null) {
226            returnedExtensions.addAll(reply.getExtensions());
227        }
228        SubscriptionsExtension subElem = (SubscriptionsExtension) reply.getExtension(PubSubElementType.SUBSCRIPTIONS);
229        return subElem.getSubscriptions();
230    }
231
232        /**
233         * Get the affiliations of this node.
234         *
235         * @return List of {@link Affiliation}
236         * @throws NoResponseException
237         * @throws XMPPErrorException
238         * @throws NotConnectedException
239         */
240    public List<Affiliation> getAffiliations() throws NoResponseException, XMPPErrorException,
241                    NotConnectedException {
242        return getAffiliations(null, null);
243    }
244
245    /**
246     * Get the affiliations of this node.
247     * <p>
248     * {@code additionalExtensions} can be used e.g. to add a "Result Set Management" extension.
249     * {@code returnedExtensions} will be filled with the stanza(/packet) extensions found in the answer.
250     * </p>
251     *
252     * @param additionalExtensions additional {@code PacketExtensions} add to the request
253     * @param returnedExtensions a collection that will be filled with the returned packet
254     *        extensions
255     * @return List of {@link Affiliation}
256     * @throws NoResponseException
257     * @throws XMPPErrorException
258     * @throws NotConnectedException
259     */
260    public List<Affiliation> getAffiliations(List<ExtensionElement> additionalExtensions, Collection<ExtensionElement> returnedExtensions)
261                    throws NoResponseException, XMPPErrorException, NotConnectedException {
262        PubSub pubSub = createPubsubPacket(Type.get, new NodeExtension(PubSubElementType.AFFILIATIONS, getId()));
263        if (additionalExtensions != null) {
264            for (ExtensionElement pe : additionalExtensions) {
265                pubSub.addExtension(pe);
266            }
267        }
268        PubSub reply = sendPubsubPacket(pubSub);
269        if (returnedExtensions != null) {
270            returnedExtensions.addAll(reply.getExtensions());
271        }
272        AffiliationsExtension affilElem = (AffiliationsExtension) reply.getExtension(PubSubElementType.AFFILIATIONS);
273        return affilElem.getAffiliations();
274    }
275
276        /**
277         * The user subscribes to the node using the supplied jid.  The
278         * bare jid portion of this one must match the jid for the connection.
279         * 
280         * Please note that the {@link Subscription.State} should be checked 
281         * on return since more actions may be required by the caller.
282         * {@link Subscription.State#pending} - The owner must approve the subscription 
283         * request before messages will be received.
284         * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true, 
285         * the caller must configure the subscription before messages will be received.  If it is false
286         * the caller can configure it but is not required to do so.
287         * @param jid The jid to subscribe as.
288         * @return The subscription
289         * @throws XMPPErrorException 
290         * @throws NoResponseException 
291         * @throws NotConnectedException 
292         */
293        public Subscription subscribe(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException
294        {
295            PubSub pubSub = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId()));
296                PubSub reply = sendPubsubPacket(pubSub);
297                return reply.getExtension(PubSubElementType.SUBSCRIPTION);
298        }
299        
300        /**
301         * The user subscribes to the node using the supplied jid and subscription
302         * options.  The bare jid portion of this one must match the jid for the 
303         * connection.
304         * 
305         * Please note that the {@link Subscription.State} should be checked 
306         * on return since more actions may be required by the caller.
307         * {@link Subscription.State#pending} - The owner must approve the subscription 
308         * request before messages will be received.
309         * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true, 
310         * the caller must configure the subscription before messages will be received.  If it is false
311         * the caller can configure it but is not required to do so.
312         * @param jid The jid to subscribe as.
313         * @return The subscription
314         * @throws XMPPErrorException 
315         * @throws NoResponseException 
316         * @throws NotConnectedException 
317         */
318        public Subscription subscribe(String jid, SubscribeForm subForm) throws NoResponseException, XMPPErrorException, NotConnectedException
319        {
320            PubSub request = createPubsubPacket(Type.set, new SubscribeExtension(jid, getId()));
321                request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm));
322                PubSub reply = PubSubManager.sendPubsubPacket(con, request);
323                return reply.getExtension(PubSubElementType.SUBSCRIPTION);
324        }
325
326        /**
327         * Remove the subscription related to the specified JID.  This will only 
328         * work if there is only 1 subscription.  If there are multiple subscriptions,
329         * use {@link #unsubscribe(String, String)}.
330         * 
331         * @param jid The JID used to subscribe to the node
332         * @throws XMPPErrorException 
333         * @throws NoResponseException 
334         * @throws NotConnectedException 
335         * 
336         */
337        public void unsubscribe(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException
338        {
339                unsubscribe(jid, null);
340        }
341        
342        /**
343         * Remove the specific subscription related to the specified JID.
344         * 
345         * @param jid The JID used to subscribe to the node
346         * @param subscriptionId The id of the subscription being removed
347         * @throws XMPPErrorException 
348         * @throws NoResponseException 
349         * @throws NotConnectedException 
350         */
351        public void unsubscribe(String jid, String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException
352        {
353                sendPubsubPacket(createPubsubPacket(Type.set, new UnsubscribeExtension(jid, getId(), subscriptionId)));
354        }
355
356        /**
357         * Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted
358         * via the {@link #sendConfigurationForm(Form)}.
359         * 
360         * @return A subscription options form
361         * @throws XMPPErrorException 
362         * @throws NoResponseException 
363         * @throws NotConnectedException 
364         */
365        public SubscribeForm getSubscriptionOptions(String jid) throws NoResponseException, XMPPErrorException, NotConnectedException
366        {
367                return getSubscriptionOptions(jid, null);
368        }
369
370
371        /**
372         * Get the options for configuring the specified subscription.
373         * 
374         * @param jid JID the subscription is registered under
375         * @param subscriptionId The subscription id
376         * 
377         * @return The subscription option form
378         * @throws XMPPErrorException 
379         * @throws NoResponseException 
380         * @throws NotConnectedException 
381         * 
382         */
383        public SubscribeForm getSubscriptionOptions(String jid, String subscriptionId) throws NoResponseException, XMPPErrorException, NotConnectedException
384        {
385                PubSub packet = sendPubsubPacket(createPubsubPacket(Type.get, new OptionsExtension(jid, getId(), subscriptionId)));
386                FormNode ext = packet.getExtension(PubSubElementType.OPTIONS);
387                return new SubscribeForm(ext.getForm());
388        }
389
390        /**
391         * Register a listener for item publication events.  This 
392         * listener will get called whenever an item is published to 
393         * this node.
394         * 
395         * @param listener The handler for the event
396         */
397        @SuppressWarnings("unchecked")
398    public void addItemEventListener(@SuppressWarnings("rawtypes") ItemEventListener listener)
399        {
400                StanzaListener conListener = new ItemEventTranslator(listener); 
401                itemEventToListenerMap.put(listener, conListener);
402                con.addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item"));
403        }
404
405        /**
406         * Unregister a listener for publication events.
407         * 
408         * @param listener The handler to unregister
409         */
410        public void removeItemEventListener(@SuppressWarnings("rawtypes") ItemEventListener listener)
411        {
412                StanzaListener conListener = itemEventToListenerMap.remove(listener);
413                
414                if (conListener != null)
415                        con.removeSyncStanzaListener(conListener);
416        }
417
418        /**
419         * Register a listener for configuration events.  This listener
420         * will get called whenever the node's configuration changes.
421         * 
422         * @param listener The handler for the event
423         */
424        public void addConfigurationListener(NodeConfigListener listener)
425        {
426                StanzaListener conListener = new NodeConfigTranslator(listener); 
427                configEventToListenerMap.put(listener, conListener);
428                con.addSyncStanzaListener(conListener, new EventContentFilter(EventElementType.configuration.toString()));
429        }
430
431        /**
432         * Unregister a listener for configuration events.
433         * 
434         * @param listener The handler to unregister
435         */
436        public void removeConfigurationListener(NodeConfigListener listener)
437        {
438                StanzaListener conListener = configEventToListenerMap .remove(listener);
439                
440                if (conListener != null)
441                        con.removeSyncStanzaListener(conListener);
442        }
443        
444        /**
445         * Register an listener for item delete events.  This listener
446         * gets called whenever an item is deleted from the node.
447         * 
448         * @param listener The handler for the event
449         */
450        public void addItemDeleteListener(ItemDeleteListener listener)
451        {
452                StanzaListener delListener = new ItemDeleteTranslator(listener); 
453                itemDeleteToListenerMap.put(listener, delListener);
454                EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract");
455                EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString());
456                
457                con.addSyncStanzaListener(delListener, new OrFilter(deleteItem, purge));
458        }
459
460        /**
461         * Unregister a listener for item delete events.
462         * 
463         * @param listener The handler to unregister
464         */
465        public void removeItemDeleteListener(ItemDeleteListener listener)
466        {
467                StanzaListener conListener = itemDeleteToListenerMap .remove(listener);
468                
469                if (conListener != null)
470                        con.removeSyncStanzaListener(conListener);
471        }
472
473        @Override
474        public String toString()
475        {
476                return super.toString() + " " + getClass().getName() + " id: " + id;
477        }
478        
479        protected PubSub createPubsubPacket(Type type, ExtensionElement ext)
480        {
481                return createPubsubPacket(type, ext, null);
482        }
483        
484        protected PubSub createPubsubPacket(Type type, ExtensionElement ext, PubSubNamespace ns)
485        {
486                return PubSub.createPubsubPacket(to, type, ext, ns);
487        }
488
489        protected PubSub sendPubsubPacket(PubSub packet) throws NoResponseException, XMPPErrorException, NotConnectedException
490        {
491                return PubSubManager.sendPubsubPacket(con, packet);
492        }
493
494
495        private static List<String> getSubscriptionIds(Stanza packet)
496        {
497                HeadersExtension headers = (HeadersExtension)packet.getExtension("headers", "http://jabber.org/protocol/shim");
498                List<String> values = null;
499                
500                if (headers != null)
501                {
502                        values = new ArrayList<String>(headers.getHeaders().size());
503                        
504                        for (Header header : headers.getHeaders())
505                        {
506                                values.add(header.getValue());
507                        }
508                }
509                return values;
510        }
511
512        /**
513         * This class translates low level item publication events into api level objects for 
514         * user consumption.
515         * 
516         * @author Robin Collier
517         */
518        public class ItemEventTranslator implements StanzaListener
519        {
520                @SuppressWarnings("rawtypes")
521        private ItemEventListener listener;
522
523                public ItemEventTranslator(@SuppressWarnings("rawtypes") ItemEventListener eventListener)
524                {
525                        listener = eventListener;
526                }
527                
528                @SuppressWarnings({ "rawtypes", "unchecked" })
529        public void processPacket(Stanza packet)
530                {
531                EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
532                        ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
533            ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), (List<Item>)itemsElem.getItems(), getSubscriptionIds(packet), DelayInformationManager.getDelayTimestamp(packet));
534                        listener.handlePublishedItems(eventItems);
535                }
536        }
537
538        /**
539         * This class translates low level item deletion events into api level objects for 
540         * user consumption.
541         * 
542         * @author Robin Collier
543         */
544        public class ItemDeleteTranslator implements StanzaListener
545        {
546                private ItemDeleteListener listener;
547
548                public ItemDeleteTranslator(ItemDeleteListener eventListener)
549                {
550                        listener = eventListener;
551                }
552                
553                public void processPacket(Stanza packet)
554                {
555                EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
556                
557                List<ExtensionElement> extList = event.getExtensions();
558                
559                if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName()))
560                {
561                        listener.handlePurge();
562                }
563                else
564                {
565                                ItemsExtension itemsElem = (ItemsExtension)event.getEvent();
566                                @SuppressWarnings("unchecked")
567                Collection<RetractItem> pubItems = (Collection<RetractItem>) itemsElem.getItems();
568                                List<String> items = new ArrayList<String>(pubItems.size());
569
570                                for (RetractItem item : pubItems)
571                                {
572                                        items.add(item.getId());
573                                }
574
575                                ItemDeleteEvent eventItems = new ItemDeleteEvent(itemsElem.getNode(), items, getSubscriptionIds(packet));
576                                listener.handleDeletedItems(eventItems);
577                }
578                }
579        }
580        
581        /**
582         * This class translates low level node configuration events into api level objects for 
583         * user consumption.
584         * 
585         * @author Robin Collier
586         */
587        public class NodeConfigTranslator implements StanzaListener
588        {
589                private NodeConfigListener listener;
590
591                public NodeConfigTranslator(NodeConfigListener eventListener)
592                {
593                        listener = eventListener;
594                }
595                
596                public void processPacket(Stanza packet)
597                {
598                EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
599                        ConfigurationEvent config = (ConfigurationEvent)event.getEvent();
600
601                        listener.handleNodeConfiguration(config);
602                }
603        }
604
605        /**
606         * Filter for {@link StanzaListener} to filter out events not specific to the 
607         * event type expected for this node.
608         * 
609         * @author Robin Collier
610         */
611        class EventContentFilter implements StanzaFilter
612        {
613                private String firstElement;
614                private String secondElement;
615                
616                EventContentFilter(String elementName)
617                {
618                        firstElement = elementName;
619                }
620
621                EventContentFilter(String firstLevelEelement, String secondLevelElement)
622                {
623                        firstElement = firstLevelEelement;
624                        secondElement = secondLevelElement;
625                }
626
627                public boolean accept(Stanza packet)
628                {
629                        if (!(packet instanceof Message))
630                                return false;
631
632                        EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns());
633                        
634                        if (event == null)
635                                return false;
636
637                        NodeExtension embedEvent = event.getEvent();
638                        
639                        if (embedEvent == null)
640                                return false;
641                        
642                        if (embedEvent.getElementName().equals(firstElement))
643                        {
644                                if (!embedEvent.getNode().equals(getId()))
645                                        return false;
646                                
647                                if (secondElement == null)
648                                        return true;
649                                
650                                if (embedEvent instanceof EmbeddedPacketExtension)
651                                {
652                                        List<ExtensionElement> secondLevelList = ((EmbeddedPacketExtension)embedEvent).getExtensions();
653                                        
654                                        if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement))
655                                                return true;
656                                }
657                        }
658                        return false;
659                }
660        }
661}