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