diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md
index afc3f3e3d..374c85654 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](http://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;