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}