001/**
002 *
003 * Copyright 2003-2007 Jive Software.
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.jiveproperties.packet;
018
019import java.io.ByteArrayOutputStream;
020import java.io.ObjectOutputStream;
021import java.io.Serializable;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.HashSet;
026import java.util.Map;
027import java.util.logging.Level;
028import java.util.logging.Logger;
029
030import org.jivesoftware.smack.packet.PacketExtension;
031import org.jivesoftware.smack.util.StringUtils;
032import org.jivesoftware.smack.util.XmlStringBuilder;
033
034/**
035 * Properties provide an easy mechanism for clients to share data. Each property has a
036 * String name, and a value that is a Java primitive (int, long, float, double, boolean)
037 * or any Serializable object (a Java object is Serializable when it implements the
038 * Serializable interface).
039 *
040 */
041public class JivePropertiesExtension implements PacketExtension {
042    /**
043     * Namespace used to store packet properties.
044     */
045    public static final String NAMESPACE = "http://www.jivesoftware.com/xmlns/xmpp/properties";
046
047    public static final String ELEMENT = "properties";
048
049    private static final Logger LOGGER = Logger.getLogger(JivePropertiesExtension.class.getName());
050    
051    private final Map<String, Object> properties;
052
053    public JivePropertiesExtension() {
054        properties = new HashMap<String, Object>();
055    }
056
057    public JivePropertiesExtension(Map<String, Object> properties) {
058        this.properties = properties;
059    }
060
061    /**
062     * Returns the packet property with the specified name or <tt>null</tt> if the
063     * property doesn't exist. Property values that were originally primitives will
064     * be returned as their object equivalent. For example, an int property will be
065     * returned as an Integer, a double as a Double, etc.
066     *
067     * @param name the name of the property.
068     * @return the property, or <tt>null</tt> if the property doesn't exist.
069     */
070    public synchronized Object getProperty(String name) {
071        if (properties == null) {
072            return null;
073        }
074        return properties.get(name);
075    }
076
077    /**
078     * Sets a property with an Object as the value. The value must be Serializable
079     * or an IllegalArgumentException will be thrown.
080     *
081     * @param name the name of the property.
082     * @param value the value of the property.
083     */
084    public synchronized void setProperty(String name, Object value) {
085        if (!(value instanceof Serializable)) {
086            throw new IllegalArgumentException("Value must be serialiazble");
087        }
088        properties.put(name, value);
089    }
090
091    /**
092     * Deletes a property.
093     *
094     * @param name the name of the property to delete.
095     */
096    public synchronized void deleteProperty(String name) {
097        if (properties == null) {
098            return;
099        }
100        properties.remove(name);
101    }
102
103    /**
104     * Returns an unmodifiable collection of all the property names that are set.
105     *
106     * @return all property names.
107     */
108    public synchronized Collection<String> getPropertyNames() {
109        if (properties == null) {
110            return Collections.emptySet();
111        }
112        return Collections.unmodifiableSet(new HashSet<String>(properties.keySet()));
113    }
114
115    /**
116     * Returns an unmodifiable map of all properties.
117     *
118     * @return all properties.
119     */
120    public synchronized Map<String, Object> getProperties() {
121        if (properties == null) {
122            return Collections.emptyMap();
123        }
124        return Collections.unmodifiableMap(new HashMap<String, Object>(properties));
125    }
126
127    @Override
128    public String getElementName() {
129        return ELEMENT;
130    }
131
132    @Override
133    public String getNamespace() {
134        return NAMESPACE;
135    }
136
137    @Override
138    public CharSequence toXML() {
139        XmlStringBuilder xml = new XmlStringBuilder(this);
140        xml.rightAngelBracket();
141        // Loop through all properties and write them out.
142        for (String name : getPropertyNames()) {
143            Object value = getProperty(name);
144            xml.openElement("property");
145            xml.element("name", name);
146            xml.halfOpenElement("value");
147
148            String type;
149            String valueStr;
150            if (value instanceof Integer) {
151                type = "integer";
152                valueStr = Integer.toString((Integer) value);
153            }
154            else if (value instanceof Long) {
155                type = "long";
156                valueStr = Long.toString((Long) value);
157            }
158            else if (value instanceof Float) {
159                type = "float";
160                valueStr = Float.toString((Float) value);
161            }
162            else if (value instanceof Double) {
163                type = "double";
164                valueStr = Double.toString((Double) value);
165            }
166            else if (value instanceof Boolean) {
167                type = "boolean";
168                valueStr = Boolean.toString((Boolean) value);
169            }
170            else if (value instanceof String) {
171                type = "string";
172                valueStr = (String) value;
173            }
174            // Otherwise, it's a generic Serializable object. Serialized objects are in
175            // a binary format, which won't work well inside of XML. Therefore, we base-64
176            // encode the binary data before adding it.
177            else {
178                ByteArrayOutputStream byteStream = null;
179                ObjectOutputStream out = null;
180                try {
181                    byteStream = new ByteArrayOutputStream();
182                    out = new ObjectOutputStream(byteStream);
183                    out.writeObject(value);
184                    type = "java-object";
185                    valueStr = StringUtils.encodeBase64(byteStream.toByteArray());
186                }
187                catch (Exception e) {
188                    LOGGER.log(Level.SEVERE, "Error encoding java object", e);
189                    type = "java-object";
190                    valueStr = "Serializing error: " + e.getMessage();
191                }
192                finally {
193                    if (out != null) {
194                        try {
195                            out.close();
196                        }
197                        catch (Exception e) {
198                            // Ignore.
199                        }
200                    }
201                    if (byteStream != null) {
202                        try {
203                            byteStream.close();
204                        }
205                        catch (Exception e) {
206                            // Ignore.
207                        }
208                    }
209                }
210            }
211            xml.attribute("type", type);
212            xml.rightAngelBracket();
213            xml.escape(valueStr);
214            xml.closeElement("value");
215            xml.closeElement("property");
216        }
217        xml.closeElement(this);
218
219        return xml;
220    }
221
222}