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