diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index b2d84fbb3..05eb8e311 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -54,6 +54,7 @@ Smack Extensions and currently supported XEPs of smack-extensions | Software Version | [XEP-0092](https://xmpp.org/extensions/xep-0092.html) | n/a | Retrieve and announce the software application of an XMPP entity. | | Stream Initation | [XEP-0095](https://xmpp.org/extensions/xep-0095.html) | n/a | Initiating a data stream between any two XMPP entities. | | [SI File Transfer](filetransfer.md) | [XEP-0096](https://xmpp.org/extensions/xep-0096.html) | n/a | Transfer files between two users over XMPP. | +| User Mood | [XEP-0107](https://xmpp.org/extensions/xep-0107.html) | 1.2.1 | Communicate the users current mood. | | [Entity Capabilities](caps.md) | [XEP-0115](https://xmpp.org/extensions/xep-0115.html) | n/a | Broadcasting and dynamic discovery of entity capabilities. | | [Jingle](jingle.html) | [XEP-0116](https://xmpp.org/extensions/xep-0166.html) | n/a | Initiate and manage sessions between two XMPP entities. | | Data Forms Validation | [XEP-0122](https://xmpp.org/extensions/xep-0122.html) | n/a | Enables an application to specify additional validation guidelines . | diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/Mood.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/Mood.java new file mode 100644 index 000000000..207410cda --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/Mood.java @@ -0,0 +1,110 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood; + +/** + * Moods specified by XEP-0107: User Mood. + * + * @see + * XEP-0107 §3: Mood Values + */ +public enum Mood { + afraid, + amazed, + amorous, + angry, + annoyed, + anxious, + aroused, + ashamed, + bored, + brave, + calm, + cautious, + cold, + confident, + confused, + contemplative, + contented, + cranky, + crazy, + creative, + curious, + dejected, + depressed, + disappointed, + disgusted, + dismayed, + distracted, + embarrassed, + envious, + excited, + flirtatious, + frustrated, + grateful, + grieving, + grumpy, + guilty, + happy, + hopeful, + hot, + humbled, + humiliated, + hungry, + hurt, + impressed, + in_awe, + in_love, + indignant, + interested, + intoxicated, + invincible, + jealous, + lonely, + lost, + lucky, + mean, + moody, + nervous, + neutral, + offended, + outraged, + playful, + proud, + relaxed, + relieved, + remorseful, + restless, + sad, + sarcastic, + satisfied, + serious, + shocked, + shy, + sick, + sleepy, + spontaneous, + stressed, + strong, + surprised, + thankful, + thirsty, + tired, + undefined, + weak, + worried +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodListener.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodListener.java new file mode 100644 index 000000000..2abe9af10 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodListener.java @@ -0,0 +1,27 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.mood.element.MoodElement; + +import org.jxmpp.jid.BareJid; + +public interface MoodListener { + + void onMoodUpdated(BareJid jid, Message message, MoodElement moodElement); +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodManager.java new file mode 100644 index 000000000..c96f4555f --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/MoodManager.java @@ -0,0 +1,179 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.AsyncButOrdered; +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.mood.element.MoodConcretisation; +import org.jivesoftware.smackx.mood.element.MoodElement; +import org.jivesoftware.smackx.mood.provider.MoodConcretisationProvider; +import org.jivesoftware.smackx.pep.PepListener; +import org.jivesoftware.smackx.pep.PepManager; +import org.jivesoftware.smackx.pubsub.EventElement; +import org.jivesoftware.smackx.pubsub.ItemsExtension; +import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.pubsub.PayloadItem; +import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubManager; + +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.EntityBareJid; + +/** + * Entry point for Smacks API for XEP-0107: User Mood. + * + * To set a mood, please use one of the {@link #setMood(Mood)} methods. This will publish the users mood to a pubsub + * node.
+ *
+ * In order to get updated about other users moods, register a {@link MoodListener} at + * {@link #addMoodListener(MoodListener)}. That listener will get updated about any incoming mood updates of contacts.
+ *
+ * To stop publishing the users mood, refer to {@link #clearMood()}.
+ *
+ * It is also possible to add {@link MoodElement}s to {@link Message}s by using {@link #addMoodToMessage(Message, Mood)}.
+ *
+ * The API can be extended with custom mood concretisations by extending {@link MoodConcretisation} and registering + * {@link MoodConcretisationProvider}s using {@link ProviderManager#addExtensionProvider(String, String, Object)}.
+ * An example of how this can be done can be found in the MoodConcretisationTest in the test package. + * + * @see + * XEP-0107: User Mood + */ +public final class MoodManager extends Manager { + + public static final String MOOD_NODE = "http://jabber.org/protocol/mood"; + public static final String MOOD_NOTIFY = MOOD_NODE + "+notify"; + + private static final Map INSTANCES = new WeakHashMap<>(); + + private final Set moodListeners = new HashSet<>(); + private final AsyncButOrdered asyncButOrdered = new AsyncButOrdered<>(); + private PubSubManager pubSubManager; + + private MoodManager(XMPPConnection connection) { + super(connection); + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(MOOD_NOTIFY); + PepManager.getInstanceFor(connection).addPepListener(new PepListener() { + @Override + public void eventReceived(final EntityBareJid from, final EventElement event, final Message message) { + if (!MOOD_NODE.equals(event.getEvent().getNode())) { + return; + } + + final BareJid contact = from.asBareJid(); + asyncButOrdered.performAsyncButOrdered(contact, new Runnable() { + @Override + public void run() { + ItemsExtension items = (ItemsExtension) event.getExtensions().get(0); + PayloadItem payload = (PayloadItem) items.getItems().get(0); + MoodElement mood = (MoodElement) payload.getPayload(); + + for (MoodListener listener : moodListeners) { + listener.onMoodUpdated(contact, message, mood); + } + } + }); + } + }); + } + + public static MoodManager getInstanceFor(XMPPConnection connection) { + MoodManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new MoodManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + public void setMood(Mood mood) + throws InterruptedException, SmackException.NotLoggedInException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException { + setMood(mood, null, null); + } + + public void setMood(Mood mood, String text) + throws InterruptedException, SmackException.NotLoggedInException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException { + setMood(mood, null, text); + } + + public void setMood(Mood mood, MoodConcretisation concretisation) + throws InterruptedException, SmackException.NotLoggedInException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException { + setMood(mood, concretisation, null); + } + + public void setMood(Mood mood, MoodConcretisation concretisation, String text) + throws InterruptedException, SmackException.NotLoggedInException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException { + MoodElement element = buildMood(mood, concretisation, text); + publishMood(element); + } + + public void clearMood() + throws InterruptedException, SmackException.NotLoggedInException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException { + MoodElement element = buildMood(null, null, null); + publishMood(element); + } + + private void publishMood(MoodElement moodElement) + throws SmackException.NotLoggedInException, InterruptedException, PubSubException.NotALeafNodeException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { + if (pubSubManager == null) { + pubSubManager = PubSubManager.getInstance(getAuthenticatedConnectionOrThrow(), connection().getUser().asBareJid()); + } + + LeafNode node = pubSubManager.getOrCreateLeafNode(MOOD_NODE); + node.publish(new PayloadItem<>(moodElement)); + } + + private static MoodElement buildMood(Mood mood, MoodConcretisation concretisation, String text) { + return new MoodElement( + new MoodElement.MoodSubjectElement(mood, concretisation), + text); + } + + public static void addMoodToMessage(Message message, Mood mood) { + addMoodToMessage(message, mood, null); + } + + public static void addMoodToMessage(Message message, Mood mood, MoodConcretisation concretisation) { + MoodElement element = buildMood(mood, concretisation, null); + message.addExtension(element); + } + + public synchronized void addMoodListener(MoodListener listener) { + moodListeners.add(listener); + } + + public synchronized void removeMoodListener(MoodListener listener) { + moodListeners.remove(listener); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/MoodConcretisation.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/MoodConcretisation.java new file mode 100644 index 000000000..228dd63b1 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/MoodConcretisation.java @@ -0,0 +1,39 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood.element; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +/** + * XEP-0107 can be extended with additional custom mood concretisations. + * In order to extend Smacks implementation with a custom mood concretisation, just extend this class and overwrite + * {@link #getElementName()} and {@link #getNamespace()} with your custom values. + * + * TODO: Solution for provider. + */ +public abstract class MoodConcretisation implements ExtensionElement { + + @Override + public final XmlStringBuilder toXML(String enclosingNamespace) { + return new XmlStringBuilder(this).closeEmptyElement(); + } + + public String getMood() { + return getElementName(); + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/MoodElement.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/MoodElement.java new file mode 100644 index 000000000..2dcf86356 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/MoodElement.java @@ -0,0 +1,200 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood.element; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.Objects; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.mood.Mood; + +/** + * {@link ExtensionElement} that contains the users mood. + * + * Optionally this element also contains a text node, which contains a natural language description or + * reason for the mood. + */ +public class MoodElement implements ExtensionElement { + + public static final String NAMESPACE = "http://jabber.org/protocol/mood"; + public static final String ELEMENT = "mood"; + public static final String ELEM_TEXT = "text"; + + private final MoodSubjectElement mood; + private final String text; + + public MoodElement(MoodSubjectElement mood, String text) { + if (mood == null && text != null) { + throw new IllegalArgumentException("If is null, text MUST be null too."); + } + + this.mood = mood; + this.text = text; + } + + /** + * Return the senders mood. + * This method returns null in case the sender wants to stop sending their mood. + * + * @return mood or null + */ + public Mood getMood() { + return mood != null ? mood.getMood() : null; + } + + /** + * The user might set a reason or description for/of their mood. + * This method returns a natural language reason for the mood. + * + * @return text or null. + */ + public String getText() { + return text; + } + + /** + * Returns true, if the user gives a reason for their mood. + * + * @return true or false + */ + public boolean hasText() { + return getText() != null; + } + + /** + * Implementors might implement custom concretisations of mood. + * This method returns any custom concretisation of the mood the user might have set. + * + * @return concretisation or null. + */ + public MoodConcretisation getMoodConcretisation() { + return mood != null ? mood.getConcretisation() : null; + } + + /** + * Return true, if this mood has a concretisation. + * + * @return true or false + */ + public boolean hasConcretisation() { + return getMoodConcretisation() != null; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML(String enclosingNamespace) { + XmlStringBuilder xml = new XmlStringBuilder(this); + + if (mood == null && text == null) { + // Empty mood element used as STOP signal + return xml.closeEmptyElement(); + } + xml.rightAngleBracket(); + + xml.optAppend(mood); + + if (text != null) { + xml.openElement(ELEM_TEXT) + .append(text) + .closeElement(ELEM_TEXT); + } + return xml.closeElement(getElementName()); + } + + /** + * Extract a {@link MoodElement} from a message. + * + * @param message message + * + * @return {@link MoodElement} or null. + */ + public static MoodElement fromMessage(Message message) { + return message.getExtension(ELEMENT, NAMESPACE); + } + + /** + * Return true, if the {@code message} has a {@link MoodElement}, otherwise false. + * + * @param message message + * + * @return true of false + */ + public static boolean hasMoodElement(Message message) { + return message.hasExtension(ELEMENT, NAMESPACE); + } + + /** + * {@link NamedElement} which represents the mood. + * This element has the element name of the mood selected from {@link Mood}. + */ + public static class MoodSubjectElement implements NamedElement { + + private final Mood mood; + private final MoodConcretisation concretisation; + + public MoodSubjectElement(Mood mood, MoodConcretisation concretisation) { + this.mood = Objects.requireNonNull(mood); + this.concretisation = concretisation; + } + + @Override + public String getElementName() { + return mood.toString(); + } + + @Override + public XmlStringBuilder toXML(String enclosingNamespace) { + XmlStringBuilder xml = new XmlStringBuilder(); + + if (concretisation == null) { + return xml.emptyElement(getElementName()); + } + + return xml.openElement(getElementName()) + .append(concretisation.toXML(null)) + .closeElement(getElementName()); + } + + /** + * Return the mood of the user. + * + * @return mood or null + */ + public Mood getMood() { + return mood; + } + + /** + * Return the concretisation of the mood. + * + * @return concretisation or null + */ + public MoodConcretisation getConcretisation() { + return concretisation; + } + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/package-info.java new file mode 100644 index 000000000..6990d800c --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/element/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Smack's API for XEP-0107: User Mood. + */ +package org.jivesoftware.smackx.mood.element; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/package-info.java new file mode 100644 index 000000000..96f41b51f --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Smack's API for XEP-0107: User Mood. + */ +package org.jivesoftware.smackx.mood; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/MoodConcretisationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/MoodConcretisationProvider.java new file mode 100644 index 000000000..5b6764853 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/MoodConcretisationProvider.java @@ -0,0 +1,28 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.mood.element.MoodConcretisation; + +import org.xmlpull.v1.XmlPullParser; + +public abstract class MoodConcretisationProvider extends ExtensionElementProvider { + + @Override + public abstract C parse(XmlPullParser parser, int initialDepth) throws Exception; +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/MoodProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/MoodProvider.java new file mode 100644 index 000000000..7ac71eb12 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/MoodProvider.java @@ -0,0 +1,84 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood.provider; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smackx.mood.Mood; +import org.jivesoftware.smackx.mood.element.MoodConcretisation; +import org.jivesoftware.smackx.mood.element.MoodElement; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +public class MoodProvider extends ExtensionElementProvider { + + private static final Logger LOGGER = Logger.getLogger(MoodProvider.class.getName()); + public static final MoodProvider INSTANCE = new MoodProvider(); + + @Override + public MoodElement parse(XmlPullParser parser, int initialDepth) throws Exception { + String text = null; + Mood mood = null; + MoodConcretisation concretisation = null; + + outerloop: while (true) { + int tag = parser.next(); + String name = parser.getName(); + String namespace = parser.getNamespace(); + + switch (tag) { + case START_TAG: + if (MoodElement.ELEM_TEXT.equals(name)) { + text = parser.nextText(); + continue outerloop; + } + + if (!MoodElement.NAMESPACE.equals(namespace)) { + LOGGER.log(Level.FINE, "Foreign namespace " + namespace + " detected. Try to find suitable MoodConcretisationProvider."); + MoodConcretisationProvider provider = (MoodConcretisationProvider) ProviderManager.getExtensionProvider(name, namespace); + if (provider != null) { + concretisation = provider.parse(parser); + } else { + LOGGER.log(Level.FINE, "No provider for <" + name + " xmlns:'" + namespace + "'/> found. Ignore."); + } + continue outerloop; + } + + try { + mood = Mood.valueOf(name); + continue outerloop; + } catch (IllegalArgumentException e) { + throw new XmlPullParserException("Unknown mood value: " + name + " encountered."); + } + + case END_TAG: + if (MoodElement.ELEMENT.equals(name)) { + MoodElement.MoodSubjectElement subjectElement = (mood == null && concretisation == null) ? + null : new MoodElement.MoodSubjectElement(mood, concretisation); + return new MoodElement(subjectElement, text); + } + } + } + } +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/SimpleMoodConcretisationProvider.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/SimpleMoodConcretisationProvider.java new file mode 100644 index 000000000..9d360febb --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/SimpleMoodConcretisationProvider.java @@ -0,0 +1,39 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood.provider; + +import org.jivesoftware.smackx.mood.element.MoodConcretisation; + +import org.xmlpull.v1.XmlPullParser; + +/** + * Simple {@link MoodConcretisationProvider} implementation, suitable for really simple {@link MoodConcretisation}s, + * that only consist of name and namespace. In such a case it is sufficient to just return an instance of the element + * addressed by the element name and namespace, since no other values must be parsed. + * + * @param type of the {@link MoodConcretisation} + */ +public abstract class SimpleMoodConcretisationProvider extends MoodConcretisationProvider { + + @Override + public C parse(XmlPullParser parser, int initialDepth) throws Exception { + // Since the elements name and namespace is known, we can just return an instance of the MoodConcretisation. + return simpleExtension(); + } + + protected abstract C simpleExtension(); +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/package-info.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/package-info.java new file mode 100644 index 000000000..6c58dc366 --- /dev/null +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/mood/provider/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Smack's API for XEP-0107: User Mood. + */ +package org.jivesoftware.smackx.mood.provider; diff --git a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers index 806686f50..a34d5435d 100644 --- a/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers +++ b/smack-extensions/src/main/resources/org.jivesoftware.smack.extensions/extensions.providers @@ -582,4 +582,11 @@ org.jivesoftware.smackx.jingle.provider.JingleErrorProvider + + + mood + http://jabber.org/protocol/mood + org.jivesoftware.smackx.mood.provider.MoodProvider + + diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodConcretisationTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodConcretisationTest.java new file mode 100644 index 000000000..3f159cdeb --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodConcretisationTest.java @@ -0,0 +1,116 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.mood.element.MoodConcretisation; +import org.jivesoftware.smackx.mood.element.MoodElement; +import org.jivesoftware.smackx.mood.provider.MoodProvider; +import org.jivesoftware.smackx.mood.provider.SimpleMoodConcretisationProvider; + +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; + +/** + * This test checks, if extending XEP-0107: User Mood using custom mood concretisations works. + * For that purpose, the second example of XEP-0107 §2.1 is recreated by creating a custom mood concretisation (ecstatic), + * along with a provider, which is dynamically registered with the {@link ProviderManager}. + */ +public class MoodConcretisationTest extends SmackTestSuite { + + @Test + public void concretisationTest() throws Exception { + ProviderManager.addExtensionProvider( + EcstaticMoodConcretisation.ELEMENT, + EcstaticMoodConcretisation.NAMESPACE, + EcstaticMoodConcretisationProvider.INSTANCE); + + String xml = + "" + + "" + + "" + + "" + + "Yay, the mood spec has been approved!" + + ""; + + MoodElement element = new MoodElement( + new MoodElement.MoodSubjectElement( + Mood.happy, + new EcstaticMoodConcretisation()), + "Yay, the mood spec has been approved!"); + + assertXMLEqual(xml, element.toXML(null).toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MoodElement parsed = MoodProvider.INSTANCE.parse(parser); + assertXMLEqual(xml, parsed.toXML(null).toString()); + + assertTrue(parsed.hasConcretisation()); + assertTrue(parsed.hasText()); + assertEquals(EcstaticMoodConcretisation.ELEMENT, parsed.getMoodConcretisation().getMood()); + } + + @Test + public void unknownConcretisationTest() throws Exception { + String xml = + "" + + "" + + "" + + "" + + "Hoot hoot!" + + ""; + + XmlPullParser parser = TestUtils.getParser(xml); + MoodElement element = MoodProvider.INSTANCE.parse(parser); + + // We should not have a provider for sad owls, so the concretisation should not be there. + assertFalse(element.hasConcretisation()); + } + + public static class EcstaticMoodConcretisation extends MoodConcretisation { + + public static final String NAMESPACE = "https://example.org/"; + public static final String ELEMENT = "ecstatic"; + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + } + + public static class EcstaticMoodConcretisationProvider extends SimpleMoodConcretisationProvider { + + @Override + protected EcstaticMoodConcretisation simpleExtension() { + return new EcstaticMoodConcretisation(); + } + + static EcstaticMoodConcretisationProvider INSTANCE = new EcstaticMoodConcretisationProvider(); + } +} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodElementTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodElementTest.java new file mode 100644 index 000000000..b504ed98b --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodElementTest.java @@ -0,0 +1,82 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNull; +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.mood.element.MoodElement; +import org.jivesoftware.smackx.mood.provider.MoodProvider; + +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +public class MoodElementTest extends SmackTestSuite { + + @Test + public void toXmlTest() throws Exception { + String xml = + "" + + "" + + "Yay, the mood spec has been approved!" + + ""; + MoodElement moodElement = new MoodElement(new MoodElement.MoodSubjectElement(Mood.happy, null), "Yay, the mood spec has been approved!"); + + assertXMLEqual(xml, moodElement.toXML(null).toString()); + assertFalse(moodElement.hasConcretisation()); + assertEquals(Mood.happy, moodElement.getMood()); + + XmlPullParser parser = TestUtils.getParser(xml); + MoodElement parsed = MoodProvider.INSTANCE.parse(parser); + assertEquals(xml, parsed.toXML(null).toString()); + } + + @Test(expected = IllegalArgumentException.class) + public void illegalArgumentsTest() { + MoodElement element = new MoodElement(null, "Text alone is not allowed."); + } + + @Test + public void emptyMoodTest() throws Exception { + MoodElement empty = new MoodElement(null, null); + assertNull(empty.getText()); + assertNull(empty.getMood()); + assertNull(empty.getMoodConcretisation()); + assertFalse(empty.hasText()); + assertFalse(empty.hasConcretisation()); + + String xml = ""; + XmlPullParser parser = TestUtils.getParser(xml); + MoodElement emptyParsed = MoodProvider.INSTANCE.parse(parser); + assertEquals(empty.toXML(null).toString(), emptyParsed.toXML(null).toString()); + } + + @Test(expected = XmlPullParserException.class) + public void unknownMoodValueExceptionTest() throws Exception { + String xml = + "" + + "" + + ""; + XmlPullParser parser = TestUtils.getParser(xml); + MoodElement element = MoodProvider.INSTANCE.parse(parser); + } +} diff --git a/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodManagerTest.java b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodManagerTest.java new file mode 100644 index 000000000..943371b12 --- /dev/null +++ b/smack-extensions/src/test/java/org/jivesoftware/smackx/mood/MoodManagerTest.java @@ -0,0 +1,50 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertTrue; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smackx.mood.element.MoodElement; + +import org.junit.Test; + +public class MoodManagerTest extends SmackTestSuite { + + @Test + public void addMessageTest() { + Message message = new Message(); + MoodManager.addMoodToMessage(message, Mood.sad); + + assertTrue(message.hasExtension(MoodElement.ELEMENT, MoodElement.NAMESPACE)); + assertTrue(MoodElement.hasMoodElement(message)); + MoodElement element = MoodElement.fromMessage(message); + assertNotNull(element); + assertEquals(Mood.sad, element.getMood()); + assertFalse(element.hasConcretisation()); + assertFalse(element.hasText()); + + message = new Message(); + MoodManager.addMoodToMessage(message, Mood.happy, new MoodConcretisationTest.EcstaticMoodConcretisation()); + element = MoodElement.fromMessage(message); + assertTrue(element.hasConcretisation()); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java new file mode 100644 index 000000000..cd4e8a34c --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/MoodIntegrationTest.java @@ -0,0 +1,69 @@ +/** + * + * Copyright 2018 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.mood; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.roster.RosterIntegrationTest; +import org.jivesoftware.smackx.mood.element.MoodElement; + +import org.igniterealtime.smack.inttest.AbstractSmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.util.SimpleResultSyncPoint; +import org.junit.AfterClass; +import org.jxmpp.jid.BareJid; + +public class MoodIntegrationTest extends AbstractSmackIntegrationTest { + + private final MoodManager mm1; + private final MoodManager mm2; + + public MoodIntegrationTest(SmackIntegrationTestEnvironment environment) { + super(environment); + mm1 = MoodManager.getInstanceFor(conOne); + mm2 = MoodManager.getInstanceFor(conTwo); + } + + @SmackIntegrationTest + public void test() throws Exception { + RosterIntegrationTest.ensureBothAccountsAreSubscribedToEachOther(conOne, conTwo, timeout); + + final SimpleResultSyncPoint moodReceived = new SimpleResultSyncPoint(); + + mm2.addMoodListener(new MoodListener() { + @Override + public void onMoodUpdated(BareJid jid, Message message, MoodElement moodElement) { + if (moodElement.getMood() == Mood.satisfied) { + moodReceived.signal(); + } + } + }); + + mm1.setMood(Mood.satisfied); + + moodReceived.waitForResult(timeout); + } + + @AfterClass + public void unsubscribe() + throws SmackException.NotLoggedInException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { + RosterIntegrationTest.ensureBothAccountsAreNotInEachOthersRoster(conOne, conTwo); + } +} diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/package-info.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/package-info.java new file mode 100644 index 000000000..9138bbf81 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/mood/package-info.java @@ -0,0 +1,23 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Integration Tests for Smacks support for XEP-0107: User Mood. + * + * @see + * XEP-0107: User Mood + */ +package org.jivesoftware.smackx.mood;