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}