From 61bf5cd4ceb972f8f3f9db39d5a189f4d433d4e6 Mon Sep 17 00:00:00 2001 From: Vyacheslav Blinov Date: Fri, 7 Mar 2014 17:46:46 +0400 Subject: [PATCH] Advanced Message Processing implementation (XEP-0079) Fixes SMACK-544 --- .../smackx/amp/AMPDeliverCondition.java | 83 ++++++ .../smackx/amp/AMPExpireAtCondition.java | 72 +++++ .../jivesoftware/smackx/amp/AMPExtension.java | 273 ++++++++++++++++++ .../smackx/amp/AMPExtensionProvider.java | 125 ++++++++ .../jivesoftware/smackx/amp/AMPManager.java | 121 ++++++++ .../smackx/amp/AMPMatchResourceCondition.java | 78 +++++ .../extensions.providers | 7 + .../smackx/amp/AMPExtensionTest.java | 105 +++++++ .../smackx/amp/correct_stanza_test.xml | 1 + .../smackx/amp/incorrect_stanza_test.xml | 13 + 10 files changed, 878 insertions(+) create mode 100644 extensions/src/main/java/org/jivesoftware/smackx/amp/AMPDeliverCondition.java create mode 100644 extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExpireAtCondition.java create mode 100644 extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtension.java create mode 100644 extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtensionProvider.java create mode 100644 extensions/src/main/java/org/jivesoftware/smackx/amp/AMPManager.java create mode 100644 extensions/src/main/java/org/jivesoftware/smackx/amp/AMPMatchResourceCondition.java create mode 100644 extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java create mode 100644 extensions/src/test/resources/org/jivesoftware/smackx/amp/correct_stanza_test.xml create mode 100644 extensions/src/test/resources/org/jivesoftware/smackx/amp/incorrect_stanza_test.xml diff --git a/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPDeliverCondition.java b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPDeliverCondition.java new file mode 100644 index 000000000..c79b9a4ef --- /dev/null +++ b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPDeliverCondition.java @@ -0,0 +1,83 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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.amp; + +import org.jivesoftware.smack.Connection; + +public class AMPDeliverCondition implements AMPExtension.Condition { + + public static final String NAME = "deliver"; + + /** + * Check if server supports deliver condition + * @param connection Smack connection instance + * @return true if deliver condition is supported. + */ + public static boolean isSupported(Connection connection) { + return AMPManager.isConditionSupported(connection, NAME); + } + + private final Value value; + + /** + * Create new amp deliver condition with value setted to one of defined by XEP-0079. + * See http://xmpp.org/extensions/xep-0079.html#conditions-def-deliver + * @param value AMPDeliveryCondition.Value instance that will be used as value parameter. Can't be null. + */ + public AMPDeliverCondition(Value value) { + if (value == null) + throw new NullPointerException("Can't create AMPDeliverCondition with null value"); + this.value = value; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getValue() { + return value.toString(); + } + + /** + * Value for amp deliver condition as defined by XEP-0079. + * See http://xmpp.org/extensions/xep-0079.html#conditions-def-deliver + */ + public static enum Value { + /** + * The message would be immediately delivered to the intended recipient or routed to the next hop. + */ + direct, + /** + * The message would be forwarded to another XMPP address or account. + */ + forward, + /** + * The message would be sent through a gateway to an address or account on a non-XMPP system. + */ + gateway, + /** + * The message would not be delivered at all (e.g., because the intended recipient is offline and message storage is not enabled). + */ + none, + /** + * The message would be stored offline for later delivery to the intended recipient. + */ + stored + } +} diff --git a/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExpireAtCondition.java b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExpireAtCondition.java new file mode 100644 index 000000000..5eab1b554 --- /dev/null +++ b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExpireAtCondition.java @@ -0,0 +1,72 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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.amp; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.util.XmppDateTime; + +import java.util.Date; + + +public class AMPExpireAtCondition implements AMPExtension.Condition { + + public static final String NAME = "expire-at"; + + /** + * Check if server supports expire-at condition + * @param connection Smack connection instance + * @return true if expire-at condition is supported. + */ + public static boolean isSupported(Connection connection) { + return AMPManager.isConditionSupported(connection, NAME); + } + + private final String value; + + /** + * Create new expire-at amp condition with value setted as XEP-0082 formatted date. + * @param utcDateTime Date instance of time + * that will be used as value parameter after formatting to XEP-0082 format. Can't be null. + */ + public AMPExpireAtCondition(Date utcDateTime) { + if (utcDateTime == null) + throw new NullPointerException("Can't create AMPExpireAtCondition with null value"); + this.value = XmppDateTime.formatXEP0082Date(utcDateTime); + } + + /** + * Create new expire-at amp condition with defined value. + * @param utcDateTime UTC time string that will be used as value parameter. + * Should be formatted as XEP-0082 Date format. Can't be null. + */ + public AMPExpireAtCondition(String utcDateTime) { + if (utcDateTime == null) + throw new NullPointerException("Can't create AMPExpireAtCondition with null value"); + this.value = utcDateTime; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getValue() { + return value; + } + +} diff --git a/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtension.java b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtension.java new file mode 100644 index 000000000..cb6aca8fa --- /dev/null +++ b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtension.java @@ -0,0 +1,273 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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.amp; + +import org.jivesoftware.smack.packet.PacketExtension; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +public class AMPExtension implements PacketExtension { + + public static final String NAMESPACE = "http://jabber.org/protocol/amp"; + public static final String ELEMENT = "amp"; + + private CopyOnWriteArrayList rules = new CopyOnWriteArrayList(); + private boolean perHop = false; + + private final String from; + private final String to; + private final Status status; + + /** + * Create a new AMPExtension instance with defined from, to and status attributes. Used to create incoming packets. + * @param from jid that triggered this amp callback. + * @param to receiver of this amp receipt. + * @param status status of this amp receipt. + */ + public AMPExtension(String from, String to, Status status) { + this.from = from; + this.to = to; + this.status = status; + } + + /** + * Create a new amp request extension to be used with outgoing message. + */ + public AMPExtension() { + this.from = null; + this.to = null; + this.status = null; + } + + /** + * @return jid that triggered this amp callback. + */ + public String getFrom() { + return from; + } + + /** + * @return receiver of this amp receipt. + */ + public String getTo() { + return to; + } + + /** + * Status of this amp notification + * @return Status for this amp + */ + public Status getStatus() { + return status; + } + + /** + * Returns an Iterator for the rules in the packet. + * + * @return an Iterator for the rules in the packet. + */ + public Iterator getRules() { + return Collections.unmodifiableList(new ArrayList(rules)).iterator(); + } + + /** + * Adds a rule to the amp element. Amp can have any number of rules. + * + * @param rule the rule to add. + */ + public void addRule(Rule rule) { + rules.add(rule); + } + + /** + * Returns a count of the rules in the AMP packet. + * + * @return the number of rules in the AMP packet. + */ + public int getRulesCount() { + return rules.size(); + } + + /** + * Sets this amp ruleset to be "per-hop". + * + * @param enabled true if "per-hop" should be enabled + */ + public synchronized void setPerHop(boolean enabled) { + perHop = enabled; + } + + /** + * Returns true is this ruleset is "per-hop". + * + * @return true is this ruleset is "per-hop". + */ + public synchronized boolean isPerHop() { + return perHop; + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "amp" + * + * @return the XML element name of the packet extension. + */ + @Override + public String getElementName() { + return ELEMENT; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "http://jabber.org/protocol/xhtml-im" + * + * @return the XML namespace of the packet extension. + */ + @Override + public String getNamespace() { + return NAMESPACE; + } + + /** + * Returns the XML representation of a XHTML extension according the specification. + **/ + @Override + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\""); + if (status != null) { + buf.append(" status=\"").append(status.toString()).append("\""); + } + if (to != null) { + buf.append(" to=\"").append(to).append("\""); + } + if (from != null) { + buf.append(" from=\"").append(from).append("\""); + } + if (perHop) { + buf.append(" per-hop=\"true\""); + } + buf.append(">"); + + // Loop through all the rules and append them to the string buffer + for (Iterator i = getRules(); i.hasNext();) { + buf.append(i.next().toXML()); + } + + buf.append(""); + return buf.toString(); + } + + /** + * XEP-0079 Rule element. Defines AMP Rule parameters. Can be added to AMPExtension. + */ + public static class Rule { + public static final String ELEMENT = "rule"; + + private final Action action; + private final Condition condition; + + public Action getAction() { + return action; + } + + public Condition getCondition() { + return condition; + } + + /** + * Create a new amp rule with specified action and condition. Value will be taken from condition argument + * @param action action for this rule + * @param condition condition for this rule + */ + public Rule(Action action, Condition condition) { + if (action == null) + throw new NullPointerException("Can't create Rule with null action"); + if (condition == null) + throw new NullPointerException("Can't create Rule with null condition"); + + this.action = action; + this.condition = condition; + } + + private String toXML() { + return "<" + ELEMENT + " " + Action.ATTRIBUTE_NAME + "=\"" + action.toString() + "\" " + + Condition.ATTRIBUTE_NAME + "=\"" + condition.getName() + "\" " + + "value=\"" + condition.getValue() + "\"/>"; + } + } + + /** + * Interface for defining XEP-0079 Conditions and their values + * @see AMPDeliverCondition + * @see AMPExpireAtCondition + * @see AMPMatchResourceCondition + **/ + public static interface Condition { + String getName(); + String getValue(); + + static final String ATTRIBUTE_NAME="condition"; + } + + /** + * amp action attribute + * See http://xmpp.org/extensions/xep-0079.html#actions-def + **/ + public static enum Action { + /** + * The "alert" action triggers a reply stanza to the sending entity. + * This stanza MUST contain the element , + * which itself contains the that triggered this action. In all other respects, + * this action behaves as "drop". + */ + alert, + /** + * The "drop" action silently discards the message from any further delivery attempts + * and ensures that it is not placed into offline storage. + * The drop MUST NOT result in other responses. + */ + drop, + /** + * The "error" action triggers a reply stanza of type "error" to the sending entity. + * The stanza's child MUST contain a + * error condition, + * which itself contains the rules that triggered this action. + */ + error, + /** + * The "notify" action triggers a reply stanza to the sending entity. + * This stanza MUST contain the element , which itself + * contains the that triggered this action. Unlike the other actions, + * this action does not override the default behavior for a server. + * Instead, the server then executes its default behavior after sending the notify. + */ + notify; + + static final String ATTRIBUTE_NAME="action"; + } + + /** + * amp notification status as defined by XEP-0079 + */ + public static enum Status { + alert, + error, + notify + } +} diff --git a/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtensionProvider.java b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtensionProvider.java new file mode 100644 index 000000000..5a746a628 --- /dev/null +++ b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPExtensionProvider.java @@ -0,0 +1,125 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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.amp; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + + +public class AMPExtensionProvider implements PacketExtensionProvider { + + /** + * Creates a new AMPExtensionProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public AMPExtensionProvider() {} + + /** + * Parses a AMPExtension packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + @Override + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + final String from = parser.getAttributeValue(null, "from"); + final String to = parser.getAttributeValue(null, "to"); + final String statusString = parser.getAttributeValue(null, "status"); + AMPExtension.Status status = null; + if (statusString != null) { + try { + status = AMPExtension.Status.valueOf(statusString); + } catch (IllegalArgumentException ex) { + System.err.println("Found invalid amp status " + statusString); + } + } + + AMPExtension ampExtension = new AMPExtension(from, to, status); + + String perHopValue = parser.getAttributeValue(null, "per-hop"); + if (perHopValue != null) { + boolean perHop = Boolean.parseBoolean(perHopValue); + ampExtension.setPerHop(perHop); + } + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals(AMPExtension.Rule.ELEMENT)) { + String actionString = parser.getAttributeValue(null, AMPExtension.Action.ATTRIBUTE_NAME); + String conditionName = parser.getAttributeValue(null, AMPExtension.Condition.ATTRIBUTE_NAME); + String conditionValue = parser.getAttributeValue(null, "value"); + + AMPExtension.Condition condition = createCondition(conditionName, conditionValue); + AMPExtension.Action action = null; + if (actionString != null) { + try { + action = AMPExtension.Action.valueOf(actionString); + } catch (IllegalArgumentException ex) { + System.err.println("Found invalid rule action value " + actionString); + } + } + + if (action == null || condition == null) { + System.err.println("Rule is skipped because either it's action or it's condition is invalid"); + } else { + AMPExtension.Rule rule = new AMPExtension.Rule(action, condition); + ampExtension.addRule(rule); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(AMPExtension.ELEMENT)) { + done = true; + } + } + } + + return ampExtension; + } + + private AMPExtension.Condition createCondition(String name, String value) { + if (name == null || value == null) { + System.err.println("Can't create rule condition from null name and/or value"); + return null; + } + + + if (AMPDeliverCondition.NAME.equals(name)) { + try { + return new AMPDeliverCondition(AMPDeliverCondition.Value.valueOf(value)); + } catch (IllegalArgumentException ex) { + System.err.println("Found invalid rule delivery condition value " + value); + return null; + } + } else if (AMPExpireAtCondition.NAME.equals(name)) { + return new AMPExpireAtCondition(value); + } else if (AMPMatchResourceCondition.NAME.equals(name)) { + try { + return new AMPMatchResourceCondition(AMPMatchResourceCondition.Value.valueOf(value)); + } catch (IllegalArgumentException ex) { + System.err.println("Found invalid rule match-resource condition value " + value); + return null; + } + } else { + System.err.println("Found unknown rule condition name " + name); + return null; + } + } +} diff --git a/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPManager.java b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPManager.java new file mode 100644 index 000000000..653476f1b --- /dev/null +++ b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPManager.java @@ -0,0 +1,121 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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.amp; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.disco.packet.DiscoverInfo; + +import java.util.Iterator; + +/** + * Manages AMP stanzas within messages. A AMPManager provides a high level access to + * get and set AMP rules to messages. + * + * See http://xmpp.org/extensions/xep-0079.html for AMP extension details + * + * @author Vyacheslav Blinov + */ +public class AMPManager { + + + // Enable the AMP support on every established connection + // The ServiceDiscoveryManager class should have been already initialized + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + AMPManager.setServiceEnabled(connection, true); + } + }); + } + + /** + * Enables or disables the AMP support on a given connection.

+ * + * Before starting to send AMP messages to a user, check that the user can handle XHTML + * messages. Enable the AMP support to indicate that this client handles XHTML messages. + * + * @param connection the connection where the service will be enabled or disabled + * @param enabled indicates if the service will be enabled or disabled + */ + public synchronized static void setServiceEnabled(Connection connection, boolean enabled) { + if (isServiceEnabled(connection) == enabled) + return; + + if (enabled) { + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(AMPExtension.NAMESPACE); + } + else { + ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(AMPExtension.NAMESPACE); + } + } + + /** + * Returns true if the AMP support is enabled for the given connection. + * + * @param connection the connection to look for AMP support + * @return a boolean indicating if the AMP support is enabled for the given connection + */ + public static boolean isServiceEnabled(Connection connection) { + connection.getServiceName(); + return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(AMPExtension.NAMESPACE); + } + + /** + * Check if server supports specified action + * @param connection active xmpp connection + * @param action action to check + * @return true if this action is supported. + */ + public static boolean isActionSupported(Connection connection, AMPExtension.Action action) { + String featureName = AMPExtension.NAMESPACE + "?action=" + action.toString(); + return isFeatureSupportedByServer(connection, featureName, AMPExtension.NAMESPACE); + } + + /** + * Check if server supports specified condition + * @param connection active xmpp connection + * @param conditionName name of condition to check + * @return true if this condition is supported. + * @see AMPDeliverCondition + * @see AMPExpireAtCondition + * @see AMPMatchResourceCondition + */ + public static boolean isConditionSupported(Connection connection, String conditionName) { + String featureName = AMPExtension.NAMESPACE + "?condition=" + conditionName; + return isFeatureSupportedByServer(connection, featureName, AMPExtension.NAMESPACE); + } + + private static boolean isFeatureSupportedByServer(Connection connection, String featureName, String node) { + try { + ServiceDiscoveryManager discoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); + DiscoverInfo info = discoveryManager.discoverInfo(connection.getServiceName(), node); + Iterator it = info.getFeatures(); + while (it.hasNext()) { + DiscoverInfo.Feature feature = it.next(); + if (featureName.equals(feature.getVar())) { + return true; + } + } + } catch (XMPPException e) { + e.printStackTrace(); + } + return false; + } +} diff --git a/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPMatchResourceCondition.java b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPMatchResourceCondition.java new file mode 100644 index 000000000..dc7ac766a --- /dev/null +++ b/extensions/src/main/java/org/jivesoftware/smackx/amp/AMPMatchResourceCondition.java @@ -0,0 +1,78 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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.amp; + +import org.jivesoftware.smack.Connection; + +public class AMPMatchResourceCondition implements AMPExtension.Condition { + + public static final String NAME = "match-resource"; + + /** + * Check if server supports match-resource condition + * @param connection Smack connection instance + * @return true if match-resource condition is supported. + */ + public static boolean isSupported(Connection connection) { + return AMPManager.isConditionSupported(connection, NAME); + } + + private final Value value; + + /** + * Create new amp match-resource condition with value setted to one of defined by XEP-0079. + * See http://xmpp.org/extensions/xep-0079.html#conditions-def-match + * @param value AMPDeliveryCondition.Value instance that will be used as value parameter. Can't be null. + */ + public AMPMatchResourceCondition(Value value) { + if (value == null) + throw new NullPointerException("Can't create AMPMatchResourceCondition with null value"); + this.value = value; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getValue() { + return value.toString(); + } + + /** + * match-resource amp condition value as defined by XEP-0079 + * See http://xmpp.org/extensions/xep-0079.html#conditions-def-match + */ + public static enum Value { + /** + * Destination resource matches any value, effectively ignoring the intended resource. + * Example: "home/laptop" matches "home", "home/desktop" or "work/desktop" + */ + any, + /** + * Destination resource exactly matches the intended resource. + * Example: "home/laptop" only matches "home/laptop" and not "home/desktop" or "work/desktop" + */ + exact, + /** + * Destination resource matches any value except for the intended resource. + * Example: "home/laptop" matches "work/desktop", "home" or "home/desktop", but not "home/laptop" + */ + other + } +} diff --git a/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers b/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers index d2298797a..2845b0824 100644 --- a/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers +++ b/extensions/src/main/resources/org.jivesoftware.smackx/extensions.providers @@ -449,4 +449,11 @@ org.jivesoftware.smackx.privacy.provider.PrivacyProvider + + + amp + http://jabber.org/protocol/amp + org.jivesoftware.smackx.amp.AMPExtensionProvider + + diff --git a/extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java b/extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java new file mode 100644 index 000000000..dfc9b5e71 --- /dev/null +++ b/extensions/src/test/java/org/jivesoftware/smackx/amp/AMPExtensionTest.java @@ -0,0 +1,105 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * 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.amp; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.junit.Before; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class AMPExtensionTest { + + private InputStream CORRECT_SENDING_STANZA_STREAM; + private InputStream INCORRECT_RECEIVING_STANZA_STREAM; + + @Before + public void setUp() throws IOException { + CORRECT_SENDING_STANZA_STREAM = getClass().getResourceAsStream("correct_stanza_test.xml"); + INCORRECT_RECEIVING_STANZA_STREAM = getClass().getResourceAsStream("incorrect_stanza_test.xml"); + } + + @Test + public void isCorrectToXmlTransform() throws IOException { + String correctStanza = toString(CORRECT_SENDING_STANZA_STREAM); + + AMPExtension ext = new AMPExtension(); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.alert, new AMPDeliverCondition(AMPDeliverCondition.Value.direct))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.drop, new AMPDeliverCondition(AMPDeliverCondition.Value.forward))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.error, new AMPDeliverCondition(AMPDeliverCondition.Value.gateway))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.notify, new AMPDeliverCondition(AMPDeliverCondition.Value.none))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.notify, new AMPDeliverCondition(AMPDeliverCondition.Value.stored))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.notify, new AMPExpireAtCondition("2004-09-10T08:33:14Z"))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.notify, new AMPMatchResourceCondition(AMPMatchResourceCondition.Value.any))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.notify, new AMPMatchResourceCondition(AMPMatchResourceCondition.Value.exact))); + ext.addRule(new AMPExtension.Rule(AMPExtension.Action.notify, new AMPMatchResourceCondition(AMPMatchResourceCondition.Value.other))); + + assertEquals(correctStanza, ext.toXML()); + } + + @Test + public void isCorrectFromXmlErrorHandling() throws Exception { + AMPExtensionProvider ampProvider = new AMPExtensionProvider(); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(INCORRECT_RECEIVING_STANZA_STREAM, "UTF-8"); + + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(AMPExtension.ELEMENT, parser.getName()); + + PacketExtension extension = ampProvider.parseExtension(parser); + assertTrue(extension instanceof AMPExtension); + AMPExtension amp = (AMPExtension) extension; + + assertEquals(0, amp.getRulesCount()); + assertEquals(AMPExtension.Status.alert, amp.getStatus()); + assertEquals("bernardo@hamlet.lit/elsinore", amp.getFrom()); + assertEquals("francisco@hamlet.lit", amp.getTo()); + } + + @Test + public void isCorrectFromXmlDeserialization() throws Exception { + AMPExtensionProvider ampProvider = new AMPExtensionProvider(); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(CORRECT_SENDING_STANZA_STREAM, "UTF-8"); + + assertEquals(XmlPullParser.START_TAG, parser.next()); + assertEquals(AMPExtension.ELEMENT, parser.getName()); + PacketExtension extension = ampProvider.parseExtension(parser); + assertTrue(extension instanceof AMPExtension); + AMPExtension amp = (AMPExtension) extension; + + assertEquals(9, amp.getRulesCount()); + } + + + private String toString(InputStream stream) throws IOException { + byte[] data = new byte[stream.available()]; + stream.read(data); + stream.close(); + + return new String(data, Charset.defaultCharset()); + } +} diff --git a/extensions/src/test/resources/org/jivesoftware/smackx/amp/correct_stanza_test.xml b/extensions/src/test/resources/org/jivesoftware/smackx/amp/correct_stanza_test.xml new file mode 100644 index 000000000..08ec26b6a --- /dev/null +++ b/extensions/src/test/resources/org/jivesoftware/smackx/amp/correct_stanza_test.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/src/test/resources/org/jivesoftware/smackx/amp/incorrect_stanza_test.xml b/extensions/src/test/resources/org/jivesoftware/smackx/amp/incorrect_stanza_test.xml new file mode 100644 index 000000000..f03b34341 --- /dev/null +++ b/extensions/src/test/resources/org/jivesoftware/smackx/amp/incorrect_stanza_test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file