Skip to content

Commit fd3d684

Browse files
committed
Add method to call installation asynchronously
Trigger from JMX/Touch UI leveraging server-sent events Consume the messages in JQuery UI/Coral UI dialog This closes #801
1 parent 5778070 commit fd3d684

File tree

16 files changed

+634
-99
lines changed

16 files changed

+634
-99
lines changed

accesscontroltool-bundle/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@
142142
<artifactId>org.apache.sling.serviceusermapper</artifactId>
143143
<scope>provided</scope>
144144
</dependency>
145+
<dependency>
146+
<groupId>org.apache.sling</groupId>
147+
<artifactId>org.apache.sling.event</artifactId>
148+
<scope>provided</scope>
149+
</dependency>
145150
<dependency>
146151
<groupId>org.apache.sling</groupId>
147152
<artifactId>org.apache.sling.commons.scheduler</artifactId>

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/api/AcInstallationService.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package biz.netcentric.cq.tools.actool.api;
22

3+
import java.util.function.BiConsumer;
4+
import java.util.function.Consumer;
5+
36
/*-
47
* #%L
58
* Access Control Tool Bundle
@@ -18,6 +21,29 @@
1821
@ProviderType
1922
public interface AcInstallationService {
2023

24+
/**
25+
* Applies the configuration asynchronously.
26+
* Almost immediately returns a string with the ID of the started job.
27+
* Only one execution at a time is allowed.
28+
* @param options
29+
* @throws IllegalStateException if another asynchronous installation is currently running
30+
* @return the job id
31+
* @since 3.6.0
32+
* @see #attachLogListener(String, InstallationLogListener)
33+
*/
34+
public String applyAsynchronously(InstallationOptions options);
35+
36+
/** Attaches the log listener callback to an installation triggered previously via {@link #applyAsynchronously(InstallationOptions)}.
37+
*
38+
* @param jobId the job id returned by {@link #applyAsynchronously(InstallationOptions)}
39+
* @param listener the listener to attach, receives the level and the message per each log line
40+
* @param finishListener the listener to attach, receives a boolean status indicating success or failure once the installation was finished
41+
* @return {@code true} if the listeners were attached successfully (i.e. an installation with the given executionId was triggered before and is still ongoing), {@code false} otherwise
42+
* @since 3.6.0
43+
* @see #applyAsynchronously(InstallationOptions)
44+
*/
45+
public boolean attachLogListener(String jobId, BiConsumer<InstallationLogLevel, String> listener, Consumer<Boolean> finishListener);
46+
2147
/** Applies the full configuration as stored at the path configured at PID biz.netcentric.cq.tools.actool.impl.AcInstallationServiceImpl
2248
* to the repository.
2349
*
@@ -69,7 +95,7 @@ public interface AcInstallationService {
6995
@Deprecated
7096
public InstallationLog apply(String configurationRootPath, String[] restrictedToPaths, boolean skipIfConfigUnchanged);
7197

72-
/** Applies the configuration
98+
/** Applies the configuration.
7399
*
74100
* @param options the installation options which further specify the installation
75101
* @return the installation log
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package biz.netcentric.cq.tools.actool.api;
2+
3+
/*-
4+
* #%L
5+
* Access Control Tool Bundle
6+
* %%
7+
* Copyright (C) 2015 - 2025 Cognizant Netcentric
8+
* %%
9+
* All rights reserved. This program and the accompanying materials
10+
* are made available under the terms of the Eclipse Public License v1.0
11+
* which accompanies this distribution, and is available at
12+
* http://www.eclipse.org/legal/epl-v10.html
13+
* #L%
14+
*/
15+
16+
public enum InstallationLogLevel {
17+
ERROR,
18+
WARNING,
19+
INFO,
20+
TRACE
21+
}

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/api/InstallationOptions.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
import java.util.List;
17+
import java.util.Map;
1718
import java.util.Optional;
1819

