You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by dk...@apache.org on 2019/01/31 18:06:43 UTC

[sling-org-apache-sling-app-cms] branch master updated: Adding a service to auto-version pages

This is an automated email from the ASF dual-hosted git repository.

dklco pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-app-cms.git


The following commit(s) were added to refs/heads/master by this push:
     new fbf700c  Adding a service to auto-version pages
     new dfd3258  Merge branch 'master' of github.com:apache/sling-org-apache-sling-app-cms
fbf700c is described below

commit fbf700ce9dff15c7ec522253dc1605372f7accdf
Author: Dan Klco <dk...@apache.org>
AuthorDate: Thu Jan 31 13:06:24 2019 -0500

    Adding a service to auto-version pages
---
 .../main/java/org/apache/sling/cms/CMSUtils.java   |  16 +-
 builder/src/main/provisioning/cms.txt              |   7 +-
 .../internal/listeners/AutoVersioningListener.java | 179 +++++++++++++++++++++
 .../listeners/AutoVersioningListenerConfig.java    |  34 ++++
 .../apache/sling/cms/core/models/VersionInfo.java  |  22 ++-
 .../main/resources/OSGI-INF/l10n/bundle.properties |  15 ++
 ...eUserMapperImpl.amended-sling-cms-metadata.json |   6 -
 ...serMapperImpl.amended-sling-cms-versionmgr.json |   6 +
 8 files changed, 261 insertions(+), 24 deletions(-)

