diff --git a/changes.xml b/changes.xml
index efa28d9..8ed9874 100644
--- a/changes.xml
+++ b/changes.xml
@@ -23,6 +23,14 @@
xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd">
+
+
+ New OSGi configuration "wcm.io Commons AEM Instance Type" required, see System configuration.
+ ]]>
+
+
ComponentPropertyResolverFactory: Mark return values of get methods as @NotNull.
diff --git a/pom.xml b/pom.xml
index f6d0713..2a68682 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,7 +31,7 @@
io.wcm
io.wcm.wcm.commons
- 1.8.2
+ 1.9.0
jar
WCM Commons
@@ -49,7 +49,7 @@
wcm/commons
- 2021-06-01T16:37:33Z
+ 2021-10-28T13:13:19Z
@@ -68,6 +68,12 @@
compile
+
+ org.osgi
+ org.osgi.service.cm
+ compile
+
+
org.junit.jupiter
junit-jupiter-api
diff --git a/src/main/java/io/wcm/wcm/commons/instancetype/InstanceTypeService.java b/src/main/java/io/wcm/wcm/commons/instancetype/InstanceTypeService.java
new file mode 100644
index 0000000..50fb475
--- /dev/null
+++ b/src/main/java/io/wcm/wcm/commons/instancetype/InstanceTypeService.java
@@ -0,0 +1,60 @@
+/*
+ * #%L
+ * wcm.io
+ * %%
+ * Copyright (C) 2021 wcm.io
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package io.wcm.wcm.commons.instancetype;
+
+import java.util.Set;
+
+import org.jetbrains.annotations.NotNull;
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * Allows to detect if the current AEM instance is an author or publish instance.
+ *
+ * This service does not rely in SlingSettingServices
which is deprecated and subject to removal in latest
+ * AEM versions.
+ * Instead, it is based on a OSGi configuration which should be configured properly for author and publish instances.
+ * If not configured, it "guesses" the instance type from other OSGi configs and writes a warning in the logs.
+ *
+ */
+@ProviderType
+public interface InstanceTypeService {
+
+ /**
+ * Returns true if code is running on AEM author instance.
+ * @return true if AEM author instance.
+ */
+ boolean isAuthor();
+
+ /**
+ * Returns true if code is running on AEM publish instance.
+ * @return true if AEM publish instance.
+ */
+ boolean isPublish();
+
+ /**
+ * Returns a set with a single "author" or "publish" run mode string.
+ * This method is provided for for compatibility with code relying on sets of run modes formerly provided by
+ * SlingSettingsService
.
+ * @return Set with a single element: Either "author" or "publish".
+ */
+ @NotNull
+ Set getRunModes();
+
+}
diff --git a/src/main/java/io/wcm/wcm/commons/instancetype/impl/InstanceTypeServiceImpl.java b/src/main/java/io/wcm/wcm/commons/instancetype/impl/InstanceTypeServiceImpl.java
new file mode 100644
index 0000000..730f6d1
--- /dev/null
+++ b/src/main/java/io/wcm/wcm/commons/instancetype/impl/InstanceTypeServiceImpl.java
@@ -0,0 +1,142 @@
+/*
+ * #%L
+ * wcm.io
+ * %%
+ * Copyright (C) 2021 wcm.io
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package io.wcm.wcm.commons.instancetype.impl;
+
+import static org.osgi.framework.Constants.SERVICE_PID;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.service.metatype.annotations.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.day.cq.wcm.api.WCMMode;
+
+import io.wcm.wcm.commons.instancetype.InstanceTypeService;
+import io.wcm.wcm.commons.util.RunMode;
+
+/**
+ * Implements {@link InstanceTypeService}.
+ */
+@Component(service = InstanceTypeService.class)
+@Designate(ocd = InstanceTypeServiceImpl.Config.class)
+public class InstanceTypeServiceImpl implements InstanceTypeService {
+
+ @ObjectClassDefinition(name = "wcm.io Commons AEM Instance Type",
+ description = "Configures if the current instance is an author or publish instance, and makes this information accessible for other services.")
+ @interface Config {
+
+ @AttributeDefinition(name = "Instance Type",
+ description = "Should be explicitely configured to 'author' or 'publish'. If not set, instance type will be guessed by heuristics from other OSGi configurations.",
+ options = {
+ @Option(value = RunMode.AUTHOR, label = "Author"),
+ @Option(value = RunMode.PUBLISH, label = "Publish"),
+ @Option(value = InstanceTypeServiceImpl.TYPE_AUTO, label = "Detect automatically (not recommended)")
+ })
+ String instance_type() default InstanceTypeServiceImpl.TYPE_AUTO;
+
+ }
+
+ static final String TYPE_AUTO = "auto";
+
+ static final String WCM_REQUEST_FILTER_PID = "com.day.cq.wcm.core.WCMRequestFilter";
+ static final String WCM_MODE_PROPERTY = "wcmfilter.mode";
+
+ private boolean isAuthor;
+ private Set runModes;
+
+ @Reference
+ private ConfigurationAdmin configAdmin;
+
+ private final Logger log = LoggerFactory.getLogger(InstanceTypeServiceImpl.class);
+
+ @Activate
+ private void activate(Config config) {
+ // detect instance type
+ String instanceType = config.instance_type();
+ if (StringUtils.equals(instanceType, RunMode.AUTHOR)) {
+ isAuthor = true;
+ }
+ else if (StringUtils.equals(instanceType, RunMode.PUBLISH)) {
+ isAuthor = false;
+ }
+ else {
+ // not configured or set to "auto" - rely on guessing author mode
+ isAuthor = detectAutorMode();
+
+ log.warn("Please provide a 'wcm.io Commons AEM Instance Type' configuration "
+ + "- falling back to guessing instance type from other configuration => {}.",
+ isAuthor ? RunMode.AUTHOR : RunMode.PUBLISH);
+ }
+
+ // set matching run mode set
+ if (isAuthor) {
+ runModes = Collections.singleton(RunMode.AUTHOR);
+ }
+ else {
+ runModes = Collections.singleton(RunMode.PUBLISH);
+ }
+ }
+
+ private boolean detectAutorMode() {
+ try {
+ Configuration[] configs = configAdmin.listConfigurations("(" + SERVICE_PID + "=" + WCM_REQUEST_FILTER_PID + ")");
+ if (configs != null && configs.length > 0) {
+ Object defaultWcmMode = configs[0].getProperties().get(WCM_MODE_PROPERTY);
+ if (defaultWcmMode instanceof String) {
+ return !StringUtils.equalsIgnoreCase(WCMMode.DISABLED.name(), (String)defaultWcmMode);
+ }
+ }
+ }
+ catch (IOException | InvalidSyntaxException ex) {
+ log.warn("Unable to read OSGi configuration: {}", WCM_REQUEST_FILTER_PID, ex);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isAuthor() {
+ return isAuthor;
+ }
+
+ @Override
+ public boolean isPublish() {
+ return !isAuthor;
+ }
+
+ @Override
+ public @NotNull Set getRunModes() {
+ return Collections.unmodifiableSet(runModes);
+ }
+
+}
diff --git a/src/main/java/io/wcm/wcm/commons/instancetype/package-info.java b/src/main/java/io/wcm/wcm/commons/instancetype/package-info.java
new file mode 100644
index 0000000..2bf7670
--- /dev/null
+++ b/src/main/java/io/wcm/wcm/commons/instancetype/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * #%L
+ * wcm.io
+ * %%
+ * Copyright (C) 2018 wcm.io
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+/**
+ * Detect instance type (author/publish) of AEM instance.
+ */
+@org.osgi.annotation.versioning.Version("1.0")
+package io.wcm.wcm.commons.instancetype;
diff --git a/src/main/java/io/wcm/wcm/commons/util/RunMode.java b/src/main/java/io/wcm/wcm/commons/util/RunMode.java
index 85efd72..2cb8f8b 100644
--- a/src/main/java/io/wcm/wcm/commons/util/RunMode.java
+++ b/src/main/java/io/wcm/wcm/commons/util/RunMode.java
@@ -29,6 +29,10 @@
/**
* Sling run mode utility methods.
+ *
+ * Most methods in this class are deprecated because SlingSettingsService
is deprecated in recent AEM
+ * versions. The OSGi service {@link io.wcm.wcm.commons.instancetype.InstanceTypeService} can be used instead.
+ *
*/
@ProviderType
public final class RunMode {
diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md
index 8608f6e..6398cd2 100644
--- a/src/site/markdown/configuration.md
+++ b/src/site/markdown/configuration.md
@@ -14,3 +14,23 @@ Create a principal-based service user mapping with an entry like this:
The built-in principal `sling-scripting` has read access to `/apps` and `/libs`.
This configuration is required **on both author and publish instances**.
+
+
+### AEM Instance Type configuration
+
+To detect whether code is currently running on an Author or Publish instance (without relying on the deprecated `SlingSettingsService`), it is required to provide an OSGi configuration "wcm.io Commons AEM Instance Type" for author and publish instances:
+
+```
+[configurations runModes=author]
+ io.wcm.wcm.commons.instancetype.impl.InstanceTypeServiceImpl
+ instance.type="author"
+
+[configurations runModes=publish]
+ io.wcm.wcm.commons.instancetype.impl.InstanceTypeServiceImpl
+ instance.type="publish"
+```
+
+If this configuration is not present, the [InstanceTypeService][InstanceTypeService] implementation tries to guess the instance type from other OSGi configurations, but this is only a fallback.
+
+
+[InstanceTypeService]: apidocs/io/wcm/wcm/commons/instancetype/InstanceTypeService.html
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index f19b47d..facca6e 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -26,7 +26,7 @@ The WCM Commons library contains:
|WCM Commons version |AEM version supported
|--------------------|----------------------
-|1.8.x or higher |AEM 6.4+
+|1.8.x or higher |AEM 6.4+, AEMaaCS
|1.6.x - 1.7.x |AEM 6.3+
|1.3.x - 1.5.x |AEM 6.2+
|1.0.x - 1.2.x |AEM 6.1+
diff --git a/src/test/java/io/wcm/wcm/commons/instancetype/impl/InstanceTypeServiceImplTest.java b/src/test/java/io/wcm/wcm/commons/instancetype/impl/InstanceTypeServiceImplTest.java
new file mode 100644
index 0000000..d6c8700
--- /dev/null
+++ b/src/test/java/io/wcm/wcm/commons/instancetype/impl/InstanceTypeServiceImplTest.java
@@ -0,0 +1,109 @@
+/*
+ * #%L
+ * wcm.io
+ * %%
+ * Copyright (C) 2021 wcm.io
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+package io.wcm.wcm.commons.instancetype.impl;
+
+import static io.wcm.wcm.commons.instancetype.impl.InstanceTypeServiceImpl.WCM_MODE_PROPERTY;
+import static io.wcm.wcm.commons.instancetype.impl.InstanceTypeServiceImpl.WCM_REQUEST_FILTER_PID;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+import com.day.cq.wcm.api.WCMMode;
+
+import io.wcm.testing.mock.aem.junit5.AemContext;
+import io.wcm.testing.mock.aem.junit5.AemContextExtension;
+import io.wcm.wcm.commons.instancetype.InstanceTypeService;
+import io.wcm.wcm.commons.util.RunMode;
+
+@ExtendWith(AemContextExtension.class)
+class InstanceTypeServiceImplTest {
+
+ final AemContext context = new AemContext();
+
+ @Test
+ void testWithoutAnyConfig() {
+ InstanceTypeService underTest = context.registerInjectActivateService(InstanceTypeServiceImpl.class);
+ assertPublish(underTest);
+ }
+
+ @Test
+ void testWithGuessingFromWcmFilterConfig_Author() throws IOException {
+ // prepare a WCM request filter config as it is expected to be found on author
+ createWcmRequestFilerConfig(WCMMode.EDIT);
+
+ InstanceTypeService underTest = context.registerInjectActivateService(InstanceTypeServiceImpl.class);
+ assertAuthor(underTest);
+ }
+
+ @Test
+ void testWithGuessingFromWcmFilterConfig_Publish() throws IOException {
+ // prepare a WCM request filter config as it is expected to be found on publish
+ createWcmRequestFilerConfig(WCMMode.DISABLED);
+
+ InstanceTypeService underTest = context.registerInjectActivateService(InstanceTypeServiceImpl.class);
+ assertPublish(underTest);
+ }
+
+ @Test
+ void testWithExplicitConfig_Author() {
+ InstanceTypeService underTest = context.registerInjectActivateService(InstanceTypeServiceImpl.class,
+ "instance.type", RunMode.AUTHOR);
+ assertAuthor(underTest);
+ }
+
+ @Test
+ void testWithExplicitConfig_Publish() {
+ InstanceTypeService underTest = context.registerInjectActivateService(InstanceTypeServiceImpl.class,
+ "instance.type", RunMode.PUBLISH);
+ assertPublish(underTest);
+ }
+
+ private void assertAuthor(InstanceTypeService underTest) {
+ assertTrue(underTest.isAuthor());
+ assertFalse(underTest.isPublish());
+ assertEquals(Collections.singleton(RunMode.AUTHOR), underTest.getRunModes());
+ }
+
+ private void assertPublish(InstanceTypeService underTest) {
+ assertFalse(underTest.isAuthor());
+ assertTrue(underTest.isPublish());
+ assertEquals(Collections.singleton(RunMode.PUBLISH), underTest.getRunModes());
+ }
+
+ @SuppressWarnings("null")
+ private void createWcmRequestFilerConfig(WCMMode wcmMode) throws IOException {
+ ConfigurationAdmin configAdmin = context.getService(ConfigurationAdmin.class);
+ Configuration config = configAdmin.getConfiguration(WCM_REQUEST_FILTER_PID);
+ Dictionary props = new Hashtable<>();
+ props.put(WCM_MODE_PROPERTY, wcmMode.name());
+ config.update(props);
+ }
+
+}