001/**
002 *
003 * Copyright 2014 Vyacheslav Blinov
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.amp.packet;
018
019import java.util.Collections;
020import java.util.List;
021import java.util.concurrent.CopyOnWriteArrayList;
022
023import org.jivesoftware.smack.packet.ExtensionElement;
024
025import org.jivesoftware.smackx.amp.AMPDeliverCondition;
026import org.jivesoftware.smackx.amp.AMPExpireAtCondition;
027import org.jivesoftware.smackx.amp.AMPMatchResourceCondition;
028
029public class AMPExtension implements ExtensionElement {
030
031    public static final String NAMESPACE = "http://jabber.org/protocol/amp";
032    public static final String ELEMENT = "amp";
033
034    private final CopyOnWriteArrayList<Rule> rules = new CopyOnWriteArrayList<>();
035    private boolean perHop = false;
036
037    private final String from;
038    private final String to;
039    private final Status status;
040
041    /**
042     * Create a new AMPExtension instance with defined from, to and status attributes. Used to create incoming packets.
043     * @param from jid that triggered this amp callback.
044     * @param to receiver of this amp receipt.
045     * @param status status of this amp receipt.
046     */
047    public AMPExtension(String from, String to, Status status) {
048        this.from = from;
049        this.to = to;
050        this.status = status;
051    }
052
053    /**
054     * Create a new amp request extension to be used with outgoing message.
055     */
056    public AMPExtension() {
057        this.from = null;
058        this.to = null;
059        this.status = null;
060    }
061
062    /**
063     * Get the JID that triggered this AMP callback.
064     * @return jid that triggered this amp callback.
065     */
066    public String getFrom() {
067        return from;
068    }
069
070    /**
071     * Get the receiver of this AMP receipt.
072     * @return receiver of this amp receipt.
073     */
074    public String getTo() {
075        return to;
076    }
077
078    /**
079     * Status of this amp notification.
080     * @return Status for this amp
081     */
082    public Status getStatus() {
083        return status;
084    }
085
086    /**
087     * Returns a unmodifiable List of the rules in the packet.
088     *
089     * @return a unmodifiable List of the rules in the packet.
090     */
091    public List<Rule> getRules() {
092        return Collections.unmodifiableList(rules);
093    }
094
095    /**
096     * Adds a rule to the amp element. Amp can have any number of rules.
097     *
098     * @param rule the rule to add.
099     */
100    public void addRule(Rule rule) {
101        rules.add(rule);
102    }
103
104    /**
105     * Returns a count of the rules in the AMP packet.
106     *
107     * @return the number of rules in the AMP packet.
108     */
109    public int getRulesCount() {
110        return rules.size();
111    }
112
113    /**
114     * Sets this amp ruleset to be "per-hop".
115     *
116     * @param enabled true if "per-hop" should be enabled
117     */
118    public synchronized void setPerHop(boolean enabled) {
119        perHop = enabled;
120    }
121
122    /**
123     * Returns true is this ruleset is "per-hop".
124     *
125     * @return true is this ruleset is "per-hop".
126     */
127    public synchronized boolean isPerHop() {
128        return perHop;
129    }
130
131    /**
132     * Returns the XML element name of the extension sub-packet root element.
133     * Always returns "amp"
134     *
135     * @return the XML element name of the stanza extension.
136     */
137    @Override
138    public String getElementName() {
139        return ELEMENT;
140    }
141
142    /**
143     * Returns the XML namespace of the extension sub-packet root element.
144     * According the specification the namespace is always "http://jabber.org/protocol/xhtml-im"
145     *
146     * @return the XML namespace of the stanza extension.
147     */
148    @Override
149    public String getNamespace() {
150        return NAMESPACE;
151    }
152
153    /**
154     * Returns the XML representation of a XHTML extension according the specification.
155     **/
156    @Override
157    public String toXML(String enclosingNamespace) {
158        StringBuilder buf = new StringBuilder();
159        buf.append('<').append(getElementName()).append(" xmlns=\"").append(getNamespace()).append('"');
160        if (status != null) {
161            buf.append(" status=\"").append(status.toString()).append('"');
162        }
163        if (to != null) {
164            buf.append(" to=\"").append(to).append('"');
165        }
166        if (from != null) {
167            buf.append(" from=\"").append(from).append('"');
168        }
169        if (perHop) {
170            buf.append(" per-hop=\"true\"");
171        }
172        buf.append('>');
173
174        // Loop through all the rules and append them to the string buffer
175        for (Rule rule : getRules()) {
176            buf.append(rule.toXML());
177        }
178
179        buf.append("</").append(getElementName()).append('>');
180        return buf.toString();
181    }
182
183    /**
184     * XEP-0079 Rule element. Defines AMP Rule parameters. Can be added to AMPExtension.
185     */
186    public static class Rule {
187        public static final String ELEMENT = "rule";
188
189        private final Action action;
190        private final Condition condition;
191
192        public Action getAction() {
193            return action;
194        }
195
196        public Condition getCondition() {
197            return condition;
198        }
199
200        /**
201         * Create a new amp rule with specified action and condition. Value will be taken from condition argument
202         * @param action action for this rule
203         * @param condition condition for this rule
204         */
205        public Rule(Action action, Condition condition) {
206            if (action == null)
207                throw new NullPointerException("Can't create Rule with null action");
208            if (condition == null)
209                throw new NullPointerException("Can't create Rule with null condition");
210
211            this.action = action;
212            this.condition = condition;
213        }
214
215        private String toXML() {
216            return "<" + ELEMENT + " " + Action.ATTRIBUTE_NAME + "=\"" + action.toString() + "\" " +
217                    Condition.ATTRIBUTE_NAME + "=\"" + condition.getName() + "\" " +
218                    "value=\"" + condition.getValue() + "\"/>";
219        }
220    }
221
222    /**
223     * Interface for defining XEP-0079 Conditions and their values.
224     * @see AMPDeliverCondition
225     * @see AMPExpireAtCondition
226     * @see AMPMatchResourceCondition
227     **/
228    public interface Condition {
229        String getName();
230        String getValue();
231
232        String ATTRIBUTE_NAME = "condition";
233    }
234
235    /**
236     * amp action attribute.
237     * See http://xmpp.org/extensions/xep-0079.html#actions-def
238     **/
239    public enum Action {
240        /**
241         * The "alert" action triggers a reply &lt;message/&gt; stanza to the sending entity.
242         * This &lt;message/&gt; stanza MUST contain the element &lt;amp status='alert'/&gt;,
243         * which itself contains the &lt;rule/&gt; that triggered this action. In all other respects,
244         * this action behaves as "drop".
245         */
246        alert,
247        /**
248         * The "drop" action silently discards the message from any further delivery attempts
249         * and ensures that it is not placed into offline storage.
250         * The drop MUST NOT result in other responses.
251         */
252        drop,
253        /**
254         * The "error" action triggers a reply &lt;message/&gt; stanza of type "error" to the sending entity.
255         * The &lt;message/&gt; stanza's &lt;error/&gt; child MUST contain a
256         * &lt;failed-rules xmlns='http://jabber.org/protocol/amp#errors'/&gt; error condition,
257         * which itself contains the rules that triggered this action.
258         */
259        error,
260        /**
261         * The "notify" action triggers a reply &lt;message/&gt; stanza to the sending entity.
262         * This &lt;message/&gt; stanza MUST contain the element &lt;amp status='notify'/&gt;, which itself
263         * contains the &lt;rule/&gt; that triggered this action. Unlike the other actions,
264         * this action does not override the default behavior for a server.
265         * Instead, the server then executes its default behavior after sending the notify.
266         */
267        notify;
268
269        public static final String ATTRIBUTE_NAME = "action";
270    }
271
272    /**
273     * amp notification status as defined by XEP-0079.
274     */
275    public enum Status {
276        alert,
277        error,
278        notify
279    }
280}