001/** 002 * 003 * Copyright 2015-2024 Florian Schmaus 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.muc; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.List; 022 023import org.jivesoftware.smack.SmackException.NoResponseException; 024import org.jivesoftware.smack.SmackException.NotConnectedException; 025import org.jivesoftware.smack.XMPPException.XMPPErrorException; 026 027import org.jivesoftware.smackx.muc.MultiUserChatException.MucConfigurationNotSupportedException; 028import org.jivesoftware.smackx.xdata.BooleanFormField; 029import org.jivesoftware.smackx.xdata.FormField; 030import org.jivesoftware.smackx.xdata.form.FillableForm; 031import org.jivesoftware.smackx.xdata.form.FilledForm; 032import org.jivesoftware.smackx.xdata.form.Form; 033 034import org.jxmpp.jid.Jid; 035import org.jxmpp.jid.util.JidUtil; 036 037/** 038 * Multi-User Chat configuration form manager is used to fill out and submit a {@link FilledForm} used to 039 * configure rooms. 040 * <p> 041 * Room configuration needs either be done right after the room is created and still locked. Or at 042 * any later point (see <a href="http://xmpp.org/extensions/xep-0045.html#roomconfig">XEP-45 § 10.2 043 * Subsequent Room Configuration</a>). When done with the configuration, call 044 * {@link #submitConfigurationForm()}. 045 * </p> 046 * <p> 047 * The manager may not provide all possible configuration options. If you want direct access to the 048 * configuration form, use {@link MultiUserChat#getConfigurationForm()} and 049 * {@link MultiUserChat#sendConfigurationForm(FillableForm)}. 050 * </p> 051 */ 052public class MucConfigFormManager { 053 054 private static final String HASH_ROOMCONFIG = "#roomconfig"; 055 056 public static final String FORM_TYPE = MultiUserChatConstants.NAMESPACE + HASH_ROOMCONFIG; 057 058 /** 059 * The constant String {@value}. 060 * 061 * @see <a href="http://xmpp.org/extensions/xep-0045.html#owner">XEP-0045 § 10. Owner Use Cases</a> 062 */ 063 public static final String MUC_ROOMCONFIG_ROOMOWNERS = "muc#roomconfig_roomowners"; 064 065 /** 066 * The constant String {@value}. 067 * 068 * @see <a href="http://xmpp.org/extensions/xep-0045.html#owner">XEP-0045 § 10. Owner Use Cases</a> 069 */ 070 public static final String MUC_ROOMCONFIG_ROOMADMINS = "muc#roomconfig_roomadmins"; 071 072 /** 073 * The constant String {@value}. 074 */ 075 public static final String MUC_ROOMCONFIG_MEMBERSONLY = "muc#roomconfig_membersonly"; 076 077 /** 078 * The constant String {@value}. 079 * 080 * @see <a href="http://xmpp.org/extensions/xep-0045.html#enter-pw">XEP-0045 § 7.2.6 Password-Protected Rooms</a> 081 */ 082 public static final String MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM = "muc#roomconfig_passwordprotectedroom"; 083 084 /** 085 * The constant String {@value}. 086 */ 087 public static final String MUC_ROOMCONFIG_ROOMSECRET = "muc#roomconfig_roomsecret"; 088 089 /** 090 * The constant String {@value}. 091 */ 092 public static final String MUC_ROOMCONFIG_MODERATEDROOM = "muc#roomconfig_moderatedroom"; 093 094 /** 095 * The constant String {@value}. 096 */ 097 public static final String MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM = "muc#roomconfig_publicroom"; 098 099 /** 100 * The constant String {@value}. 101 */ 102 public static final String MUC_ROOMCONFIG_ROOMNAME = "muc#roomconfig_roomname"; 103 104 /** 105 * The constant String {@value}. 106 */ 107 public static final String MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING = "muc#roomconfig_enablelogging"; 108 109 /** 110 * The constant String {@value}. 111 */ 112 public static final String MUC_ROOMCONFIG_CHANGE_SUBJECT = "muc#roomconfig_changesubject"; 113 114 private final MultiUserChat multiUserChat; 115 private final FillableForm answerForm; 116 private final List<Jid> owners; 117 private final List<Jid> admins; 118 119 /** 120 * Create a new MUC config form manager. 121 * <p> 122 * Note that the answerForm needs to be filled out with the defaults. 123 * </p> 124 * 125 * @param multiUserChat the MUC for this configuration form. 126 * @throws InterruptedException if the calling thread was interrupted. 127 * @throws NotConnectedException if the XMPP connection is not connected. 128 * @throws XMPPErrorException if there was an XMPP error returned. 129 * @throws NoResponseException if there was no response from the remote entity. 130 */ 131 MucConfigFormManager(MultiUserChat multiUserChat) throws NoResponseException, 132 XMPPErrorException, NotConnectedException, InterruptedException { 133 this.multiUserChat = multiUserChat; 134 135 // Set the answer form 136 Form configForm = multiUserChat.getConfigurationForm(); 137 this.answerForm = configForm.getFillableForm(); 138 139 // Set the local variables according to the fields found in the answer form 140 FormField roomOwnersFormField = answerForm.getDataForm().getField(MUC_ROOMCONFIG_ROOMOWNERS); 141 if (roomOwnersFormField != null) { 142 // Set 'owners' to the currently configured owners 143 List<? extends CharSequence> ownerStrings = roomOwnersFormField.getValues(); 144 owners = new ArrayList<>(ownerStrings.size()); 145 JidUtil.jidsFrom(ownerStrings, owners, null); 146 } 147 else { 148 // roomowners not supported, this should barely be the case 149 owners = null; 150 } 151 152 FormField roomAdminsFormField = answerForm.getDataForm().getField(MUC_ROOMCONFIG_ROOMADMINS); 153 if (roomAdminsFormField != null) { 154 // Set 'admins' to the currently configured admins 155 List<? extends CharSequence> adminStrings = roomAdminsFormField.getValues(); 156 admins = new ArrayList<>(adminStrings.size()); 157 JidUtil.jidsFrom(adminStrings, admins, null); 158 } 159 else { 160 // roomadmins not supported, this should barely be the case 161 admins = null; 162 } 163 } 164 165 /** 166 * Check if the room supports room owners. 167 * @return <code>true</code> if supported, <code>false</code> if not. 168 * @see #MUC_ROOMCONFIG_ROOMOWNERS 169 */ 170 public boolean supportsRoomOwners() { 171 return owners != null; 172 } 173 174 /** 175 * Check if the room supports room admins. 176 * @return <code>true</code> if supported, <code>false</code> if not. 177 * @see #MUC_ROOMCONFIG_ROOMADMINS 178 */ 179 public boolean supportsRoomAdmins() { 180 return admins != null; 181 } 182 183 /** 184 * Set the owners of the room. 185 * 186 * @param newOwners a collection of JIDs to become the new owners of the room. 187 * @return a reference to this object. 188 * @throws MucConfigurationNotSupportedException if the MUC service does not support this option. 189 * @see #MUC_ROOMCONFIG_ROOMOWNERS 190 */ 191 public MucConfigFormManager setRoomOwners(Collection<? extends Jid> newOwners) throws MucConfigurationNotSupportedException { 192 if (!supportsRoomOwners()) { 193 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ROOMOWNERS); 194 } 195 owners.clear(); 196 owners.addAll(newOwners); 197 return this; 198 } 199 200 /** 201 * Set the admins of the room. 202 * 203 * @param newAdmins a collection of JIDs to become the new admins of the room. 204 * @return a reference to this object. 205 * @throws MucConfigurationNotSupportedException if the MUC service does not support this option. 206 * @see #MUC_ROOMCONFIG_ROOMADMINS 207 */ 208 public MucConfigFormManager setRoomAdmins(Collection<? extends Jid> newAdmins) throws MucConfigurationNotSupportedException { 209 if (!supportsRoomAdmins()) { 210 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ROOMADMINS); 211 } 212 admins.clear(); 213 admins.addAll(newAdmins); 214 return this; 215 } 216 217 /** 218 * Check if the room supports a members only configuration. 219 * 220 * @return <code>true</code> if supported, <code>false</code> if not. 221 */ 222 public boolean supportsMembersOnly() { 223 return answerForm.hasField(MUC_ROOMCONFIG_MEMBERSONLY); 224 } 225 226 /** 227 * Check if the room supports being moderated in the configuration. 228 * 229 * @return <code>true</code> if supported, <code>false</code> if not. 230 */ 231 public boolean supportsModeration() { 232 return answerForm.hasField(MUC_ROOMCONFIG_MODERATEDROOM); 233 } 234 235 /** 236 * Make the room for members only. 237 * 238 * @return a reference to this object. 239 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 240 */ 241 public MucConfigFormManager makeMembersOnly() throws MucConfigurationNotSupportedException { 242 return setMembersOnly(true); 243 } 244 245 /** 246 * Set if the room is members only. Rooms are not members only per default. 247 * 248 * @param isMembersOnly if the room should be members only. 249 * @return a reference to this object. 250 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 251 */ 252 public MucConfigFormManager setMembersOnly(boolean isMembersOnly) throws MucConfigurationNotSupportedException { 253 if (!supportsMembersOnly()) { 254 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MEMBERSONLY); 255 } 256 answerForm.setAnswer(MUC_ROOMCONFIG_MEMBERSONLY, isMembersOnly); 257 return this; 258 } 259 260 261 /** 262 * Make the room moderated. 263 * 264 * @return a reference to this object. 265 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 266 */ 267 public MucConfigFormManager makeModerated() throws MucConfigurationNotSupportedException { 268 return setModerated(true); 269 } 270 271 /** 272 * Set if the room is members only. Rooms are not members only per default. 273 * 274 * @param isModerated if the room should be moderated. 275 * @return a reference to this object. 276 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 277 */ 278 public MucConfigFormManager setModerated(boolean isModerated) throws MucConfigurationNotSupportedException { 279 if (!supportsModeration()) { 280 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MODERATEDROOM); 281 } 282 answerForm.setAnswer(MUC_ROOMCONFIG_MODERATEDROOM, isModerated); 283 return this; 284 } 285 286 287 /** 288 * Check if the room supports its visibility being controlled via configuration. 289 * 290 * @return <code>true</code> if supported, <code>false</code> if not. 291 */ 292 public boolean supportsPublicRoom() { 293 return answerForm.hasField(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM); 294 } 295 296 /** 297 * Make the room publicly searchable. 298 * 299 * @return a reference to this object. 300 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 301 */ 302 public MucConfigFormManager makePublic() throws MucConfigurationNotSupportedException { 303 return setPublic(true); 304 } 305 306 /** 307 * Make the room hidden (not publicly searchable). 308 * 309 * @return a reference to this object. 310 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 311 */ 312 public MucConfigFormManager makeHidden() throws MucConfigurationNotSupportedException { 313 return setPublic(false); 314 } 315 316 /** 317 * Set if the room is publicly searchable (i.e. visible via discovery requests to the MUC service). 318 * 319 * @param isPublic if the room should be publicly searchable. 320 * @return a reference to this object. 321 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 322 */ 323 public MucConfigFormManager setPublic(boolean isPublic) throws MucConfigurationNotSupportedException { 324 if (!supportsPublicRoom()) { 325 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM); 326 } 327 answerForm.setAnswer(MUC_ROOMCONFIG_PUBLICLYSEARCHABLEROOM, isPublic); 328 return this; 329 } 330 331 public boolean supportsRoomname() { 332 return answerForm.hasField(MUC_ROOMCONFIG_ROOMNAME); 333 } 334 335 public MucConfigFormManager setRoomName(String roomName) throws MucConfigurationNotSupportedException { 336 if (!supportsRoomname()) { 337 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ROOMNAME); 338 } 339 answerForm.setAnswer(MUC_ROOMCONFIG_ROOMNAME, roomName); 340 return this; 341 } 342 343 /** 344 * Check if the room supports password protection. 345 * 346 * @return <code>true</code> if supported, <code>false</code> if not. 347 */ 348 public boolean supportsPasswordProtected() { 349 return answerForm.hasField(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM); 350 } 351 352 /** 353 * Set a password and make the room password protected. Users will need to supply the password 354 * to join the room. 355 * 356 * @param password the password to set. 357 * @return a reference to this object. 358 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 359 */ 360 public MucConfigFormManager setAndEnablePassword(String password) 361 throws MucConfigurationNotSupportedException { 362 return setIsPasswordProtected(true).setRoomSecret(password); 363 } 364 365 /** 366 * Make the room password protected. 367 * 368 * @return a reference to this object. 369 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 370 */ 371 public MucConfigFormManager makePasswordProtected() throws MucConfigurationNotSupportedException { 372 return setIsPasswordProtected(true); 373 } 374 375 /** 376 * Set if this room is password protected. Rooms are by default not password protected. 377 * 378 * @param isPasswordProtected TODO javadoc me please 379 * @return a reference to this object. 380 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 381 */ 382 public MucConfigFormManager setIsPasswordProtected(boolean isPasswordProtected) 383 throws MucConfigurationNotSupportedException { 384 if (!supportsPasswordProtected()) { 385 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM); 386 } 387 answerForm.setAnswer(MUC_ROOMCONFIG_PASSWORDPROTECTEDROOM, isPasswordProtected); 388 return this; 389 } 390 391 public boolean supportsPublicLogging() { 392 return answerForm.hasField(MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING); 393 } 394 395 public MucConfigFormManager setPublicLogging(boolean enabled) throws MucConfigurationNotSupportedException { 396 if (!supportsPublicLogging()) { 397 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING); 398 } 399 answerForm.setAnswer(MUC_ROOMCONFIG_ENABLE_PUBLIC_LOGGING, enabled); 400 return this; 401 } 402 403 public MucConfigFormManager enablePublicLogging() throws MucConfigurationNotSupportedException { 404 return setPublicLogging(true); 405 } 406 407 public MucConfigFormManager disablPublicLogging() throws MucConfigurationNotSupportedException { 408 return setPublicLogging(false); 409 } 410 411 /** 412 * Set the room secret, aka the room password. If set and enabled, the password is required to 413 * join the room. Note that this does only set it by does not enable password protection. Use 414 * {@link #setAndEnablePassword(String)} to set a password and make the room protected. 415 * 416 * @param secret the secret/password. 417 * @return a reference to this object. 418 * @throws MucConfigurationNotSupportedException if the requested MUC configuration is not supported by the MUC service. 419 */ 420 public MucConfigFormManager setRoomSecret(String secret) 421 throws MucConfigurationNotSupportedException { 422 if (!answerForm.hasField(MUC_ROOMCONFIG_ROOMSECRET)) { 423 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_ROOMSECRET); 424 } 425 answerForm.setAnswer(MUC_ROOMCONFIG_ROOMSECRET, secret); 426 return this; 427 } 428 429 public boolean supportsChangeSubjectByOccupant() { 430 return answerForm.hasField(MUC_ROOMCONFIG_CHANGE_SUBJECT); 431 } 432 433 public boolean occupantsAreAllowedToChangeSubject() throws MucConfigurationNotSupportedException { 434 if (!supportsChangeSubjectByOccupant()) { 435 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_CHANGE_SUBJECT); 436 } 437 return answerForm.getField(MUC_ROOMCONFIG_CHANGE_SUBJECT).ifPossibleAsOrThrow(BooleanFormField.class).getValueAsBoolean(); 438 } 439 440 public MucConfigFormManager setChangeSubjectByOccupant(boolean enabled) throws MucConfigurationNotSupportedException { 441 if (!supportsChangeSubjectByOccupant()) { 442 throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_CHANGE_SUBJECT); 443 } 444 answerForm.setAnswer(MUC_ROOMCONFIG_CHANGE_SUBJECT, enabled); 445 return this; 446 } 447 448 public MucConfigFormManager allowOccupantsToChangeSubject() throws MucConfigurationNotSupportedException { 449 return setChangeSubjectByOccupant(true); 450 } 451 452 public MucConfigFormManager disallowOccupantsToChangeSubject() throws MucConfigurationNotSupportedException { 453 return setChangeSubjectByOccupant(false); 454 } 455 456 /** 457 * Submit the configuration as {@link FilledForm} to the room. 458 * 459 * @throws NoResponseException if there was no response from the room. 460 * @throws XMPPErrorException if there was an XMPP error returned. 461 * @throws NotConnectedException if the XMPP connection is not connected. 462 * @throws InterruptedException if the calling thread was interrupted. 463 */ 464 public void submitConfigurationForm() throws NoResponseException, XMPPErrorException, NotConnectedException, 465 InterruptedException { 466 if (owners != null) { 467 answerForm.setAnswer(MUC_ROOMCONFIG_ROOMOWNERS, JidUtil.toStringList(owners)); 468 } 469 if (admins != null) { 470 answerForm.setAnswer(MUC_ROOMCONFIG_ROOMADMINS, JidUtil.toStringList(admins)); 471 } 472 multiUserChat.sendConfigurationForm(answerForm); 473 } 474}