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 */ 017 018package org.jivesoftware.smackx.muc; 019 020import java.net.MalformedURLException; 021import java.net.URL; 022import java.util.Collections; 023import java.util.List; 024import java.util.logging.Level; 025import java.util.logging.Logger; 026 027import org.jivesoftware.smackx.disco.packet.DiscoverInfo; 028import org.jivesoftware.smackx.xdata.Form; 029import org.jivesoftware.smackx.xdata.FormField; 030 031import org.jxmpp.jid.EntityBareJid; 032import org.jxmpp.jid.Jid; 033import org.jxmpp.jid.util.JidUtil; 034 035/** 036 * Represents the room information that was discovered using Service Discovery. It's possible to 037 * obtain information about a room before joining the room but only for rooms that are public (i.e. 038 * rooms that may be discovered). 039 * 040 * @author Gaston Dombiak 041 */ 042public class RoomInfo { 043 044 private static final Logger LOGGER = Logger.getLogger(RoomInfo.class.getName()); 045 046 /** 047 * JID of the room. The localpart of the JID is commonly used as the ID of the room or name. 048 */ 049 private final EntityBareJid room; 050 /** 051 * Description of the room. 052 */ 053 private final String description; 054 055 /** 056 * Name of the room. 057 */ 058 private final String name; 059 060 /** 061 * Last known subject of the room. 062 */ 063 private final String subject; 064 /** 065 * Current number of occupants in the room. 066 */ 067 private final int occupantsCount; 068 /** 069 * A room is considered members-only if an invitation is required in order to enter the room. 070 * Any user that is not a member of the room won't be able to join the room unless the user 071 * decides to register with the room (thus becoming a member). 072 */ 073 private final boolean membersOnly; 074 /** 075 * Moderated rooms enable only participants to speak. Users that join the room and aren't 076 * participants can't speak (they are just visitors). 077 */ 078 private final boolean moderated; 079 /** 080 * Every presence stanza can include the JID of every occupant unless the owner deactives this 081 * configuration. 082 */ 083 private final boolean nonanonymous; 084 /** 085 * Indicates if users must supply a password to join the room. 086 */ 087 private final boolean passwordProtected; 088 /** 089 * Persistent rooms are saved to the database to make sure that rooms configurations can be 090 * restored in case the server goes down. 091 */ 092 private final boolean persistent; 093 094 /** 095 * Maximum number of history messages returned by the room. 096 */ 097 private final int maxhistoryfetch; 098 099 /** 100 * Contact Address 101 */ 102 private final List<EntityBareJid> contactJid; 103 104 /** 105 * Natural Language for Room Discussions 106 */ 107 private final String lang; 108 109 /** 110 * An associated LDAP group that defined room membership. Should be an LDAP 111 * Distinguished Name 112 */ 113 private final String ldapgroup; 114 115 /** 116 * True if the room subject can be modified by participants 117 */ 118 private final Boolean subjectmod; 119 120 /** 121 * URL for archived discussion logs 122 */ 123 private final URL logs; 124 125 /** 126 * An associated pubsub node 127 */ 128 private final String pubsub; 129 130 /** 131 * The rooms extended configuration form; 132 */ 133 private final Form form; 134 135 RoomInfo(DiscoverInfo info) { 136 final Jid from = info.getFrom(); 137 if (from != null) { 138 this.room = info.getFrom().asEntityBareJidIfPossible(); 139 } else { 140 this.room = null; 141 } 142 // Get the information based on the discovered features 143 this.membersOnly = info.containsFeature("muc_membersonly"); 144 this.moderated = info.containsFeature("muc_moderated"); 145 this.nonanonymous = info.containsFeature("muc_nonanonymous"); 146 this.passwordProtected = info.containsFeature("muc_passwordprotected"); 147 this.persistent = info.containsFeature("muc_persistent"); 148 149 List<DiscoverInfo.Identity> identities = info.getIdentities(); 150 // XEP-45 6.4 is not really clear on the topic if an identity needs to 151 // be send together with the disco result and how to call this description. 152 if (!identities.isEmpty()) { 153 this.name = identities.get(0).getName(); 154 } else { 155 LOGGER.warning("DiscoverInfo does not contain any Identity: " + info.toXML(null)); 156 this.name = ""; 157 } 158 String subject = ""; 159 int occupantsCount = -1; 160 String description = ""; 161 int maxhistoryfetch = -1; 162 List<EntityBareJid> contactJid = null; 163 String lang = null; 164 String ldapgroup = null; 165 Boolean subjectmod = null; 166 URL logs = null; 167 String pubsub = null; 168 // Get the information based on the discovered extended information 169 form = Form.getFormFrom(info); 170 if (form != null) { 171 FormField descField = form.getField("muc#roominfo_description"); 172 if (descField != null && !descField.getValues().isEmpty()) { 173 // Prefer the extended result description 174 description = descField.getFirstValue(); 175 } 176 177 FormField subjField = form.getField("muc#roominfo_subject"); 178 if (subjField != null && !subjField.getValues().isEmpty()) { 179 subject = subjField.getFirstValue(); 180 } 181 182 FormField occCountField = form.getField("muc#roominfo_occupants"); 183 if (occCountField != null && !occCountField.getValues().isEmpty()) { 184 occupantsCount = Integer.parseInt(occCountField.getFirstValue()); 185 } 186 187 FormField maxhistoryfetchField = form.getField("muc#maxhistoryfetch"); 188 if (maxhistoryfetchField != null && !maxhistoryfetchField.getValues().isEmpty()) { 189 maxhistoryfetch = Integer.parseInt(maxhistoryfetchField.getFirstValue()); 190 } 191 192 FormField contactJidField = form.getField("muc#roominfo_contactjid"); 193 if (contactJidField != null && !contactJidField.getValues().isEmpty()) { 194 List<CharSequence> contactJidValues = contactJidField.getValues(); 195 contactJid = JidUtil.filterEntityBareJidList(JidUtil.jidSetFrom(contactJidValues)); 196 } 197 198 FormField langField = form.getField("muc#roominfo_lang"); 199 if (langField != null && !langField.getValues().isEmpty()) { 200 lang = langField.getFirstValue(); 201 } 202 203 FormField ldapgroupField = form.getField("muc#roominfo_ldapgroup"); 204 if (ldapgroupField != null && !ldapgroupField.getValues().isEmpty()) { 205 ldapgroup = ldapgroupField.getFirstValue(); 206 } 207 208 FormField subjectmodField = form.getField("muc#roominfo_subjectmod"); 209 if (subjectmodField != null && !subjectmodField.getValues().isEmpty()) { 210 String firstValue = subjectmodField.getFirstValue(); 211 subjectmod = ("true".equals(firstValue) || "1".equals(firstValue)); 212 } 213 214 FormField urlField = form.getField("muc#roominfo_logs"); 215 if (urlField != null && !urlField.getValues().isEmpty()) { 216 String urlString = urlField.getFirstValue(); 217 try { 218 logs = new URL(urlString); 219 } catch (MalformedURLException e) { 220 LOGGER.log(Level.SEVERE, "Could not parse URL", e); 221 } 222 } 223 224 FormField pubsubField = form.getField("muc#roominfo_pubsub"); 225 if (pubsubField != null && !pubsubField.getValues().isEmpty()) { 226 pubsub = pubsubField.getFirstValue(); 227 } 228 } 229 this.description = description; 230 this.subject = subject; 231 this.occupantsCount = occupantsCount; 232 this.maxhistoryfetch = maxhistoryfetch; 233 this.contactJid = contactJid; 234 this.lang = lang; 235 this.ldapgroup = ldapgroup; 236 this.subjectmod = subjectmod; 237 this.logs = logs; 238 this.pubsub = pubsub; 239 } 240 241 /** 242 * Returns the JID of the room whose information was discovered. 243 * 244 * @return the JID of the room whose information was discovered. 245 */ 246 public EntityBareJid getRoom() { 247 return room; 248 } 249 250 /** 251 * Returns the room name. 252 * <p> 253 * The name returnd here was provided as value of the name attribute 254 * of the returned identity within the disco#info result. 255 * </p> 256 * 257 * @return the name of the room. 258 */ 259 public String getName() { 260 return name; 261 } 262 263 /** 264 * Returns the discovered description of the room. 265 * <p> 266 * The description returned by this method was provided as value of the form 267 * field of the extended disco info result. It may be <code>null</code>. 268 * </p> 269 * 270 * @return the discovered description of the room or null 271 */ 272 public String getDescription() { 273 return description; 274 } 275 276 /** 277 * Returns the discovered subject of the room. The subject may be null if the room does not 278 * have a subject. 279 * 280 * @return the discovered subject of the room or null 281 */ 282 public String getSubject() { 283 return subject; 284 } 285 286 /** 287 * Returns the discovered number of occupants that are currently in the room. If this 288 * information was not discovered (i.e. the server didn't send it) then a value of -1 will be 289 * returned. 290 * 291 * @return the number of occupants that are currently in the room or -1 if that information was 292 * not provided by the server. 293 */ 294 public int getOccupantsCount() { 295 return occupantsCount; 296 } 297 298 /** 299 * Returns true if the room has restricted the access so that only members may enter the room. 300 * 301 * @return true if the room has restricted the access so that only members may enter the room. 302 */ 303 public boolean isMembersOnly() { 304 return membersOnly; 305 } 306 307 /** 308 * Returns true if the room enabled only participants to speak. Occupants with a role of 309 * visitor won't be able to speak in the room. 310 * 311 * @return true if the room enabled only participants to speak. 312 */ 313 public boolean isModerated() { 314 return moderated; 315 } 316 317 /** 318 * Returns true if presence packets will include the JID of every occupant. 319 * 320 * @return true if presence packets will include the JID of every occupant. 321 */ 322 public boolean isNonanonymous() { 323 return nonanonymous; 324 } 325 326 /** 327 * Returns true if users musy provide a valid password in order to join the room. 328 * 329 * @return true if users musy provide a valid password in order to join the room. 330 */ 331 public boolean isPasswordProtected() { 332 return passwordProtected; 333 } 334 335 /** 336 * Returns true if the room will persist after the last occupant have left the room. 337 * 338 * @return true if the room will persist after the last occupant have left the room. 339 */ 340 public boolean isPersistent() { 341 return persistent; 342 } 343 344 /** 345 * Returns the maximum number of history messages which are returned by the 346 * room or '-1' if this property is not reported by the room. 347 * 348 * @return the maximum number of history messages or '-1' 349 */ 350 public int getMaxHistoryFetch() { 351 return maxhistoryfetch; 352 } 353 354 /** 355 * Returns Contact Addresses as JIDs, if such are reported. 356 * 357 * @return a list of contact addresses for this room. 358 */ 359 public List<EntityBareJid> getContactJids() { 360 return Collections.unmodifiableList(contactJid); 361 } 362 363 /** 364 * Returns the natural language of the room discussion, or <code>null</code>. 365 * 366 * @return the language of the room discussion or <code>null</code>. 367 */ 368 public String getLang() { 369 return lang; 370 } 371 372 /** 373 * Returns an associated LDAP group that defines room membership. The 374 * value should be an LDAP Distinguished Name according to an 375 * implementation-specific or deployment-specific definition of a group. 376 * 377 * @return an associated LDAP group or <code>null</code> 378 */ 379 public String getLdapGroup() { 380 return ldapgroup; 381 } 382 383 /** 384 * Returns an Boolean instance with the value 'true' if the subject can be 385 * modified by the room participants, 'false' if not, or <code>null</code> 386 * if this information is reported by the room. 387 * 388 * @return an boolean that is true if the subject can be modified by 389 * participants or <code>null</code> 390 */ 391 public Boolean isSubjectModifiable() { 392 return subjectmod; 393 } 394 395 /** 396 * An associated pubsub node for this room or <code>null</code>. 397 * 398 * @return the associated pubsub node or <code>null</code> 399 */ 400 public String getPubSub() { 401 return pubsub; 402 } 403 404 /** 405 * Returns the URL where archived discussion logs can be found or 406 * <code>null</code> if there is no such URL. 407 * 408 * @return the URL where archived logs can be found or <code>null</code> 409 */ 410 public URL getLogsUrl() { 411 return logs; 412 } 413 414 /** 415 * Returns the form included in the extended disco info result or 416 * <code>null</code> if no such form was sent. 417 * 418 * @return The room info form or <code>null</code> 419 * @see <a 420 * href="http://xmpp.org/extensions/xep-0045.html#disco-roominfo">XEP-45: 421 * Multi User Chat - 6.5 Querying for Room Information</a> 422 */ 423 public Form getForm() { 424 return form; 425 } 426 427}