PagePersistenceStrategy.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2017 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.caconfig.extensions.persistence.impl;
import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT;
import static com.day.cq.commons.jcr.JcrConstants.NT_UNSTRUCTURED;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.commit;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.containsJcrContent;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.deleteChildrenNotInCollection;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.deletePageOrResource;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.ensureContainingPage;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.ensurePageIfNotContainingPage;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.getOrCreateResource;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.isItemModifiedOrNewlyAdded;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.replaceProperties;
import static io.wcm.caconfig.extensions.persistence.impl.PersistenceUtils.updatePageLastMod;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.caconfig.management.ConfigurationManagementSettings;
import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData;
import org.apache.sling.caconfig.spi.ConfigurationPersistData;
import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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 com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.cq.wcm.api.PageManagerFactory;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* AEM-specific persistence strategy that has higher precedence than the default strategy from Sling,
* but lower precedence that the persistence strategy that is part of AEM since version 6.3.
*
* <p>
* It supports reading configurations from cq:Page nodes in /conf, the configuration is read from the jcr:content child
* node. Unlike the persistence strategy in AEM 6.3 this also supports writing configuration to /conf.
* </p>
*/
@Component(service = ConfigurationPersistenceStrategy2.class)
@Designate(ocd = PagePersistenceStrategy.Config.class)
public class PagePersistenceStrategy implements ConfigurationPersistenceStrategy2 {
@ObjectClassDefinition(name = "wcm.io Context-Aware Configuration Persistence Strategy: AEM Page",
description = "Stores Context-Aware Configuration in AEM pages instead of simple resources.")
@interface Config {
@AttributeDefinition(name = "Enabled",
description = "Enable this persistence strategy.")
boolean enabled() default false;
@AttributeDefinition(name = "Resource type",
description = "Resource type for configuration pages.")
String resourceType();
@AttributeDefinition(name = "Service Ranking",
description = "Priority of persistence strategy (higher = higher priority).")
int service_ranking() default 1500;
}
private static final String DEFAULT_CONFIG_NODE_TYPE = NT_UNSTRUCTURED;
@Reference
private ConfigurationManagementSettings configurationManagementSettings;
@Reference
private PageManagerFactory pageManagerFactory;
private boolean enabled;
private String resourceType;
@Activate
void activate(Config value) {
this.enabled = value.enabled();
this.resourceType = value.resourceType();
}
@Override
public Resource getResource(@NotNull Resource resource) {
if (!enabled) {
return null;
}
if (containsJcrContent(resource.getPath())) {
return resource;
}
return resource.getChild(JCR_CONTENT);
}
@Override
public Resource getCollectionParentResource(@NotNull Resource resource) {
if (!enabled) {
return null;
}
return resource;
}
@Override
public Resource getCollectionItemResource(@NotNull Resource resource) {
return getResource(resource);
}
@Override
public String getResourcePath(@NotNull String resourcePath) {
if (!enabled) {
return null;
}
if (containsJcrContent(resourcePath)) {
return resourcePath;
}
return resourcePath + "/" + JCR_CONTENT;
}
@Override
public String getCollectionParentResourcePath(@NotNull String resourcePath) {
if (!enabled) {
return null;
}
return resourcePath;
}
@Override
public String getCollectionItemResourcePath(@NotNull String resourcePath) {
return getResourcePath(resourcePath);
}
@Override
public String getConfigName(@NotNull String configName, @Nullable String relatedConfigPath) {
if (!enabled) {
return null;
}
if (containsJcrContent(configName)) {
return configName;
}
return configName + "/" + JCR_CONTENT;
}
@Override
public String getCollectionParentConfigName(@NotNull String configName, @Nullable String relatedConfigPath) {
if (!enabled) {
return null;
}
return configName;
}
@Override
public String getCollectionItemConfigName(@NotNull String configName, @Nullable String relatedConfigPath) {
return getConfigName(configName, relatedConfigPath);
}
@Override
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public boolean persistConfiguration(@NotNull ResourceResolver resolver, @NotNull String configResourcePath, @NotNull ConfigurationPersistData data) {
if (!enabled) {
return false;
}
String path = getResourcePath(configResourcePath);
ensureContainingPage(resolver, path, resourceType, configurationManagementSettings);
getOrCreateResource(resolver, path, DEFAULT_CONFIG_NODE_TYPE, data.getProperties(), configurationManagementSettings);
PageManager pageManager = pageManagerFactory.getPageManager(resolver);
updatePageLastMod(resolver, pageManager, path);
commit(resolver, configResourcePath);
return true;
}
@Override
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
public boolean persistConfigurationCollection(@NotNull ResourceResolver resolver, @NotNull String configResourceCollectionParentPath,
@NotNull ConfigurationCollectionPersistData data) {
if (!enabled) {
return false;
}
PageManager pageManager = pageManagerFactory.getPageManager(resolver);
// create page for collection parent
String parentPath = getCollectionParentResourcePath(configResourceCollectionParentPath);
ensurePageIfNotContainingPage(resolver, parentPath, resourceType, configurationManagementSettings);
Resource configResourceParent = getOrCreateResource(resolver, parentPath, DEFAULT_CONFIG_NODE_TYPE, ValueMap.EMPTY, configurationManagementSettings);
updatePageLastMod(resolver, pageManager, parentPath);
// delete existing children no longer in the list
deleteChildrenNotInCollection(configResourceParent, data);
// create new or overwrite existing children
for (ConfigurationPersistData item : data.getItems()) {
String path = getCollectionItemResourcePath(parentPath + "/" + item.getCollectionItemName());
if (isItemModifiedOrNewlyAdded(resolver, path, item, configurationManagementSettings)) {
ensureContainingPage(resolver, path, resourceType, configurationManagementSettings);
getOrCreateResource(resolver, path, DEFAULT_CONFIG_NODE_TYPE, item.getProperties(), configurationManagementSettings);
updatePageLastMod(resolver, pageManager, path);
}
}
// if resource collection parent properties are given replace them as well
if (data.getProperties() != null) {
Page parentPage = configResourceParent.adaptTo(Page.class);
if (parentPage != null) {
replaceProperties(parentPage.getContentResource(), data.getProperties(), configurationManagementSettings);
}
else {
replaceProperties(configResourceParent, data.getProperties(), configurationManagementSettings);
}
}
commit(resolver, configResourceCollectionParentPath);
return true;
}
@Override
public boolean deleteConfiguration(@NotNull ResourceResolver resolver, @NotNull String configResourcePath) {
if (!enabled) {
return false;
}
Resource configResource = resolver.getResource(configResourcePath);
if (configResource != null) {
deletePageOrResource(configResource);
}
PageManager pageManager = pageManagerFactory.getPageManager(resolver);
updatePageLastMod(resolver, pageManager, configResourcePath);
commit(resolver, configResourcePath);
return true;
}
}