Skip to content

Commit 71c8e2d

Browse files
authored
Merge pull request igniterealtime#669 from Flowdalic/flow
Flow
2 parents b952619 + 3901cc6 commit 71c8e2d

File tree

13 files changed

+412
-344
lines changed

13 files changed

+412
-344
lines changed

smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MucConfigFormManager.java

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2015-2024 Florian Schmaus
3+
* Copyright 2015-2025 Florian Schmaus
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -19,6 +19,9 @@
1919
import java.util.ArrayList;
2020
import java.util.Collection;
2121
import java.util.List;
22+
import java.util.logging.Level;
23+
import java.util.logging.Logger;
24+
import java.util.stream.Collectors;
2225

2326
import org.jivesoftware.smack.SmackException.NoResponseException;
2427
import org.jivesoftware.smack.SmackException.NotConnectedException;
@@ -53,6 +56,8 @@ public class MucConfigFormManager {
5356

5457
private static final String HASH_ROOMCONFIG = "#roomconfig";
5558

59+
private static final Logger LOGGER = Logger.getLogger(MucConfigFormManager.class.getName());
60+
5661
public static final String FORM_TYPE = MultiUserChatConstants.NAMESPACE + HASH_ROOMCONFIG;
5762

5863
/**
@@ -111,6 +116,10 @@ public class MucConfigFormManager {
111116
*/
112117
public static final String MUC_ROOMCONFIG_CHANGE_SUBJECT = "muc#roomconfig_changesubject";
113118

119+
public static final String MUC_ROOMCONFIG_WHOIS = "muc#roomconfig_whois";
120+
121+
public static final String MUC_ROOMCONFIG_MAXUSERS = "muc#roomconfig_maxusers";
122+
114123
private final MultiUserChat multiUserChat;
115124
private final FillableForm answerForm;
116125
private final List<Jid> owners;
@@ -453,6 +462,50 @@ public MucConfigFormManager disallowOccupantsToChangeSubject() throws MucConfigu
453462
return setChangeSubjectByOccupant(false);
454463
}
455464

