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