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