diff --git a/api/src/main/java/org/apache/sling/cms/CMSUtils.java b/api/src/main/java/org/apache/sling/cms/CMSUtils.java
index 639467c..5a3f18b 100644
--- a/api/src/main/java/org/apache/sling/cms/CMSUtils.java
+++ b/api/src/main/java/org/apache/sling/cms/CMSUtils.java
@@ -41,7 +41,7 @@ public class CMSUtils {
      * 
      * @param resources the collection of resources to adapt
      * @param type      the type to which to adapt the resources
-     * @param <T>         the type to which the resources are adapted
+     * @param           <T> the type to which the resources are adapted
      * @return the list of adapted classes
      */
     @NotNull
@@ -64,7 +64,7 @@ public class CMSUtils {
      * 
      * @param resources the array of resources to adapt
      * @param type      the type to which to adapt the resources
-     * @param <T>         the type to which the resources are adapted
+     * @param           <T> the type to which the resources are adapted
      * @return the list of adapted classes
      */
     @NotNull
@@ -100,11 +100,13 @@ public class CMSUtils {
      */
     @Nullable
     public static final Resource findPublishableParent(Resource resource) {
-        String type = resource.getValueMap().get(JcrConstants.JCR_PRIMARYTYPE, String.class);
-        if (ArrayUtils.contains(CMSConstants.PUBLISHABLE_TYPES, type)) {
-            return resource;
-        } else if (resource.getParent() != null) {
-            return findPublishableParent(resource.getParent());
+        if (resource != null) {
+            String type = resource.getValueMap().get(JcrConstants.JCR_PRIMARYTYPE, String.class);
+            if (ArrayUtils.contains(CMSConstants.PUBLISHABLE_TYPES, type)) {
+                return resource;
+            } else if (resource.getParent() != null) {
+                return findPublishableParent(resource.getParent());
+            }
         }
         return null;
     }
diff --git a/builder/src/main/provisioning/cms.txt b/builder/src/main/provisioning/cms.txt
index 4a12bfc..4b8f6d1 100644
--- a/builder/src/main/provisioning/cms.txt
+++ b/builder/src/main/provisioning/cms.txt
@@ -92,9 +92,8 @@
     set ACL for sling-ugc
         allow   jcr:write    on /etc/usergenerated
     end
-    create service user sling-cms-metadata
-    set ACL for sling-cms-metadata
-        allow   jcr:all    on /content
-        allow   jcr:all    on /static
+    create service user sling-cms-versionmgr
+    set ACL for sling-cms-versionmgr
+        allow   jcr:write,jcr:nodeTypeManagement,jcr:versionManagement    on /content
     end
     
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/listeners/AutoVersioningListener.java b/core/src/main/java/org/apache/sling/cms/core/internal/listeners/AutoVersioningListener.java
new file mode 100644
index 0000000..07f8017
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/listeners/AutoVersioningListener.java
@@ -0,0 +1,179 @@
+/*
+ * 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.cms.core.internal.listeners;
+
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.version.Version;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+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.api.resource.observation.ExternalResourceChangeListener;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.cms.CMSConstants;
+import org.apache.sling.cms.CMSUtils;
+import org.apache.sling.cms.Page;
+import org.apache.sling.cms.core.models.VersionInfo;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Modified;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.Designate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A Resource Change Listener will automatically version pages when a page is
+ * published / unpublished or an interval passes since the page was last saved.
+ */
+@Component(service = { AutoVersioningListener.class, ResourceChangeListener.class,
+        ExternalResourceChangeListener.class }, property = { ResourceChangeListener.CHANGES + "=ADDED",
+                ResourceChangeListener.CHANGES + "=CHANGED", ResourceChangeListener.CHANGES + "=REMOVED",
+                ResourceChangeListener.PATHS + "=/content" }, immediate = true)
+@Designate(ocd = AutoVersioningListenerConfig.class)
+public class AutoVersioningListener implements ResourceChangeListener, ExternalResourceChangeListener {
+
+    private static final Logger log = LoggerFactory.getLogger(AutoVersioningListener.class);
+    public static final String NN_METADATA = "metadata";
+
+    public static final String PN_X_PARSED_BY = "X-Parsed-By";
+
+    private int cutoff;
+    private boolean enabled;
+
+    @Activate
+    @Modified
+    public void activate(AutoVersioningListenerConfig config) {
+        this.cutoff = config.cutoff();
+        this.enabled = config.enabled();
+    }
+
+    @Reference
+    private ResourceResolverFactory factory;
+
+    @Override
+    public void onChange(List<ResourceChange> changes) {
+        if (enabled) {
+            log.trace("onChange");
+            Map<String, Object> serviceParams = new HashMap<>();
+            serviceParams.put(ResourceResolverFactory.SUBSERVICE, "sling-cms-versionmgr");
+            ResourceResolver serviceResolver = null;
+
+            try {
+                serviceResolver = factory.getServiceResourceResolver(serviceParams);
+                Set<String> pages = new HashSet<>();
+                for (ResourceChange rc : changes) {
+                    Resource changed = serviceResolver.getResource(rc.getPath());
+                    Resource page = CMSUtils.findPublishableParent(changed);
+                    if (changed != null && page != null && CMSConstants.NT_PAGE.equals(page.getResourceType())
+                            && !pages.contains(page.getPath())) {
+                        log.debug("Evaluating the changes to {}", page.getPath());
+                        String user = changed.getValueMap().get(JcrConstants.JCR_LASTMODIFIED + "By", String.class);
+                        if (pastLastModified(page)) {
+                            log.debug("Page {} needs to be versioned", page.getPath());
+                            versionPage(page, user);
+                        } else {
+                            log.trace("Page {} does not need to be versioned", page.getPath());
+                        }
+                        pages.add(page.getPath());
+                    } else {
+                        log.trace("Not versioning {}", page);
+                    }
+                }
+            } catch (LoginException e) {
+                log.error("Exception getting service user", e);
+            } finally {
+                if (serviceResolver != null) {
+                    serviceResolver.close();
+                }
+            }
+        }
+    }
+
+    private boolean pastLastModified(Resource pageResource) {
+        try {
+            Page page = pageResource.adaptTo(Page.class);
+            if (page != null && page.isPublished()) {
+                Version latestVersion = Optional.ofNullable(pageResource.adaptTo(VersionInfo.class))
+                        .map(VersionInfo::getVersions).map(vs -> !vs.isEmpty() ? vs.get(vs.size() - 1) : null)
+                        .orElse(null);
+                if (latestVersion != null) {
+                    if (latestVersion.hasProperty("jcr:frozenNode/jcr:content/jcr:lastModified")) {
+                        Calendar lMod = latestVersion.getProperty("jcr:frozenNode/jcr:content/jcr:lastModified")
+                                .getDate();
+                        Calendar co = Calendar.getInstance();
+                        co.add(Calendar.SECOND, cutoff * -1);
+                        if (lMod.before(co)) {
+                            log.trace("Page should be versioned");
+                            return true;
+                        } else {
+                            log.trace("Page should not be versioned");
+                        }
+                    } else {
+                        log.trace("No last modified found for version");
+                    }
+                } else {
+                    return true;
+                }
+            } else {
+                log.trace("Page is not published");
+            }
+        } catch (RepositoryException e) {
+            log.error("Failed to check if modified date outside cutoff", e);
+        }
+        return false;
+    }
+
+    private void versionPage(Resource page, String user) {
+        log.debug("Versioning page {}", page);
+        ModifiableValueMap mvm = null;
+        Resource content = page.getChild(JcrConstants.JCR_CONTENT);
+        if (content != null) {
+            mvm = content.adaptTo(ModifiableValueMap.class);
+        }
+        Node node = page.adaptTo(Node.class);
+
+        if (mvm != null && node != null) {
+            try {
+                mvm.put(JcrConstants.JCR_LASTMODIFIED + "By", user);
+                mvm.put(JcrConstants.JCR_LASTMODIFIED, Calendar.getInstance());
+                node.addMixin(JcrConstants.MIX_VERSIONABLE);
+                page.getResourceResolver().commit();
+                node.getSession().getWorkspace().getVersionManager().checkpoint(node.getPath());
+            } catch (PersistenceException e) {
+                log.warn("Failed to save modification date on page: " + page, e);
+            } catch (RepositoryException e) {
+                log.warn("Failed to version page: " + page, e);
+            }
+        }
+    }
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/internal/listeners/AutoVersioningListenerConfig.java b/core/src/main/java/org/apache/sling/cms/core/internal/listeners/AutoVersioningListenerConfig.java
new file mode 100644
index 0000000..1dbc76d
--- /dev/null
+++ b/core/src/main/java/org/apache/sling/cms/core/internal/listeners/AutoVersioningListenerConfig.java
@@ -0,0 +1,34 @@
+/*
+ * 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.cms.core.internal.listeners;
+
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+/**
+ * Configuration for the auto versioning listener
+ */
+@ObjectClassDefinition(name = "%cms.autoversioning.name", description = "%cms.autoversioning.description", localization = "OSGI-INF/l10n/bundle")
+public @interface AutoVersioningListenerConfig {
+    
+    @AttributeDefinition(name = "%cms.autoversioning.enabled.name", description = "%cms.autoversioning.enabled.description")
+    boolean enabled() default true;
+
+    @AttributeDefinition(name = "%cms.autoversioning.cutoff.name", description = "%cms.autoversioning.cutoff.description")
+    int cutoff() default 86400;
+
+}
diff --git a/core/src/main/java/org/apache/sling/cms/core/models/VersionInfo.java b/core/src/main/java/org/apache/sling/cms/core/models/VersionInfo.java
index bc394d4..29c767d 100644
--- a/core/src/main/java/org/apache/sling/cms/core/models/VersionInfo.java
+++ b/core/src/main/java/org/apache/sling/cms/core/models/VersionInfo.java
@@ -30,6 +30,8 @@ import javax.jcr.version.VersionIterator;
 import org.apache.jackrabbit.JcrConstants;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.models.annotations.Model;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Returns all of the versions for a Resource. At the moment only JCR nodes are
@@ -38,22 +40,28 @@ import org.apache.sling.models.annotations.Model;
 @Model(adaptables = { Resource.class })
 public class VersionInfo {
 
+    private static final Logger log = LoggerFactory.getLogger(VersionInfo.class);
+
     private Resource resource;
 
     public VersionInfo(Resource resource) {
         this.resource = resource;
     }
 
-    @SuppressWarnings("deprecation")
-    public List<Version> getVersions() throws RepositoryException {
+    public List<Version> getVersions() {
         List<Version> versions = new ArrayList<>();
         final Node node = resource.adaptTo(Node.class);
-        if (node != null && node.isNodeType(JcrConstants.MIX_VERSIONABLE)) {
-            final VersionHistory history = node.getVersionHistory();
-            for (final VersionIterator it = history.getAllVersions(); it.hasNext();) {
-                final Version v = it.nextVersion();
-                versions.add(v);
+        try {
+            if (node != null && node.isNodeType(JcrConstants.MIX_VERSIONABLE)) {
+                final VersionHistory history = node.getSession().getWorkspace().getVersionManager()
+                        .getVersionHistory(node.getPath());
+                for (final VersionIterator it = history.getAllVersions(); it.hasNext();) {
+                    final Version v = it.nextVersion();
+                    versions.add(v);
+                }
             }
+        } catch (RepositoryException e) {
+            log.warn("Exception retrieving version history for: " + resource, e);
         }
         return versions;
     }
diff --git a/core/src/main/resources/OSGI-INF/l10n/bundle.properties b/core/src/main/resources/OSGI-INF/l10n/bundle.properties
index c1d3250..8fd82d1 100644
--- a/core/src/main/resources/OSGI-INF/l10n/bundle.properties
+++ b/core/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -78,6 +78,21 @@ under the bucket. This will be sliced off the UUID, so if you had a UUID of 123
 a bucket of bob and a path depth of 1, this would yield a path like: bob/1/123. \
 This can be overridden by the path depth in the UGCBucketConfig.
 
+#Versioning Listener
+cms.autoversioning.name=Auto-Versioning
+cms.autoversioning.description=A Resource Change listener to automatically version pages \
+which are published, unpublished or updated having not been versioned in a defined time \
+range
+
+cms.autoversioning.enabled.name=Enabled
+cms.autoversioning.enabled.description=Enable or disable the AutoVersioning, if not \
+enabled the autoversioning will not occur
+
+cms.autoversioning.max.name=Cutoff
+cms.autoversioning.cutoff.description=The cutoff for determine if the page should be \
+auto-versioned. If the page has was last versioned before the cutoff, the page will be \
+versioned.
+
 # Path Suggestion Servlet
 pathsuggestionservlet.name=Apache Sling CMS Path Suggestion Servlet
 pathsuggestionservlet.description=A servlet for providing suggested paths based \
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-metadata.json b/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-metadata.json
deleted file mode 100644
index 458a8de..0000000
--- a/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-metadata.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-    "jcr:primaryType": "sling:OsgiConfig",
-    "user.mapping": [
-        "org.apache.sling.cms.core:sling-cms-metadata=sling-cms-metadata"
-    ]
-}
\ No newline at end of file
diff --git a/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-versionmgr.json b/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-versionmgr.json
new file mode 100644
index 0000000..7256259
--- /dev/null
+++ b/ui/src/main/resources/jcr_root/libs/sling-cms/install/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-sling-cms-versionmgr.json
@@ -0,0 +1,6 @@
+{
+    "jcr:primaryType": "sling:OsgiConfig",
+    "user.mapping": [
+        "org.apache.sling.cms.core:sling-cms-versionmgr=sling-cms-versionmgr"
+    ]
+}
\ No newline at end of file