465+
enum WhoisAllowedBy {
466+
moderators,
467+
anyone,
468+
}
469+
470+
public boolean supportsWhoisAllowedBy() {
471+
return answerForm.hasField(MUC_ROOMCONFIG_WHOIS);
472+
}
473+
474+
public MucConfigFormManager setWhoisAllowedBy(WhoisAllowedBy whoisAllowedBy)
475+
throws MucConfigurationNotSupportedException {
476+
if (!supportsWhoisAllowedBy()) {
477+
throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_WHOIS);
478+
}
479+
answerForm.setAnswer(MUC_ROOMCONFIG_WHOIS, whoisAllowedBy.name());
480+
return this;
481+
}
482+
483+
public boolean supportsMaxUsers() {
484+
return answerForm.hasField(MUC_ROOMCONFIG_MAXUSERS);
485+
}
486+
487+
public List<Integer> getPossibleMaxUsersValues() throws MucConfigurationNotSupportedException {
488+
if (!supportsMaxUsers()) {
489+
throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MAXUSERS);
490+
}
491+
return answerForm.getField(MUC_ROOMCONFIG_MAXUSERS)
492+
.getValuesAsString()
493+
.stream()
494+
.map(s -> Integer.valueOf(s))
495+
.collect(Collectors.toList());
496+
}
497+
498+
public MucConfigFormManager setMaxUsers(int maxUsers) throws MucConfigurationNotSupportedException {
499+
if (!supportsMaxUsers()) {
500+
throw new MucConfigurationNotSupportedException(MUC_ROOMCONFIG_MAXUSERS);
501+
}
502+
if (maxUsers < 1) {
503+
throw new IllegalArgumentException();
504+
}
505+
answerForm.setAnswer(MUC_ROOMCONFIG_MAXUSERS, maxUsers);
506+
return this;
507+
}
508+
456509
/**
457510
* Submit the configuration as {@link FilledForm} to the room.
458511
*
@@ -471,4 +524,32 @@ public void submitConfigurationForm() throws NoResponseException, XMPPErrorExcep
471524
}
472525
multiUserChat.sendConfigurationForm(answerForm);
473526
}
527+
528+
public void cancel() throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
529+
var cancelForm = FillableForm.newCancelForm();
530+
multiUserChat.sendConfigurationForm(cancelForm);
531+
}
532+
533+
public interface MucConfigApplier {
534+
void apply(MucConfigFormManager manager)
535+
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, MucConfigurationNotSupportedException;
536+
}
537+
538+
public MultiUserChat applyAndSubmit(MucConfigApplier applier)
539+
throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException, MucConfigurationNotSupportedException {
540+
try {
541+
applier.apply(this);
542+
submitConfigurationForm();
543+
} catch (XMPPErrorException | InterruptedException | MucConfigurationNotSupportedException e) {
544+
try {
545+
cancel();
546+
} catch (NoResponseException | XMPPErrorException | NotConnectedException
547+
| InterruptedException cancelException) {
548+
LOGGER.log(Level.SEVERE, "Exception while canceling MUC configuration for " + multiUserChat, e);
549+
}
550+
throw e;
551+
}
552+
553+
return multiUserChat;
554+
}
474555
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
*
3+
* Copyright 2025 Florian Schmaus
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.jivesoftware.smackx.muc;
18+
19+
public enum MucFeature {
20+
NonAnonymous("muc_nonanonymous"),
21+
SemiAnonymous("muc_semianonymous"),
22+
PasswordProtected("muc_passwordprotected"),
23+
StableIds(MultiUserChatConstants.STABLE_ID_FEATURE),
24+
;
25+
26+
MucFeature(String name) {
27+
this.name = name;
28+
}
29+
30+
private final String name;
31+
32+
public String getName() {
33+
return name;
34+
}
35+
}

smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2634,6 +2634,16 @@ public XMPPConnection getXmppConnection() {
26342634
return connection;
26352635
}
26362636

2637+
public boolean supports(MucFeature feature) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
2638+
return supports(feature.getName());
2639+
}
2640+
2641+
public boolean supports(String feature) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException {
2642+
var sdm = ServiceDiscoveryManager.getInstanceFor(connection);
2643+
var info = sdm.discoverInfo(myRoomJid);
2644+
return info.containsFeature(feature);
2645+
}
2646+
26372647
public boolean serviceSupportsStableIds() {
26382648
return DiscoverInfo.nullSafeContainsFeature(mucServiceDiscoInfo, MultiUserChatConstants.STABLE_ID_FEATURE);
26392649
}

smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FillableForm.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,9 @@ public SubmitForm getSubmitForm() {
295295
DataForm form = getDataFormToSubmit();
296296
return new SubmitForm(form);
297297
}
298+
299+
public static FillableForm newCancelForm() {
300+
var cancelDataForm = DataForm.builder(DataForm.Type.cancel).build();
301+
return new FillableForm(cancelDataForm);
302+
}
298303
}

smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/form/FilledForm.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public boolean hasField(String fieldName) {
7171
protected FormField getFieldOrThrow(String fieldName) {
7272
FormField formField = getField(fieldName);
7373
if (formField == null) {
74-
throw new IllegalArgumentException("No field named " + fieldName);
74+
throw new IllegalArgumentException("The data form has no field named " + fieldName);
7575
}
7676
return formField;
7777
}

smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/SmackIntegrationTestFramework.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,9 @@ private void runTests(Set<Class<? extends AbstractSmackIntTest>> classes)
411411
continue;
412412
}
413413

414+
if (sinttestDebugger != null) {
415+
sinttestDebugger.onTestClassConstruction(cons);
416+
}
414417
final AbstractSmackIntTest test;
415418
try {
416419
test = cons.newInstance(environment);

smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/debugger/SinttestDebugger.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
package org.igniterealtime.smack.inttest.debugger;
1818

1919
import java.io.IOException;
20+
import java.lang.reflect.Constructor;
2021
import java.time.ZonedDateTime;
2122

2223
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
2324

25+
import org.igniterealtime.smack.inttest.AbstractSmackIntTest;
2426
import org.igniterealtime.smack.inttest.ConnectionConfigurationBuilderApplier;
2527
import org.igniterealtime.smack.inttest.SmackIntegrationTestFramework.ConcreteTest;
2628
import org.igniterealtime.smack.inttest.SmackIntegrationTestFramework.TestRunResult;
@@ -29,6 +31,8 @@ public interface SinttestDebugger {
2931

3032
SmackDebuggerFactory getSmackDebuggerFactory();
3133

34+
default void onTestClassConstruction(Constructor<? extends AbstractSmackIntTest> cons) throws IOException { };
35+
3236
default void onTestStart(ConcreteTest test, ZonedDateTime testStart) throws IOException { };
3337

3438
default void onTestSuccess(ConcreteTest test, ZonedDateTime endTime) throws IOException { };

smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/debugger/StandardSinttestDebugger.java

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
*
3-
* Copyright 2024 Florian Schmaus
3+
* Copyright 2024-2025 Florian Schmaus
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121
import java.io.Writer;
22+
import java.lang.reflect.Constructor;
2223
import java.lang.reflect.Method;
2324
import java.nio.file.Files;
2425
import java.nio.file.Path;
@@ -32,6 +33,7 @@
3233
import org.jivesoftware.smack.debugger.SmackDebuggerFactory;
3334
import org.jivesoftware.smack.util.ExceptionUtil;
3435

36+
import org.igniterealtime.smack.inttest.AbstractSmackIntTest;
3537
import org.igniterealtime.smack.inttest.SmackIntegrationTestFramework.ConcreteTest;
3638
import org.igniterealtime.smack.inttest.SmackIntegrationTestFramework.TestRunResult;
3739
import org.igniterealtime.smack.inttest.util.ResultSyncPoint.ResultSyncPointTimeoutException;
@@ -112,12 +114,7 @@ public StandardSinttestDebugger(ZonedDateTime restRunStart, String testRunId, St
112114
Path outsideTestLogFile = this.basePath.resolve("outsideTestLog");
113115
Path testsFile = this.basePath.resolve("tests");
114116
try {
115-
if (!this.basePath.toFile().exists()) {
116-
boolean created = this.basePath.toFile().mkdirs();
117-
if (!created) {
118-
throw new IOException("Could not create directory " + this.basePath);
119-
}
120-
}
117+
mkdirs(this.basePath);
121118

122119
completeWriter = Files.newBufferedWriter(completeLogFile);
123120
outsideTestWriter = currentWriter = Files.newBufferedWriter(outsideTestLogFile);
@@ -152,7 +149,7 @@ private void logSink(String logMessage) {
152149
currentWriter.append(logMessage).append('\n');
153150
}
154151

155-
completeWriter.append(logMessage + "\n");
152+
completeWriter.append(logMessage).append('\n');
156153
} catch (IOException e) {
157154
LOGGER.log(Level.WARNING, e + " while appending log message", e);
158155
}
@@ -170,6 +167,24 @@ public SmackDebuggerFactory getSmackDebuggerFactory() {
170167
return c -> new StandardSinttestSmackDebugger(c);
171168
}
172169

170+
@Override
171+
public void onTestClassConstruction(Constructor<? extends AbstractSmackIntTest> constructor) throws IOException {
172+
if (basePath == null) {
173+
return;
174+
}
175+
176+
Path testClassDirectory = basePath.resolve(constructor.getDeclaringClass().getSimpleName());
177+
mkdirs(testClassDirectory);
178+
179+
Path logFile = testClassDirectory.resolve("log");
180+
Writer newWriter = Files.newBufferedWriter(logFile);
181+
synchronized (currentWriterLock) {
182+
currentWriter = newWriter;
183+
}
184+
185+
completeWriter.append("Constructing: " + constructor.getDeclaringClass() + "\n");
186+
};
187+
173188
@Override
174189
public void onTestStart(ConcreteTest test, ZonedDateTime startTime) throws IOException {
175190
if (basePath == null) {
@@ -186,12 +201,7 @@ public void onTestStart(ConcreteTest test, ZonedDateTime startTime) throws IOExc
186201
}
187202
currentTestMethodDirectory = testClassDirectory.resolve(testName.toString());
188203

189-
if (!currentTestMethodDirectory.toFile().exists()) {
190-
boolean created = currentTestMethodDirectory.toFile().mkdirs();
191-
if (!created) {
192-
throw new IOException("Could not create directory " + currentTestMethodDirectory);
193-
}
194-
}
204+
mkdirs(currentTestMethodDirectory);
195205

196206
Path logFile = currentTestMethodDirectory.resolve("log");
197207
Writer newWriter = Files.newBufferedWriter(logFile);
@@ -288,4 +298,14 @@ public void onSinttestFinished(TestRunResult testRunResult) throws IOException {
288298

289299
LOGGER.info("Test data file://" + basePath);
290300
}
301+
302+
private static void mkdirs(Path path) throws IOException {
303+
var dir = path.toFile();
304+
if (dir.exists())
305+
return;
306+
307+
boolean created = dir.mkdirs();
308+
if (!created)
309+
throw new IOException("Could not create directory " + path);
310+
}
291311
}

0 commit comments

Comments
 (0)