1920
import org.osgi.annotation.versioning.ProviderType;
@@ -52,4 +53,14 @@ public interface InstallationOptions {
5253
*/
5354
public boolean shouldUpdateExistingExternalGroups();
5455

56+
/**
57+
* For asynchronous installations the options need to be persisted in the repository.
58+
* As regular Java serialization cannot be used with Sling Jobs (<a href="https://issues.apache.org/jira/browse/SLING-12745">SLING-12745</a>)
59+
* one has to rely on types compliant with default JCR.
60+
*
61+
* @return a new map with properties of types which are natively supported by the JCR resource provider
62+
* @see InstallationOptionsBuilder
63+
*/
64+
public Map<String, Object> getPersistableProperties();
65+
5566
}

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/api/InstallationOptionsBuilder.java

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package biz.netcentric.cq.tools.actool.api;
22

3+
import java.io.Serializable;
4+
35
/*-
46
* #%L
57
* Access Control Tool Bundle
@@ -17,6 +19,7 @@
1719
import java.util.Collection;
1820
import java.util.LinkedList;
1921
import java.util.List;
22+
import java.util.Map;
2023
import java.util.Objects;
2124
import java.util.Optional;
2225

@@ -25,28 +28,43 @@
2528
* @since 3.6.0 */
2629
public final class InstallationOptionsBuilder {
2730

28-
private Optional<String> configurationRootPath;
31+
private String configurationRootPath;
2932
private List<String> restrictedToPaths;
3033
private boolean skipIfConfigUnchanged;
3134
private boolean updateExistingExternalGroups;
3235

36+
/**
37+
* Creates a new builder with the given properties previously returned by {@link InstallationOptions#getPersistableProperties()}.
38+
*/
39+
public InstallationOptionsBuilder(Map<String, Object> properties) {
40+
this.configurationRootPath = (String)properties.get("configurationRootPath");
41+
String[] restrictedToPathsArray = (String[])properties.get("restrictedToPaths");
42+
if (restrictedToPathsArray != null) {
43+
this.restrictedToPaths = new LinkedList<>(Arrays.asList(restrictedToPathsArray));
44+
} else {
45+
this.restrictedToPaths = new LinkedList<>();
46+
}
47+
this.skipIfConfigUnchanged = (Boolean)properties.getOrDefault("skipIfConfigUnchanged", Boolean.FALSE);
48+
this.updateExistingExternalGroups = (Boolean)properties.getOrDefault("updateExistingExternalGroups", Boolean.FALSE);
49+
}
50+
3351
public InstallationOptionsBuilder() {
34-
this.configurationRootPath = Optional.empty();
52+
this.configurationRootPath = null;
3553
this.restrictedToPaths = new LinkedList<>();
3654
this.skipIfConfigUnchanged = false;
3755
this.updateExistingExternalGroups = false;
3856
}
3957

4058
public InstallationOptionsBuilder(InstallationOptions options) {
41-
this.configurationRootPath = options.getConfigurationRootPath();
59+
this.configurationRootPath = options.getConfigurationRootPath().orElse(null);
4260
this.restrictedToPaths = new LinkedList<>();
4361
this.restrictedToPaths.addAll(options.getRestrictedToPaths());
4462
this.skipIfConfigUnchanged = options.shouldSkipIfConfigUnchanged();
4563
this.updateExistingExternalGroups = options.shouldUpdateExistingExternalGroups();
4664
}
4765

4866
public InstallationOptionsBuilder withConfigurationRootPath(String configurationRootPath) {
49-
this.configurationRootPath = Optional.of(configurationRootPath);
67+
this.configurationRootPath = configurationRootPath;
5068
return this;
5169
}
5270

@@ -75,12 +93,12 @@ public InstallationOptions build() {
7593
}
7694

7795
private static final class InstallationOptionsImpl implements InstallationOptions {
78-
private final Optional<String> configurationRootPath;
96+
private final String configurationRootPath;
7997
private final List<String> restrictedToPaths;
8098
private final boolean skipIfConfigUnchanged;
8199
private final boolean updateExistingExternalGroups;
82100

83-
public InstallationOptionsImpl(InstallationOptionsBuilder builder) {
101+
InstallationOptionsImpl(InstallationOptionsBuilder builder) {
84102
this.configurationRootPath = builder.configurationRootPath;
85103
this.restrictedToPaths = builder.restrictedToPaths;
86104
this.skipIfConfigUnchanged = builder.skipIfConfigUnchanged;
@@ -89,7 +107,7 @@ public InstallationOptionsImpl(InstallationOptionsBuilder builder) {
89107

90108
@Override
91109
public Optional<String> getConfigurationRootPath() {
92-
return configurationRootPath;
110+
return Optional.ofNullable(configurationRootPath);
93111
}
94112

95113
@Override
@@ -132,5 +150,15 @@ public String toString() {
132150
+ ", skipIfConfigUnchanged=" + skipIfConfigUnchanged + ", updateExistingExternalGroups=" + updateExistingExternalGroups
133151
+ "]";
134152
}
153+
154+
@Override
155+
public Map<String, Object> getPersistableProperties() {
156+
Map<String, Object> properties = new java.util.HashMap<>();
157+
properties.put("configurationRootPath", configurationRootPath);
158+
properties.put("restrictedToPaths", restrictedToPaths.toArray(new String[0]));
159+
properties.put("skipIfConfigUnchanged", skipIfConfigUnchanged);
160+
properties.put("updateExistingExternalGroups", updateExistingExternalGroups);
161+
return properties;
162+
}
135163
}
136164
}

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/history/InstallationListener.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
import org.osgi.annotation.versioning.ConsumerType;
1818

1919
@ConsumerType
20+
/** Listener interface for installation events.
21+
*
22+
* @deprecated Unused. Will be removed in a future release.
23+
*/
24+
@Deprecated()
2025
public interface InstallationListener {
2126

2227
void onWarning(String message);

accesscontroltool-bundle/src/main/java/biz/netcentric/cq/tools/actool/history/impl/PersistableInstallationLogger.java

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
21
package biz.netcentric.cq.tools.actool.history.impl;
32

4-
import java.io.PrintWriter;
5-
import java.io.StringWriter;
6-
73
/*-
84
* #%L
95
* Access Control Tool Bundle
@@ -17,36 +13,45 @@
1713
* #L%
1814
*/
1915

16+
import java.io.Closeable;
17+
import java.io.IOException;
18+
import java.io.PrintWriter;
19+
import java.io.StringWriter;
2020
import java.sql.Timestamp;
2121
import java.text.DateFormat;
2222
import java.text.NumberFormat;
2323
import java.text.SimpleDateFormat;
24+
import java.util.Collection;
2425
import java.util.Date;
2526
import java.util.HashSet;
2627
import java.util.Locale;
2728
import java.util.Map;
2829
import java.util.Set;
2930
import java.util.TreeSet;
31+
import java.util.concurrent.CopyOnWriteArrayList;
32+
import java.util.function.BiConsumer;
33+
import java.util.function.Consumer;
3034

3135
import org.slf4j.Logger;
3236

3337
import biz.netcentric.cq.tools.actool.api.HistoryEntry;
3438
import biz.netcentric.cq.tools.actool.api.InstallationLog;
39+
import biz.netcentric.cq.tools.actool.api.InstallationLogLevel;
3540
import biz.netcentric.cq.tools.actool.api.InstallationResult;
3641
import biz.netcentric.cq.tools.actool.comparators.HistoryEntryComparator;
3742
import biz.netcentric.cq.tools.actool.history.InstallationLogger;
3843

39-
public class PersistableInstallationLogger implements InstallationLogger, InstallationLog, InstallationResult {
44+
public class PersistableInstallationLogger implements InstallationLogger, InstallationLog, InstallationResult, Closeable {
4045

4146
static final String EOL = "\n";
4247
protected static final String MSG_IDENTIFIER_ERROR = "ERROR: ";
4348
protected static final String MSG_IDENTIFIER_WARNING = "WARNING: ";
4449

45-
private Set<HistoryEntry> warnings = new HashSet<HistoryEntry>();
46-
private Set<HistoryEntry> messages = new HashSet<HistoryEntry>();
47-
private Set<HistoryEntry> errors = new HashSet<HistoryEntry>();
50+
private Set<HistoryEntry> warnings = new HashSet<>();
51+
private Set<HistoryEntry> messages = new HashSet<>();
52+
private Set<HistoryEntry> errors = new HashSet<>();
4853

49-
private Set<HistoryEntry> verboseMessages = new HashSet<HistoryEntry>();
54+
private Set<HistoryEntry> verboseMessages = new HashSet<>();
5055

5156
private boolean success = true;
5257
private final Date installationDate;
@@ -73,8 +78,13 @@ public class PersistableInstallationLogger implements InstallationLogger, Instal
7378
private int missingParentPathsForInitialContent = 0;
7479

7580
private DateFormat timestampFormat = new SimpleDateFormat("HH:mm:ss.SSS");
81+
82+
private final Collection<BiConsumer<InstallationLogLevel, String>> listeners;
83+
private final Collection<Consumer<Boolean>> finishListeners;
7684

7785
public PersistableInstallationLogger() {
86+
listeners = new CopyOnWriteArrayList<>();
87+
finishListeners = new CopyOnWriteArrayList<>();
7888
installationDate = new Date();
7989
}
8090

@@ -132,6 +142,7 @@ public void addWarning(Logger log, String warning) {
132142
protected void addWarning(String warning) {
133143
warnings.add(new HistoryEntry(msgIndex, new Timestamp(
134144
new Date().getTime()), MSG_IDENTIFIER_WARNING + warning));
145+
listeners.forEach(l -> l.accept(InstallationLogLevel.WARNING, warning));
135146
msgIndex++;
136147
}
137148

@@ -144,6 +155,7 @@ public void addMessage(Logger log, String message) {
144155
protected void addMessage(String message) {
145156
messages.add(new HistoryEntry(msgIndex, new Timestamp(new Date()
146157
.getTime()), " " + message));
158+
listeners.forEach(l -> l.accept(InstallationLogLevel.INFO, message));
147159
msgIndex++;
148160
}
149161

@@ -164,6 +176,7 @@ public void addError(final String error, Throwable e) {
164176
}
165177
errors.add(new HistoryEntry(msgIndex, new Timestamp(
166178
new Date().getTime()), MSG_IDENTIFIER_ERROR + fullErrorValue));
179+
listeners.forEach(l -> l.accept(InstallationLogLevel.ERROR, error));
167180
success = false;
168181
msgIndex++;
169182
if (e != null) {
@@ -183,6 +196,7 @@ public void addVerboseMessage(Logger log, String message) {
183196
protected void addVerboseMessage(String message) {
184197
verboseMessages.add(new HistoryEntry(msgIndex, new Timestamp(
185198
new Date().getTime()), " " + message));
199+
listeners.forEach(l -> l.accept(InstallationLogLevel.TRACE, message));
186200
msgIndex++;
187201
}
188202

@@ -370,4 +384,17 @@ public int getCountAuthorizablesMoved() {
370384
return countAuthorizablesMoved;
371385
}
372386

387+
@Override
388+
public void close() throws IOException {
389+
finishListeners.forEach(l -> l.accept(success));
390+
}
391+
392+
public void attachMessageListener(BiConsumer<InstallationLogLevel, String> messageListener) {
393+
listeners.add(messageListener);
394+
}
395+
396+
public void attachFinishListener(Consumer<Boolean> finishListener) {
397+
finishListeners.add(finishListener);
398+
}
399+
373400
}

0 commit comments

Comments
 (0)