This is an automated email from the ASF dual-hosted git repository. andysch pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
commit 26ea9fec4ce93cd501ba2b18c1781550618ae742 Author: Andreas Schaefer <[email protected]> AuthorDate: Sat Mar 27 22:35:14 2021 -0700 Removed the Setup and made the DDR configurable through the source resources through primary type and property --- org.apache.sling.ddr/README.md | 32 ++ .../ddr/api/DeclarativeDynamicResourceSetup.java | 23 -- org.apache.sling.ddr/core/pom.xml | 3 + .../DeclarativeDynamicResourceManagerService.java | 179 +++++++--- .../DeclarativeDynamicResourceSetupService.java | 362 --------------------- ...g.jcr.repoinit.RepositoryInitializer-ddr.config | 2 - .../main/resources/SLING-CONTENT/nodetypes/ddr.cnd | 23 ++ org.apache.sling.ddr/sample/Readme.md | 37 +++ org.apache.sling.ddr/sample/pom.xml | 1 + .../SLING-CONTENT/apps/ddr-dynamic/components.json | 4 +- .../resources/SLING-CONTENT/apps/ddr-sample.json | 4 +- ...iveDynamicResourceManagerService-ddrsample.json | 4 - ...ativeDynamicResourceSetupService-ddrsample.json | 16 - ...repoinit.RepositoryInitializer-ddrsample.config | 38 --- .../apps/ddr-static/{container.json => text.json} | 0 .../{container/container.html => text/text.html} | 2 +- .../resources/SLING-CONTENT/conf/ddr-sample.json | 4 + .../SLING-CONTENT/conf/ddr-sample/settings.json | 23 ++ .../SLING-CONTENT/content/ddr-sample/text.json | 4 + 19 files changed, 262 insertions(+), 499 deletions(-) diff --git a/org.apache.sling.ddr/README.md b/org.apache.sling.ddr/README.md new file mode 100644 index 0000000..a1822eb --- /dev/null +++ b/org.apache.sling.ddr/README.md @@ -0,0 +1,32 @@ +# Apache Sling Declarative Dynamic Resources + +## Introduction + +In Peregrine CMS we were facing the fact that each tenant had their own set of components that were +just a redirection through their Sling Resource Super Type to a component provided by Peregrine. This +was the Tenant could maintain their components but Peregrine could add new components w/o affecting +or confusing the tenant. +In addition, if Peregrine wants to make new components available we face the issue of a read-only +JCR tree in /libs and /apps which would require a new deployment and restart of Peregrine. + +## Declarative Dynamic Resource + +Both of these issues can be solved by creating Synthetic Resources that appear in the desired target +resource but its actual content is provided from another node. +The Declarative Dynamic Resource (DDR) is composed of these components: + +* Declarative Dynamic Resource Manager: listens to DDRs becoming available and then creates the Synthetic Resources needed +* Declarative Dynamic Resource Provider: a Resource Provider for any DDR source properly configured +* Declarative Dynamic Resource: a Synthetic Resource that lives in a given node and points to its source for its properties +* Declarative Dynamic Resource Listener: an interface that informs the implementor about newly created DDRs + +The DDR is using its own Service User **ddr-serviceuser** that is used to read and handle DDRs. + +Any source folder needs to set the **jcr:primaryType** to **slingddr:Folder** and set the property +**slingddr:target** to the path of the target folder of its DDR. That path needs to be absolute and +point to an existing resource. Keep in mind that this path points to the parent of a DDR as each node +inside the source is creating a DDR with the path of the target as parent and the source name as +name of the DDR. Keep in mind that **no resource with that name** can existing in the target folder +otherwise it will be shadowed by the existing resource. + +Andreas Schaefer, 3/27/2021 \ No newline at end of file diff --git a/org.apache.sling.ddr/api/src/main/java/org/apache/sling/ddr/api/DeclarativeDynamicResourceSetup.java b/org.apache.sling.ddr/api/src/main/java/org/apache/sling/ddr/api/DeclarativeDynamicResourceSetup.java deleted file mode 100644 index dc44211..0000000 --- a/org.apache.sling.ddr/api/src/main/java/org/apache/sling/ddr/api/DeclarativeDynamicResourceSetup.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with this - * work for additional information regarding copyright ownership. The ASF - * licenses this file to You 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. - */ -package org.apache.sling.ddr.api; - -/** - * Marker Interface for the Setup Service for Dynamic Source Instances - */ -public interface DeclarativeDynamicResourceSetup { -} diff --git a/org.apache.sling.ddr/core/pom.xml b/org.apache.sling.ddr/core/pom.xml index 550c5d8..738ab6c 100644 --- a/org.apache.sling.ddr/core/pom.xml +++ b/org.apache.sling.ddr/core/pom.xml @@ -52,6 +52,9 @@ Private-Package, Include-Resource </_removeheaders> + <Sling-Nodetypes> + SLING-CONTENT/nodetypes/ddr.cnd + </Sling-Nodetypes> <Sling-Initial-Content> SLING-CONTENT/apps/ddr;path:=/apps/ddr;overwrite:=true;overwriteProperties:=true </Sling-Initial-Content> diff --git a/org.apache.sling.ddr/core/src/main/java/org/apache/sling/ddr/core/DeclarativeDynamicResourceManagerService.java b/org.apache.sling.ddr/core/src/main/java/org/apache/sling/ddr/core/DeclarativeDynamicResourceManagerService.java index 1f19d50..fb7b447 100644 --- a/org.apache.sling.ddr/core/src/main/java/org/apache/sling/ddr/core/DeclarativeDynamicResourceManagerService.java +++ b/org.apache.sling.ddr/core/src/main/java/org/apache/sling/ddr/core/DeclarativeDynamicResourceManagerService.java @@ -28,17 +28,20 @@ import org.apache.sling.serviceusermapping.ServiceUserMapped; import org.osgi.framework.BundleContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.ConfigurationPolicy; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicyOption; -import org.osgi.service.metatype.annotations.AttributeDefinition; -import org.osgi.service.metatype.annotations.Designate; -import org.osgi.service.metatype.annotations.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.UnsupportedRepositoryOperationException; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; +import javax.jcr.query.Query; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -49,23 +52,24 @@ import static org.apache.sling.ddr.api.Constants.JCR_PRIMARY_TYPE; import static org.apache.sling.ddr.api.Constants.REP_POLICY; @Component( - service= DeclarativeDynamicResourceManager.class, - immediate = true, - configurationPolicy = ConfigurationPolicy.REQUIRE + service = { DeclarativeDynamicResourceManager.class, EventListener.class }, + immediate = true ) -@Designate(ocd = DeclarativeDynamicResourceManagerService.Configuration.class, factory = true) public class DeclarativeDynamicResourceManagerService - implements DeclarativeDynamicResourceManager + implements DeclarativeDynamicResourceManager, EventListener { - @ObjectClassDefinition( - name = "Declarative Dynamic Component Resource Provider", - description = "Configuration of the Dynamic Component Resource Provider") - public @interface Configuration { - @AttributeDefinition( - name = "Dynamic Component Target Path", - description="Path to the Folder where the Dynamic Components will added to dynamically") - String dynamic_component_target_path(); - } + public static final int EVENT_TYPES = + Event.NODE_ADDED | + Event.NODE_REMOVED | + Event.NODE_MOVED | + Event.PROPERTY_ADDED | + Event.PROPERTY_CHANGED | + Event.PROPERTY_REMOVED; + + public static final String DDR_NODE_TYPE = "slingddr:Folder"; + public static final String DDR_TARGET_PROPERTY_NAME = "slingddr:target"; + public static final String[] NODE_TYPES = new String[] { DDR_NODE_TYPE }; + public static final String CONFIGURATION_ROOT_PATH = "/conf"; @Reference private ResourceResolverFactory resourceResolverFactory; @@ -81,14 +85,82 @@ public class DeclarativeDynamicResourceManagerService private Map<String, DeclarativeDynamicResourceProvider> registeredServices = new HashMap<>(); private BundleContext bundleContext; - private String dynamicTargetPath; +// private String dynamicTargetPath; @Activate - private void activate(BundleContext bundleContext, Configuration configuration) { - log.info("Activate Started, bundle context: '{}'", bundleContext); + private void activate(BundleContext bundleContext) { this.bundleContext = bundleContext; - dynamicTargetPath = configuration.dynamic_component_target_path(); - log.info("Dynamic Target Path: '{}'", dynamicTargetPath); + log.info("Activate Started, bundle context: '{}'", bundleContext); + try (ResourceResolver resourceResolver = resourceResolverFactory.getServiceResourceResolver( + new HashMap<String, Object>() {{ put(ResourceResolverFactory.SUBSERVICE, DYNAMIC_COMPONENTS_SERVICE_USER); }} + )) { + // Register an Event Listener to get informed when + Session session = resourceResolver.adaptTo(Session.class); + if (session != null) { + log.info("Register Event Listener on Path: '{}'", CONFIGURATION_ROOT_PATH); + session.getWorkspace().getObservationManager().addEventListener( + this, EVENT_TYPES, CONFIGURATION_ROOT_PATH, + true, null, null, true + ); + } else { + log.warn("Resource Resolver could not be adapted to Session"); + } + // To make sure we get all we will query all existing nodes + Resource root = resourceResolver.getResource(CONFIGURATION_ROOT_PATH); + log.info("Manual Check for Existing Nodes in: '{}'", CONFIGURATION_ROOT_PATH); + if(root != null) { + Iterator<Resource> i = resourceResolver.findResources( + "SELECT * FROM [slingddr:Folder]", + Query.JCR_SQL2 + ); + while(i.hasNext()) { + Resource item = i.next(); + log.info("Handle Found DDR Resource: '{}'", item); + handleDDRResource(item); + } + } + } catch (LoginException e) { + log.error("Failed to Activation Resource", e); + } catch (UnsupportedRepositoryOperationException e) { + log.error("Failed to Activation Resource", e); + } catch (RepositoryException e) { + log.error("Failed to Activation Resource", e); + } + } + + private void handleDDRResource(Resource ddrSourceResource) { + if(ddrSourceResource != null) { + ValueMap properties = ddrSourceResource.getValueMap(); + String ddrTargetPath = properties.get(DDR_TARGET_PROPERTY_NAME, String.class); + log.info("Found DDR Target Path: '{}'", ddrTargetPath); + if (ddrTargetPath != null) { + Resource ddrTargetResource = ddrSourceResource.getResourceResolver().getResource(ddrTargetPath); + if(ddrTargetResource != null) { + DeclarativeDynamicResourceProviderHandler service = new DeclarativeDynamicResourceProviderHandler(); + log.info("Dynamic Target: '{}', Dynamic Provider: '{}'", ddrSourceResource, ddrSourceResource); + long id = service.registerService(bundleContext.getBundle(), ddrTargetPath, ddrSourceResource.getPath(), resourceResolverFactory); + log.info("After Registering Tenant RP: service: '{}', id: '{}'", service, id); + registeredServices.put(ddrTargetResource.getPath(), service); + Iterator<Resource> i = ddrSourceResource.listChildren(); + while (i.hasNext()) { + Resource provided = i.next(); + String componentName = provided.getName(); + if (componentName.equals(REP_POLICY)) { + continue; + } + log.info("Provided Dynamic: '{}'", provided); + ValueMap childProperties = provided.getValueMap(); + String primaryType = childProperties.get(JCR_PRIMARY_TYPE, String.class); + log.info("Dynamic Child Source: '{}', Primary Type: '{}'", componentName, primaryType); + if (componentName != null && !componentName.isEmpty() && dynamicComponentFilterNotifier != null) { + dynamicComponentFilterNotifier.addDeclarativeDynamicResource( + ddrTargetPath + '/' + componentName, provided + ); + } + } + } + } + } } public void update(String dynamicProviderPath) { @@ -96,32 +168,7 @@ public class DeclarativeDynamicResourceManagerService new HashMap<String, Object>() {{ put(ResourceResolverFactory.SUBSERVICE, DYNAMIC_COMPONENTS_SERVICE_USER); }} )) { Resource dynamicProvider = resourceResolver.getResource(dynamicProviderPath); - Resource dynamicTarget = resourceResolver.getResource(dynamicTargetPath); - log.info("Dynamic Resource Provider: '{}', Target: '{}'", dynamicProvider, dynamicTarget); - if(dynamicProvider != null) { - DeclarativeDynamicResourceProviderHandler service = new DeclarativeDynamicResourceProviderHandler(); - log.info("Dynamic Target: '{}', Dynamic Provider: '{}'", dynamicTarget, dynamicProvider); - long id = service.registerService(bundleContext.getBundle(), dynamicTargetPath, dynamicProviderPath, resourceResolverFactory); - log.info("After Registering Tenant RP: service: '{}', id: '{}'", service, id); - registeredServices.put(dynamicTarget.getPath(), service); - Iterator<Resource> i = dynamicProvider.listChildren(); - while (i.hasNext()) { - Resource provided = i.next(); - String componentName = provided.getName(); - if (componentName.equals(REP_POLICY)) { - continue; - } - log.info("Provided Dynamic: '{}'", provided); - ValueMap childProperties = provided.getValueMap(); - String primaryType = childProperties.get(JCR_PRIMARY_TYPE, String.class); - log.info("Dynamic Child Source: '{}', Primary Type: '{}'", componentName, primaryType); - if (componentName != null && !componentName.isEmpty() && dynamicComponentFilterNotifier != null) { - dynamicComponentFilterNotifier.addDeclarativeDynamicResource( - dynamicTargetPath + '/' + componentName, provided - ); - } - } - } + handleDDRResource(dynamicProvider); } catch (LoginException e) { log.error("Was not able to obtain Service Resource Resolver", e); } @@ -135,5 +182,39 @@ public class DeclarativeDynamicResourceManagerService log.info("After UnRegistering Tenant RP, service: '{}'", service); } } + + @Override + public void onEvent(EventIterator events) { + try (ResourceResolver resourceResolver = resourceResolverFactory.getServiceResourceResolver( + new HashMap<String, Object>() {{ put(ResourceResolverFactory.SUBSERVICE, DYNAMIC_COMPONENTS_SERVICE_USER); }} + )) { + while (events.hasNext()) { + Event event = events.nextEvent(); + String path = event.getPath(); + switch (event.getType()) { + case Event.PROPERTY_ADDED: + case Event.PROPERTY_CHANGED: + int index = path.lastIndexOf('/'); + if(index > 0) { + path = path.substring(0, index -1); + } + case Event.NODE_ADDED: + Resource source = resourceResolver.getResource(path); + if(source != null) { + handleDDRResource(source); + } + break; + case Event.NODE_MOVED: + //AS TODO: Handle later + break; + case Event.PROPERTY_REMOVED: + //AS TODO: Handle later +// break; + } + } + } catch (LoginException | RepositoryException e) { + log.error("Failed to Handle Events", e); + } + } } diff --git a/org.apache.sling.ddr/core/src/main/java/org/apache/sling/ddr/core/setup/DeclarativeDynamicResourceSetupService.java b/org.apache.sling.ddr/core/src/main/java/org/apache/sling/ddr/core/setup/DeclarativeDynamicResourceSetupService.java deleted file mode 100644 index eb132de..0000000 --- a/org.apache.sling.ddr/core/src/main/java/org/apache/sling/ddr/core/setup/DeclarativeDynamicResourceSetupService.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with this - * work for additional information regarding copyright ownership. The ASF - * licenses this file to You 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. - */ -package org.apache.sling.ddr.core.setup; - -import org.apache.sling.api.resource.LoginException; -import org.apache.sling.api.resource.PersistenceException; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.api.resource.ResourceResolverFactory; -import org.apache.sling.ddr.api.DeclarativeDynamicResourceSetup; -import org.apache.sling.ddr.api.DeclarativeDynamicResourceManager; -import org.apache.sling.serviceusermapping.ServiceUserMapped; -import org.osgi.framework.BundleContext; -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.component.annotations.ReferencePolicyOption; -import org.osgi.service.metatype.annotations.AttributeDefinition; -import org.osgi.service.metatype.annotations.Designate; -import org.osgi.service.metatype.annotations.ObjectClassDefinition; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.UnsupportedRepositoryOperationException; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventIterator; -import javax.jcr.observation.EventListener; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import static org.apache.sling.ddr.api.Constants.CLOSING_MULTIPLE; -import static org.apache.sling.ddr.api.Constants.COMPONENT_GROUP; -import static org.apache.sling.ddr.api.Constants.DYNAMIC_COMPONENTS_SERVICE_USER; -import static org.apache.sling.ddr.api.Constants.DYNAMIC_COMPONENT_FOLDER_NAME; -import static org.apache.sling.ddr.api.Constants.EQUALS; -import static org.apache.sling.ddr.api.Constants.JCR_PRIMARY_TYPE; -import static org.apache.sling.ddr.api.Constants.JCR_TITLE; -import static org.apache.sling.ddr.api.Constants.MULTI_SEPARATOR; -import static org.apache.sling.ddr.api.Constants.NT_UNSTRUCTURED; -import static org.apache.sling.ddr.api.Constants.OPENING_MULTIPLE; -import static org.apache.sling.ddr.api.Constants.REFERENCE_PROPERTY_NAME; -import static org.apache.sling.ddr.api.Constants.REP_POLICY; -import static org.apache.sling.ddr.api.Constants.SLING_FOLDER; -import static org.apache.sling.ddr.api.Constants.SLING_RESOURCE_SUPER_TYPE_PROPERTY; -import static org.apache.sling.ddr.api.Constants.VERTICAL_LINE; - -@Component( - service= { DeclarativeDynamicResourceSetup.class, EventListener.class }, - immediate = true -) -@Designate(ocd = DeclarativeDynamicResourceSetupService.Configuration.class, factory = true) -public class DeclarativeDynamicResourceSetupService - implements DeclarativeDynamicResourceSetup, EventListener -{ - - @ObjectClassDefinition( - name = "Declarative Dynamic Resource Setup", - description = "Configuration of the Setup for Declarative Dynamic Resource Provider") - public @interface Configuration { - @AttributeDefinition( - name = "Dynamic Target Path", - description = "The Location where the DDR are made available") - String dynamic_component_target_path() default "/apps/wknd/components"; - @AttributeDefinition( - name = "Dynamic Source Root Path", - description = "The Location of the Source for the DDRs") - String dynamic_component_root_path() default "/conf/wknd/settings"; - @AttributeDefinition( - name = "Component Group of the Dynamic Components", - description = "Component Group Name") - String dynamic_component_group() default "WKND.Content"; - @AttributeDefinition( - name = "Primary Group", - description = "Component Primary Type") - String dynamic_component_primary_type() default "cq:Component"; - @AttributeDefinition( - name = "List of Dynamic Components", - description = "Dynamic Component Definitions in format: <name>=<title>:<super resource type>") - String[] dynamic_component_names() default "button=Button-default:core/wcm/components/button/v1/button"; - @AttributeDefinition( - name = "Additional Properties for Dynamic Components", - description = "Dynamic Component Additional Properties in format: <name>=<property name>|<property value>") - String[] dynamic_component_additional_properties() default ""; - @AttributeDefinition( - name = "References for Dynamic Components", - description = "Dynamic Component Reference in format: <name>=<path>") - String[] dynamic_component_refs() default ""; - } - - private final Logger log = LoggerFactory.getLogger(getClass()); - - @Reference - private ResourceResolverFactory resourceResolverFactory; - - @Reference - DeclarativeDynamicResourceManager dynamicComponentResourceManager; - - // Make sure that the Service User Mapping is available before obtaining the Service Resource Resolver - @Reference(policyOption= ReferencePolicyOption.GREEDY, target="(" + ServiceUserMapped.SUBSERVICENAME + EQUALS + DYNAMIC_COMPONENTS_SERVICE_USER + ")") - private ServiceUserMapped serviceUserMapped; - - private BundleContext bundleContext; - private String rootPath; - private String targetRootPath; - - @Activate - private void activate(BundleContext bundleContext, Configuration configuration) { - log.info("Activate Started, bundle context: '{}'", bundleContext); - this.bundleContext = bundleContext; - rootPath = configuration.dynamic_component_root_path(); - targetRootPath = configuration.dynamic_component_target_path(); - final String group = configuration.dynamic_component_group(); - final String primaryType = configuration.dynamic_component_primary_type(); - try (ResourceResolver resourceResolver = resourceResolverFactory.getServiceResourceResolver( - new HashMap<String, Object>() {{ put(ResourceResolverFactory.SUBSERVICE, DYNAMIC_COMPONENTS_SERVICE_USER); }} - )) { - Resource root = resourceResolver.getResource(rootPath); - if(root == null) { - throw new IllegalArgumentException("Root Path: '" + rootPath + "' does not exist"); - } - Resource target = root.getChild(DYNAMIC_COMPONENT_FOLDER_NAME); - log.info("Dynamic Folder looked up: '{}'", target); - if(target == null) { - target = resourceResolver.create(root, DYNAMIC_COMPONENT_FOLDER_NAME, new HashMap<String, Object>() {{ - put(JCR_PRIMARY_TYPE, SLING_FOLDER); - }} - ); - resourceResolver.commit(); - log.info("Dynamic Folder created: '{}'", target); - } else { - // Remove any existing children - Iterator<Resource> i = target.listChildren(); - while(i.hasNext()) { - Resource child = i.next(); - if(!child.getName().equals(REP_POLICY)) { - log.info("Delete Existing Resource: '{}'", child); - resourceResolver.delete(child); - } - } - resourceResolver.commit(); - } - Map<String, List<Property>> additionalProperties = new HashMap<>(); - for(String additionalProperty: configuration.dynamic_component_additional_properties()) { - Property component = new Property(additionalProperty, "Dynamic Additional Property"); - if(!component.isComponent()) { - throw new IllegalArgumentException("Addition Properties is not a component: '" + additionalProperty + "'"); - } - addItemToListMap(additionalProperties, component); - } - Map<String, List<Property>> dynamicRefs = new HashMap<>(); - for(String ref: configuration.dynamic_component_refs()) { - Property component = new Property(ref, "Dynamic Ref"); - if(!component.isComponent()) { - throw new IllegalArgumentException("Dynamic Ref is not a component: '" + ref + "'"); - } - addItemToListMap(dynamicRefs, component); - } - log.info("Dynamic Refs: '{}'", dynamicRefs); - for (String dynamicComponentName : configuration.dynamic_component_names()) { - final Property dynamicComponent = new Property(dynamicComponentName, "Dynamic Component"); - if(!dynamicComponent.isComponent()) { - throw new IllegalArgumentException("Dynamic Configuration Name is invalid (split on = does not yield 2 tokens): " + dynamicComponentName); - } - Map<String, Object> props = new HashMap<String, Object>() {{ - put(COMPONENT_GROUP, group); - put(JCR_PRIMARY_TYPE, primaryType); - put(JCR_TITLE, dynamicComponent.getName()); - put(SLING_RESOURCE_SUPER_TYPE_PROPERTY, dynamicComponent.getValue()); - }}; - List<Property> propertyList = additionalProperties.get(dynamicComponent.getComponent()); - log.info("Component: '{}', property list: '{}'", dynamicComponent.getComponent(), propertyList); - if(propertyList != null) { - for (Property property : propertyList) { - if(property.isSingle()) { - props.put(property.getName(), property.getValue()); - } else { - log.info("Add Property as Multi-Value: '{}'", property.getValues()); - props.put(property.getName(), property.getValues().toArray()); - } - } - } - log.info("Props for to be created Node: '{}'", props); - Resource newTarget = resourceResolver.create(target, dynamicComponent.getComponent(), props); - log.info("Newly Created Target: '{}'", newTarget); - // Add Dynamic Refs - List<Property> refs = dynamicRefs.get(dynamicComponent.getComponent()); - if(refs != null) { - for (final Property ref : refs) { - Map<String, Object> refProps = new HashMap<String, Object>() {{ - put(JCR_PRIMARY_TYPE, NT_UNSTRUCTURED); - put(JCR_TITLE, ref.getName()); - put(REFERENCE_PROPERTY_NAME, ref.getValue()); - }}; - Resource newRef = resourceResolver.create( - newTarget, ref.getName(), refProps - ); - } - } - } - resourceResolver.commit(); - Resource componentResource = resourceResolver.getResource(targetRootPath); - log.info("Target Root Path: '{}', resource: '{}'", targetRootPath, componentResource); - if(componentResource == null) { - Session session = resourceResolver.adaptTo(Session.class); - if (session != null) { - final int eventTypes = Event.NODE_ADDED; - final boolean isDeep = false; - final boolean noLocal = true; - session.getWorkspace().getObservationManager().addEventListener( - this, eventTypes, targetRootPath, - isDeep, null, null, noLocal - ); - } else { - log.warn("Resource Resolver could not be adapted to Session"); - } - } else { - targetAvailable(); - } - } catch (LoginException e) { - log.error("2. Cannot Access Resource Resolver", e); - } catch (PersistenceException e) { - log.error("Failed to create Dynamic Component", e); - } catch (UnsupportedRepositoryOperationException e) { - log.error("Could not register Event Listener", e); - } catch (RepositoryException e) { - log.error("Could not get Observation Manager to register Event Listener", e); - } - } - - @Override - public void onEvent(EventIterator events) { - targetAvailable(); - } - - private void targetAvailable() { - log.info("Target Available Called"); - try (ResourceResolver resourceResolver = resourceResolverFactory.getServiceResourceResolver( - new HashMap<String, Object>() {{ put(ResourceResolverFactory.SUBSERVICE, DYNAMIC_COMPONENTS_SERVICE_USER); }} - )) { - Resource root = resourceResolver.getResource(rootPath); - if (root == null) { - throw new IllegalArgumentException("Root Path: '" + rootPath + "' does not exist"); - } - Resource target = root.getChild(DYNAMIC_COMPONENT_FOLDER_NAME); - log.info("Update the Dynamic Component Resource Manager with Provider Path: '{}'", target); - dynamicComponentResourceManager.update(target.getPath()); - log.info("Update the Dynamic Component Resource Manager done"); - - // Now test the setup - Resource button1 = resourceResolver.getResource(targetRootPath + "/" + "button1"); - log.info("Dynamic Button 1: '{}'", button1); - Resource container1 = resourceResolver.getResource(targetRootPath + "/" + "container1"); - log.info("Dynamic Container 1: '{}'", container1); - if(container1 != null) { - Iterator<Resource> i = resourceResolver.listChildren(container1.getParent()); - int index = 0; - while (i.hasNext()) { - log.info("{}. Entry: '{}'", index++, i.next()); - } - } - } catch (LoginException e) { - log.error("2. Cannot Access Resource Resolver", e); - } - } - - private void addItemToListMap(Map<String, List<Property>> target, Property value) { - String componentName = value.getComponent(); - List<Property> propertyList = target.get(componentName); - if(propertyList == null) { - propertyList = new ArrayList<>(); - target.put(componentName, propertyList); - } - propertyList.add(value); - } - - private static class Property { - private String component; - private String name; - private List<String> values = new ArrayList<>(); - - public Property(String line, String messageTitle) { - String[] split = line.split(EQUALS); - if(split.length != 2) { - throw new IllegalArgumentException(messageTitle + " is invalid (split on '" + EQUALS + "' does not yield 2 tokens): " + line); - } - String tempName = split[0]; - String tempValue = split[1]; - int index = tempValue.indexOf(VERTICAL_LINE); - if( index > 0 && index < tempValue.length() - 1) { - String[] splitTemp = tempValue.split("\\" + VERTICAL_LINE); - this.component = tempName; - this.name = splitTemp[0]; - tempValue = splitTemp[1]; - if(tempValue.charAt(0) == OPENING_MULTIPLE && tempValue.charAt(tempValue.length() - 1) == CLOSING_MULTIPLE) { - tempValue = tempValue.substring(1, tempValue.length() - 1); - splitTemp = tempValue.split(MULTI_SEPARATOR); - values.addAll(Arrays.asList(splitTemp)); - } else { - values.add(tempValue); - } - } else { - this.name = tempName; - if(tempValue.charAt(0) == OPENING_MULTIPLE && tempValue.charAt(tempValue.length() - 1) == CLOSING_MULTIPLE) { - tempValue = tempValue.substring(1, tempValue.length() - 1); - String[] splitTemp = tempValue.split(MULTI_SEPARATOR); - values.addAll(Arrays.asList(splitTemp)); - } else { - values.add(tempValue); - } - } - } - - public boolean isComponent() { return component != null; } - public boolean isEmpty() { return values.isEmpty(); } - public boolean isSingle() { return values.size() == 1; } - - public String getComponent() { - return component; - } - - public String getName() { - return name; - } - - public String getValue() { - return values.isEmpty() ? null : values.get(0); - } - - public List<String> getValues() { - return new ArrayList<>(values); - } - - @Override - public String toString() { - return "Property{" + - "component-name='" + component + '\'' + - ", name='" + name + '\'' + - ", values=" + values + - '}'; - } - } -} \ No newline at end of file diff --git a/org.apache.sling.ddr/core/src/main/resources/SLING-CONTENT/apps/ddr/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-ddr.config b/org.apache.sling.ddr/core/src/main/resources/SLING-CONTENT/apps/ddr/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-ddr.config index 95066c1..39a45b1 100644 --- a/org.apache.sling.ddr/core/src/main/resources/SLING-CONTENT/apps/ddr/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-ddr.config +++ b/org.apache.sling.ddr/core/src/main/resources/SLING-CONTENT/apps/ddr/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-ddr.config @@ -15,7 +15,6 @@ # scripts=[\ " - # Create the Service User needed for Declarative Dynamic Resources create service user ddr-serviceuser with path /home/users/system/dynamic @@ -25,7 +24,6 @@ scripts=[\ set ACL for ddr-serviceuser allow jcr:read on / end - "\ ] diff --git a/org.apache.sling.ddr/core/src/main/resources/SLING-CONTENT/nodetypes/ddr.cnd b/org.apache.sling.ddr/core/src/main/resources/SLING-CONTENT/nodetypes/ddr.cnd new file mode 100644 index 0000000..7d151d5 --- /dev/null +++ b/org.apache.sling.ddr/core/src/main/resources/SLING-CONTENT/nodetypes/ddr.cnd @@ -0,0 +1,23 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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. +// + +<slingddr='http://sling.apache.org/jcr/ddr/1.0'> + +[slingddr:Folder] > sling:Folder + - slingddr:target (string) diff --git a/org.apache.sling.ddr/sample/Readme.md b/org.apache.sling.ddr/sample/Readme.md new file mode 100644 index 0000000..fb66989 --- /dev/null +++ b/org.apache.sling.ddr/sample/Readme.md @@ -0,0 +1,37 @@ +# Declarative Dynamic Resource Sample + +This is a sample project to showcase the use of DDRs in Sling. It requires a Sling 12 +instance to be installed on. + +## Design + +These are the components of the Sample: + +* **/apps/ddr-dynamic/components**: the DDR target folder which is empty +* **/apps/ddr-sample/config**: runmode configuration for this Sample +* **/apps/ddr-static**: the base component **button** and **text** +* **/conf/ddr-sample/settings/dynamic**: the source DDR components that points to the DDR target of **/apps/ddr-dynamic/components and contains an updated version of Button and Text +* **/content/ddr-sample**: content that show the usage of enhanced Button and Text + +## What Happens + +After the DDR Sample is installed the DDR Manager will discover that */conf/ddr-sample/settings/dynamic* +is a DDR source (by its primary type) and then read out the DDR target path. Then it will register that +folder as DDR Source / Target pair with the DDR Provider. Whenever now a user requests a resource from that +folder the DDR Provider will look it up and if: + +* it finds an existing resource in that folder -> return that resource +* it finds a resource in the DDR source with that name -> return a Synthetic Resource that contains the properties of the DDR source with the same name + +## Review + +After the installation open Sling in a browser and then go to **composum** browser. +Here go to **/apps/ddr-dynamic** and you will find two child nodes **button1** and **text1**. +These resources are fully dynamic mean that they would go away if the DDR core is disabled. +Try this by going to the System Console **Bundles** and stop the **org.apache.sling.ddr.core** bundle. +Refershing the **/apps/ddr-dynamic** folder in composum will not display any child resources. Restart +the bundle now again and make sure the child resources are there again. +Now we want to see these components but open the content on **/ddr-sample/button.html**. This will +show *Hello Button: * followed by the path of the resource. + +Andreas Schaefer: 3/27/2021 \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/pom.xml b/org.apache.sling.ddr/sample/pom.xml index 3999060..4578bb1 100644 --- a/org.apache.sling.ddr/sample/pom.xml +++ b/org.apache.sling.ddr/sample/pom.xml @@ -57,6 +57,7 @@ SLING-CONTENT/apps/ddr-sample;path:=/apps/ddr-sample;overwrite:=true;overwriteProperties:=true, SLING-CONTENT/apps/ddr-dynamic;path:=/apps/ddr-dynamic;overwrite:=true;overwriteProperties:=true, SLING-CONTENT/apps/ddr-static;path:=/apps/ddr-static;overwrite:=true;overwriteProperties:=true, + SLING-CONTENT/conf/ddr-sample;path:=/conf/ddr-sample;overwrite:=true;overwriteProperties:=true, SLING-CONTENT/content/ddr-sample;path:=/content/ddr-sample;overwrite:=true;overwriteProperties:=true </Sling-Initial-Content> </instructions> diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-dynamic/components.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-dynamic/components.json index 3c7b6bf..41c83f6 100644 --- a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-dynamic/components.json +++ b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-dynamic/components.json @@ -1,5 +1,5 @@ { + "jcr:description": "Root Folder of the Declarative Dynamic Resources (Target)", "jcr:primaryType": "sling:Folder", - "jcr:title": "Declarative Dynamic Resource Folder", - "jcr:description": "Root Folder of the Declarative Dynamic Resources (Target)" + "jcr:title": "Declarative Dynamic Resource Folder" } \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample.json index 152abde..4cee809 100644 --- a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample.json +++ b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample.json @@ -1,5 +1,5 @@ { "jcr:primaryType": "sling:Folder", - "jcr:title": "Declarative Dynamic Resource Root", - "jcr:description": "Declarative Dynamic Resource Apps Root folder" + "jcr:title": "Declarative Dynamic Sample Resource Root", + "jcr:description": "Declarative Dynamic Sample Resource Apps Root folder" } \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.ddr.core.DeclarativeDynamicResourceManagerService-ddrsample.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.ddr.core.DeclarativeDynamicResourceManagerService-ddrsample.json deleted file mode 100644 index 5d0b6c2..0000000 --- a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.ddr.core.DeclarativeDynamicResourceManagerService-ddrsample.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "jcr:primaryType": "sling:OsgiConfig", - "dynamic.component.target.path": "/apps/ddr-dynamic/components" -} diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.ddr.core.setup.DeclarativeDynamicResourceSetupService-ddrsample.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.ddr.core.setup.DeclarativeDynamicResourceSetupService-ddrsample.json deleted file mode 100644 index 5dab22e..0000000 --- a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.ddr.core.setup.DeclarativeDynamicResourceSetupService-ddrsample.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "jcr:primaryType": "sling:OsgiConfig", - "dynamic.component.root.path": "/conf/ddr-sample/settings", - "dynamic.component.target.path": "/apps/ddr-dynamic/components", - "dynamic.component.group": "Dynamic.Content", - "dynamic.component.primary.type": "sling:Folder", - "dynamic.component.names": [ - "button1=Button-1|ddr-static/button", - "container1=Container-1|ddr-static/container" - ], - "dynamic.component.additional.properties": [ - "button1=jcr:description|Dynamic Test Button", - "container1=cq:styleElements|{div;section;article;main;aside;header;footer}" - ], - "dynamic.component.refs": [] -} \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-ddrsample.config b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-ddrsample.config deleted file mode 100644 index 8f79a87..0000000 --- a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-sample/config/org.apache.sling.jcr.repoinit.RepositoryInitializer-ddrsample.config +++ /dev/null @@ -1,38 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with this -# work for additional information regarding copyright ownership. The ASF -# licenses this file to You 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. -# -scripts=[\ -" -# This Repoinit Script is here to make sure that during an upgrade the necessary users -# and permissions are available - -# Create the Folders for Dynamic Components - - create path /conf(sling:Folder) - create path /conf/ddr-sample(sling:Folder) - create path /conf/ddr-sample/settings(sling:Folder) - create path /conf/ddr-sample/settings/dynamic(sling:Folder) - - create service user ddr-serviceuser with path /home/users/system/dynamic - -# Set Permission for Dynamic Components Service User - - set ACL for ddr-serviceuser - allow jcr:all on /conf/ddr-sample/settings/dynamic - end -"\ -] - -references=[] \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/container.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/text.json similarity index 100% rename from org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/container.json rename to org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/text.json diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/container/container.html b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/text/text.html similarity index 97% rename from org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/container/container.html rename to org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/text/text.html index 24f58ce..de65ea5 100644 --- a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/container/container.html +++ b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/apps/ddr-static/text/text.html @@ -16,4 +16,4 @@ specific language governing permissions and limitations under the License. --> -<p>Container</p> \ No newline at end of file +<p>Hello Text</p> \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/conf/ddr-sample.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/conf/ddr-sample.json new file mode 100644 index 0000000..7e04c89 --- /dev/null +++ b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/conf/ddr-sample.json @@ -0,0 +1,4 @@ +{ + "jcr:primaryType": "sling:Folder", + "jcr:title": "DDR Sample Configuration Folder" +} \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/conf/ddr-sample/settings.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/conf/ddr-sample/settings.json new file mode 100644 index 0000000..d79a772 --- /dev/null +++ b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/conf/ddr-sample/settings.json @@ -0,0 +1,23 @@ +{ + "jcr:primaryType": "sling:Folder", + "dynamic": { + "jcr:mixinTypes": [ + "rep:AccessControllable" + ], + "jcr:primaryType": "slingddr:Folder", + "slingddr:target": "/apps/ddr-dynamic/components", + "button1": { + "componentGroup": "Dynamic.Content", + "jcr:description": "Dynamic Test Button", + "jcr:primaryType": "sling:Folder", + "jcr:title": "Button-1", + "sling:resourceSuperType": "ddr-static/button" + }, + "text1": { + "componentGroup": "Dynamic.Content", + "jcr:primaryType": "sling:Folder", + "jcr:title": "Text-1", + "sling:resourceSuperType": "ddr-static/text" + } + } +} \ No newline at end of file diff --git a/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/content/ddr-sample/text.json b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/content/ddr-sample/text.json new file mode 100644 index 0000000..8bd5484 --- /dev/null +++ b/org.apache.sling.ddr/sample/src/main/resources/SLING-CONTENT/content/ddr-sample/text.json @@ -0,0 +1,4 @@ +{ + "jcr:primaryType": "sling:Folder", + "sling:resourceSuperType": "ddr-dynamic/components/text1" +}
