From 9b67a3e2a027e0dff54b387c1066aea36b7574f3 Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 11:46:14 -0600 Subject: [PATCH 01/10] release. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 71619ae8..10c6572a 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.icfolson.aem.groovy.console aem-groovy-console jar - 11.1.0 + 11.1.1-SNAPSHOT AEM Groovy Console The AEM Groovy Console provides an interface for running Groovy scripts in the AEM container. Scripts can be From 86734b8c6b8b9308eabc4ee0ea8e624f9a71e895 Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 16:00:25 -0600 Subject: [PATCH 02/10] version. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 10c6572a..d5f083ba 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.icfolson.aem.groovy.console aem-groovy-console jar - 11.1.1-SNAPSHOT + 11.2.0-SNAPSHOT AEM Groovy Console The AEM Groovy Console provides an interface for running Groovy scripts in the AEM container. Scripts can be From b1a8854512c276cb196c7063c62db2de4066cc10 Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 16:52:18 -0600 Subject: [PATCH 03/10] added support for executing saved scripts. --- .../console/GroovyConsoleService.groovy | 4 ++ .../audit/impl/DefaultAuditService.groovy | 5 +- .../constants/GroovyConsoleConstants.groovy | 4 ++ .../impl/DefaultGroovyConsoleService.groovy | 48 +++++++++++++++---- .../console/servlets/ScriptPostServlet.groovy | 15 +++++- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/main/groovy/com/icfolson/aem/groovy/console/GroovyConsoleService.groovy b/src/main/groovy/com/icfolson/aem/groovy/console/GroovyConsoleService.groovy index 07deee8b..b4132a8c 100644 --- a/src/main/groovy/com/icfolson/aem/groovy/console/GroovyConsoleService.groovy +++ b/src/main/groovy/com/icfolson/aem/groovy/console/GroovyConsoleService.groovy @@ -8,5 +8,9 @@ interface GroovyConsoleService { RunScriptResponse runScript(SlingHttpServletRequest request) + RunScriptResponse runScript(SlingHttpServletRequest request, String scriptPath) + + List runScripts(SlingHttpServletRequest request, List scriptPaths) + SaveScriptResponse saveScript(SlingHttpServletRequest request) } \ No newline at end of file diff --git a/src/main/groovy/com/icfolson/aem/groovy/console/audit/impl/DefaultAuditService.groovy b/src/main/groovy/com/icfolson/aem/groovy/console/audit/impl/DefaultAuditService.groovy index ca6d12dc..1a12fd41 100644 --- a/src/main/groovy/com/icfolson/aem/groovy/console/audit/impl/DefaultAuditService.groovy +++ b/src/main/groovy/com/icfolson/aem/groovy/console/audit/impl/DefaultAuditService.groovy @@ -55,7 +55,10 @@ class DefaultAuditService implements AuditService { def auditRecordNode = addAuditRecordNode(session) auditRecordNode.setProperty(AuditRecord.PROPERTY_SCRIPT, response.script) - auditRecordNode.setProperty(AuditRecord.PROPERTY_DATA, response.data) + + if (response.data) { + auditRecordNode.setProperty(AuditRecord.PROPERTY_DATA, response.data) + } if (response.exceptionStackTrace) { auditRecordNode.setProperty(AuditRecord.PROPERTY_EXCEPTION_STACK_TRACE, response.exceptionStackTrace) diff --git a/src/main/groovy/com/icfolson/aem/groovy/console/constants/GroovyConsoleConstants.groovy b/src/main/groovy/com/icfolson/aem/groovy/console/constants/GroovyConsoleConstants.groovy index 6184c33f..211c3b90 100644 --- a/src/main/groovy/com/icfolson/aem/groovy/console/constants/GroovyConsoleConstants.groovy +++ b/src/main/groovy/com/icfolson/aem/groovy/console/constants/GroovyConsoleConstants.groovy @@ -8,6 +8,10 @@ class GroovyConsoleConstants { public static final String EXTENSION_GROOVY = ".groovy" + public static final String PARAMETER_SCRIPT_PATH = "scriptPath" + + public static final String PARAMETER_SCRIPT_PATHS = "scriptPaths" + public static final String PARAMETER_SCRIPT = "script" public static final String PARAMETER_USER_ID = "userId" diff --git a/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy b/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy index a0c36795..c4c35e4f 100755 --- a/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy +++ b/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy @@ -75,12 +75,23 @@ class DefaultGroovyConsoleService implements GroovyConsoleService { @Override RunScriptResponse runScript(SlingHttpServletRequest request) { - def scriptContent = request.getRequestParameter(PARAMETER_SCRIPT)?.getString(CharEncoding.UTF_8) - def data = request.getRequestParameter(PARAMETER_DATA)?.getString(CharEncoding.UTF_8) + runScript(request, null) + } - def stream = new ByteArrayOutputStream() + @Override + RunScriptResponse runScript(SlingHttpServletRequest request, String scriptPath) { def session = request.resourceResolver.adaptTo(Session) + def scriptContent + + if (scriptPath) { + scriptContent = loadScriptContent(session, scriptPath) + } else { + scriptContent = request.getRequestParameter(PARAMETER_SCRIPT)?.getString(CharEncoding.UTF_8) + } + + def data = request.getRequestParameter(PARAMETER_DATA)?.getString(CharEncoding.UTF_8) + def stream = new ByteArrayOutputStream() def response = null def binding = getBinding(extensionService.getBinding(request), data, stream) @@ -121,6 +132,13 @@ class DefaultGroovyConsoleService implements GroovyConsoleService { response } + @Override + List runScripts(SlingHttpServletRequest request, List scriptPaths) { + scriptPaths.collect { scriptPath -> + runScript(request, scriptPath) + } + } + @Override @Synchronized SaveScriptResponse saveScript(SlingHttpServletRequest request) { @@ -192,6 +210,18 @@ class DefaultGroovyConsoleService implements GroovyConsoleService { } } + private String loadScriptContent(Session session, String scriptPath) { + def binary = session.getNode(scriptPath) + .getNode(JcrConstants.JCR_CONTENT) + .getProperty(JcrConstants.JCR_DATA).binary + + def scriptContent = binary.stream.text + + binary.dispose() + + scriptContent + } + private void saveFile(Session session, Node folderNode, String script, String fileName, Date date, String mimeType) { def fileNode = folderNode.addNode(Text.escapeIllegalJcrChars(fileName), JcrConstants.NT_FILE) @@ -200,13 +230,11 @@ class DefaultGroovyConsoleService implements GroovyConsoleService { def stream = new ByteArrayInputStream(script.getBytes(CharEncoding.UTF_8)) def binary = session.valueFactory.createBinary(stream) - resourceNode.with { - setProperty(JcrConstants.JCR_MIMETYPE, mimeType) - setProperty(JcrConstants.JCR_ENCODING, CharEncoding.UTF_8) - setProperty(JcrConstants.JCR_DATA, binary) - setProperty(JcrConstants.JCR_LASTMODIFIED, date.time) - setProperty(JcrConstants.JCR_LAST_MODIFIED_BY, session.userID) - } + resourceNode.setProperty(JcrConstants.JCR_MIMETYPE, mimeType) + resourceNode.setProperty(JcrConstants.JCR_ENCODING, CharEncoding.UTF_8) + resourceNode.setProperty(JcrConstants.JCR_DATA, binary) + resourceNode.setProperty(JcrConstants.JCR_LASTMODIFIED, date.time) + resourceNode.setProperty(JcrConstants.JCR_LAST_MODIFIED_BY, session.userID) session.save() binary.dispose() diff --git a/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy b/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy index cd41bbf2..313257ea 100755 --- a/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy +++ b/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy @@ -2,6 +2,7 @@ package com.icfolson.aem.groovy.console.servlets import com.icfolson.aem.groovy.console.GroovyConsoleService import com.icfolson.aem.groovy.console.configuration.ConfigurationService +import com.icfolson.aem.groovy.console.constants.GroovyConsoleConstants import groovy.util.logging.Slf4j import org.apache.felix.scr.annotations.Reference import org.apache.felix.scr.annotations.sling.SlingServlet @@ -26,7 +27,19 @@ class ScriptPostServlet extends AbstractJsonResponseServlet { protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException { if (configurationService.hasPermission(request)) { - writeJsonResponse(response, consoleService.runScript(request)) + def scriptPaths = request.getParameterValues(GroovyConsoleConstants.PARAMETER_SCRIPT_PATHS) + + if (scriptPaths) { + writeJsonResponse(response, consoleService.runScripts(request, scriptPaths as List)) + } else { + def scriptPath = request.getParameter(GroovyConsoleConstants.PARAMETER_SCRIPT_PATH) + + if (scriptPath) { + writeJsonResponse(response, consoleService.runScript(request, scriptPath)) + } else { + writeJsonResponse(response, consoleService.runScript(request)) + } + } } else { response.status = SC_FORBIDDEN } From 3ce29006d66c0ca0f3c9ea63572b9a14a8bb3525 Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 16:57:26 -0600 Subject: [PATCH 04/10] updated error handling. --- .../groovy/console/impl/DefaultGroovyConsoleService.groovy | 6 +++++- .../aem/groovy/console/servlets/ScriptPostServlet.groovy | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy b/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy index c4c35e4f..45b45bf6 100755 --- a/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy +++ b/src/main/groovy/com/icfolson/aem/groovy/console/impl/DefaultGroovyConsoleService.groovy @@ -29,6 +29,7 @@ import javax.jcr.Node import javax.jcr.Session import java.util.concurrent.CopyOnWriteArrayList +import static com.google.common.base.Preconditions.checkNotNull import static com.icfolson.aem.groovy.console.constants.GroovyConsoleConstants.EXTENSION_GROOVY import static com.icfolson.aem.groovy.console.constants.GroovyConsoleConstants.PARAMETER_DATA import static com.icfolson.aem.groovy.console.constants.GroovyConsoleConstants.PATH_CONSOLE_ROOT @@ -90,6 +91,8 @@ class DefaultGroovyConsoleService implements GroovyConsoleService { scriptContent = request.getRequestParameter(PARAMETER_SCRIPT)?.getString(CharEncoding.UTF_8) } + checkNotNull(scriptContent, "Script content cannot be empty.") + def data = request.getRequestParameter(PARAMETER_DATA)?.getString(CharEncoding.UTF_8) def stream = new ByteArrayOutputStream() def response = null @@ -213,7 +216,8 @@ class DefaultGroovyConsoleService implements GroovyConsoleService { private String loadScriptContent(Session session, String scriptPath) { def binary = session.getNode(scriptPath) .getNode(JcrConstants.JCR_CONTENT) - .getProperty(JcrConstants.JCR_DATA).binary + .getProperty(JcrConstants.JCR_DATA) + .binary def scriptContent = binary.stream.text diff --git a/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy b/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy index 313257ea..aa7331d3 100755 --- a/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy +++ b/src/main/groovy/com/icfolson/aem/groovy/console/servlets/ScriptPostServlet.groovy @@ -30,11 +30,15 @@ class ScriptPostServlet extends AbstractJsonResponseServlet { def scriptPaths = request.getParameterValues(GroovyConsoleConstants.PARAMETER_SCRIPT_PATHS) if (scriptPaths) { + LOG.debug("running scripts for paths = {}", scriptPaths) + writeJsonResponse(response, consoleService.runScripts(request, scriptPaths as List)) } else { def scriptPath = request.getParameter(GroovyConsoleConstants.PARAMETER_SCRIPT_PATH) if (scriptPath) { + LOG.debug("running script for path = {}", scriptPath) + writeJsonResponse(response, consoleService.runScript(request, scriptPath)) } else { writeJsonResponse(response, consoleService.runScript(request)) From 2da8745bf6d4dbe486e02adb16a2efed1fac805f Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 16:59:10 -0600 Subject: [PATCH 05/10] version. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5f083ba..080d39aa 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ com.icfolson.aem.groovy.console aem-groovy-console jar - 11.2.0-SNAPSHOT + 11.2.0 AEM Groovy Console The AEM Groovy Console provides an interface for running Groovy scripts in the AEM container. Scripts can be From a3fb7c8bf074a7bc25c58fb35ef319d06e7dbbe2 Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 17:03:22 -0600 Subject: [PATCH 06/10] added osgi config details to readme. --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f2a52265..09b6713c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ The AEM Groovy Console provides an interface for running [Groovy](http://www.gro Groovy Console Version(s) | AEM Version ------------ | ------------- 11.x.x | 6.3 -10.x.x, 9.x.x | 6.2 +10.x.x, 9.x.x | 6.2 8.x.x | 6.1 7.x.x | 6.0 6.x.x, 5.x.x | 5.6 (CQ) @@ -46,6 +46,19 @@ If you are running AEM with a context path, set the Maven property `aem.context. mvn install -P local -Daem.context.path=/context +## Configuration + +Navigate to the OSGi console configuration page and edit "Groovy Console Configuration Service". + +Property | Description | Default Value +------------ | ------------- +Email Enabled? | Check to enable email notification on completion of script execution. | False +Email Recipients | Email addresses to receive notification. | [] +Allowed Groups | List of group names that are authorized to use the console. If empty, no authorization check is performed. | [] +Vanity Path Enabled? | Enables /groovyconsole vanity path. Apache Sling Resource Resolver Factory OSGi configuration must also be updated to allow vanity paths from /etc (resource.resolver.vanitypath.whitelist). | False +Audit Disabled? | Disables auditing of script execution history. | False +Display All Audit Records? | If enabled, all audit records (including records for other users) will be displayed in the console history. | False + ## Extensions Beginning in version 7.0.0, the Groovy Console provides extension hooks to further customize script execution. The console exposes an API containing three extension provider interfaces that can be implemented as OSGi services in any bundle deployed to an AEM instance. See the default extension providers in the `com.icfolson.aem.groovy.console.extension.impl` package for examples of how a bundle can implement these services to supply additional script bindings, metaclasses, and star imports. From 560d94ceb12c207750603deaae0a26a03d3978bc Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 17:04:14 -0600 Subject: [PATCH 07/10] readme formatting updates. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 09b6713c..0f0dc637 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ If you are running AEM with a context path, set the Maven property `aem.context. Navigate to the OSGi console configuration page and edit "Groovy Console Configuration Service". Property | Description | Default Value ------------- | ------------- +------------ | ------------- | ---------- Email Enabled? | Check to enable email notification on completion of script execution. | False Email Recipients | Email addresses to receive notification. | [] Allowed Groups | List of group names that are authorized to use the console. If empty, no authorization check is performed. | [] From d55d846decf873a82c070ac7579dcd41d84fd652 Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 17:09:44 -0600 Subject: [PATCH 08/10] readme formatting updates. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f0dc637..29016552 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ If you are running AEM with a context path, set the Maven property `aem.context. mvn install -P local -Daem.context.path=/context -## Configuration +## OSGi Configuration -Navigate to the OSGi console configuration page and edit "Groovy Console Configuration Service". +Navigate to the [OSGi console configuration page](http://localhost:4502/system/console/configMgr) and edit "Groovy Console Configuration Service". Property | Description | Default Value ------------ | ------------- | ---------- From e43019666f71dc7d6823ded9806eed785def02f2 Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 17:14:04 -0600 Subject: [PATCH 09/10] readme formatting updates. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 29016552..c245e130 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The AEM Groovy Console provides an interface for running [Groovy](http://www.gro ## Requirements -* AEM author instance running on localhost:4502 +* AEM author instance running on [localhost:4502](http://localhost:4502/) * [Maven](http://maven.apache.org/) 3.x ## Compatibility @@ -30,9 +30,9 @@ Groovy Console Version(s) | AEM Version 2. [Verify](http://localhost:4502/etc/groovyconsole.html) the installation. -Additional build profiles may be added in the project's pom.xml to support deployment to non-localhost AEM servers. +Additional build profiles may be added in the project's `pom.xml` to support deployment to non-localhost AEM servers. -AEM 6.0 no longer allows vanity paths for pages in /etc by default. To enable access to the Groovy Console from /groovyconsole as in previous versions, the Apache Sling Resource Resolver Factory OSGi configuration must be updated to allow vanity paths from /etc. The Groovy Console Configuration Service can then be updated to enable the vanity path if so desired. +AEM 6.0 no longer allows vanity paths for pages in `/etc` by default. To enable access to the Groovy Console from `/groovyconsole` as in previous versions, the **Apache Sling Resource Resolver Factory** OSGi configuration must be updated to allow vanity paths from `/etc`. The **Groovy Console Configuration Service** can then be updated to enable the vanity path if so desired. ## Excluding the Groovy OSGi Bundle @@ -48,14 +48,14 @@ If you are running AEM with a context path, set the Maven property `aem.context. ## OSGi Configuration -Navigate to the [OSGi console configuration page](http://localhost:4502/system/console/configMgr) and edit "Groovy Console Configuration Service". +Navigate to the [OSGi console configuration page](http://localhost:4502/system/console/configMgr) and select the **Groovy Console Configuration Service**. Property | Description | Default Value ------------ | ------------- | ---------- Email Enabled? | Check to enable email notification on completion of script execution. | False Email Recipients | Email addresses to receive notification. | [] Allowed Groups | List of group names that are authorized to use the console. If empty, no authorization check is performed. | [] -Vanity Path Enabled? | Enables /groovyconsole vanity path. Apache Sling Resource Resolver Factory OSGi configuration must also be updated to allow vanity paths from /etc (resource.resolver.vanitypath.whitelist). | False +Vanity Path Enabled? | Enables `/groovyconsole` vanity path. **Apache Sling Resource Resolver Factory** OSGi configuration must also be updated to allow vanity paths from `/etc`. | False Audit Disabled? | Disables auditing of script execution history. | False Display All Audit Records? | If enabled, all audit records (including records for other users) will be displayed in the console history. | False From 8bd0c40b4f074ef31bc19d4ee36f249b4842720c Mon Sep 17 00:00:00 2001 From: Mark Daugherty Date: Thu, 7 Dec 2017 17:27:48 -0600 Subject: [PATCH 10/10] added batch script execution details to README. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index c245e130..6737bf00 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,18 @@ Vanity Path Enabled? | Enables `/groovyconsole` vanity path. **Apache Sling Reso Audit Disabled? | Disables auditing of script execution history. | False Display All Audit Records? | If enabled, all audit records (including records for other users) will be displayed in the console history. | False +## Batch Script Execution + +Saved scripts can be remotely executed by sending a POST request to the console servlet with either the `scriptPath` or `scriptPaths` query parameter. + +### Single Script + + curl -d "scriptPath=/etc/groovyconsole/scripts/samples/JcrSearch.groovy" -X POST -u admin:admin http://localhost:4502/bin/groovyconsole/post.json + +### Multiple Scripts + + curl -d "scriptPaths=/etc/groovyconsole/scripts/samples/JcrSearch.groovy&scriptPaths=/etc/groovyconsole/scripts/samples/FulltextQuery.groovy" -X POST -u admin:admin http://localhost:4502/bin/groovyconsole/post.json + ## Extensions Beginning in version 7.0.0, the Groovy Console provides extension hooks to further customize script execution. The console exposes an API containing three extension provider interfaces that can be implemented as OSGi services in any bundle deployed to an AEM instance. See the default extension providers in the `com.icfolson.aem.groovy.console.extension.impl` package for examples of how a bundle can implement these services to supply additional script bindings, metaclasses, and star imports.