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