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); + } + +}