You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2019/04/24 12:31:29 UTC

[sling-org-apache-sling-committer-cli] branch feature/SLING-8337 updated (6ef8c72 -> 57239f1)

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

rombert pushed a change to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git.


    from 6ef8c72  SLING-8337 - Create sub-command to manage the Jira update when promoting a release
     new 50b88dd  SLING-8337 - Create sub-command to manage the Jira update when promoting a release
     new 57239f1  SLING-8337 - Create sub-command to manage the Jira update when promoting a release

The 44 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../sling/cli/impl/jira/ListVersionsJiraAction.java      | 16 ++++++++++++++++
 .../apache/sling/cli/impl/jira/VersionClientTest.java    |  2 ++
 2 files changed, 18 insertions(+)


[sling-org-apache-sling-committer-cli] 12/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit e8e5d79917e041ae173b261c5193d99948f531ac
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Mon Mar 25 12:33:02 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * improved release parsing
---
 .../apache/sling/cli/impl/jira/VersionFinder.java  |    4 +-
 .../cli/impl/release/PrepareVoteEmailCommand.java  |    6 +-
 .../org/apache/sling/cli/impl/release/Release.java |   97 +
 .../sling/cli/impl/release/ReleaseVersion.java     |   59 -
 .../sling/cli/impl/release/TallyVotesCommand.java  |    6 +-
 .../cli/impl/release/UpdateLocalSiteCommand.java   |    6 +-
 .../apache/sling/cli/impl/release/ReleaseTest.java |   68 +
 .../sling/cli/impl/release/ReleaseVersionTest.java |   35 -
 src/test/resources/jira_versions.txt               | 1993 ++++++++++++++++++++
 9 files changed, 2170 insertions(+), 104 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
index 5bf0406..7dbcbee 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
@@ -26,6 +26,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.release.Release;
 import org.osgi.service.component.annotations.Component;
 
 import com.google.gson.Gson;
@@ -57,8 +58,9 @@ public class VersionFinder {
                 Gson gson = new Gson();
                 Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
                 List<Version> versions = gson.fromJson(reader, collectionType);
+                Release filter = Release.fromString(versionName);
                 version = versions.stream()
-                    .filter(v -> v.getName().equals(versionName))
+                    .filter(v -> filter.equals(Release.fromString(v.getName())))
                     .findFirst()
                     .orElseThrow( () -> new IllegalArgumentException("No version found with name " + versionName));
             }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
index 5b8df75..9819107 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -73,11 +73,11 @@ public class PrepareVoteEmailCommand implements Command {
         try {
             int repoId = Integer.parseInt(target);
             StagingRepository repo = repoFinder.find(repoId);
-            ReleaseVersion releaseVersion = ReleaseVersion.fromRepositoryDescription(repo.getDescription());
-            Version version = versionFinder.find(releaseVersion.getName());
+            Release release = Release.fromString(repo.getDescription());
+            Version version = versionFinder.find(release.getName());
             
             String emailContents = EMAIL_TEMPLATE
-                    .replace("##RELEASE_NAME##", releaseVersion.getFullName())
+                    .replace("##RELEASE_NAME##", release.getFullName())
                     .replace("##RELEASE_ID##", String.valueOf(repoId))
                     .replace("##VERSION_ID##", String.valueOf(version.getId()))
                     .replace("##FIXED_ISSUES_COUNT##", String.valueOf(version.getIssuesFixedCount()));
diff --git a/src/main/java/org/apache/sling/cli/impl/release/Release.java b/src/main/java/org/apache/sling/cli/impl/release/Release.java
new file mode 100644
index 0000000..5086962
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/Release.java
@@ -0,0 +1,97 @@
+/*
+ * 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.cli.impl.release;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class Release {
+
+    /*
+        Group 1: Apache Sling and any trailing whitespace (optional)
+        Group 2: Release component
+        Group 3: Release version
+        Group 4: RC status (optional)
+     */
+    private static final Pattern RELEASE_PATTERN = Pattern.compile("^\\h*(Apache Sling\\h*)?([()a-zA-Z0-9\\-.\\h]+)\\h([0-9\\-.]+)" +
+            "\\h?(RC[0-9.]+)?\\h*$");
+    
+    public static Release fromString(String repositoryDescription) {
+        
+        Release rel = new Release();
+        Matcher matcher = RELEASE_PATTERN.matcher(repositoryDescription);
+        if (matcher.matches()) {
+            rel.component = matcher.group(2).trim();
+            rel.version = matcher.group(3);
+            rel.name = rel.component + " " + rel.version;
+            StringBuilder fullName = new StringBuilder();
+            if (matcher.group(1) != null) {
+                fullName.append(matcher.group(1).trim()).append(" ");
+            }
+            fullName.append(rel.name);
+            rel.fullName = fullName.toString();
+
+
+        }
+        return rel;
+    }
+    
+    private String fullName;
+    private String name;
+    private String component;
+    private String version;
+
+    private Release() {
+        
+    }
+    
+    public String getFullName() {
+        return fullName;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+    public String getVersion() {
+        return version;
+    }
+
+    public String getComponent() {
+        return component;
+    }
+
+    @Override
+    public int hashCode() {
+        return fullName.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Release)) {
+            return false;
+        }
+        Release other = (Release) obj;
+        return Objects.equals(name, other.name);
+    }
+
+    @Override
+    public String toString() {
+        return fullName;
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java b/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
deleted file mode 100644
index 0f0ef96..0000000
--- a/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
+++ /dev/null
@@ -1,59 +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.cli.impl.release;
-
-public final class ReleaseVersion {
-    
-    public static ReleaseVersion fromRepositoryDescription(String repositoryDescription) {
-        
-        ReleaseVersion rel = new ReleaseVersion();
-        
-        rel.fullName = repositoryDescription
-            .replaceAll(" RC[0-9]*$", ""); // 'release candidate' suffix
-        rel.name = rel.fullName
-            .replace("Apache Sling ", ""); // Apache Sling prefix
-        rel.version = rel.fullName.substring(rel.fullName.lastIndexOf(' ') + 1);
-        rel.component = rel.name.substring(0, rel.name.lastIndexOf(' '));
-        
-        return rel;
-    }
-    
-    private String fullName;
-    private String name;
-    private String component;
-    private String version;
-
-    private ReleaseVersion() {
-        
-    }
-    
-    public String getFullName() {
-        return fullName;
-    }
-    
-    public String getName() {
-        return name;
-    }
-    
-    public String getVersion() {
-        return version;
-    }
-
-    public String getComponent() {
-        return component;
-    }
-}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 86742bb..6046405 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -64,8 +64,8 @@ public class TallyVotesCommand implements Command {
         try {
             
             StagingRepository repository = repoFinder.find(Integer.parseInt(target));
-            ReleaseVersion releaseVersion = ReleaseVersion.fromRepositoryDescription(repository.getDescription()); 
-            EmailThread voteThread = voteThreadFinder.findVoteThread(releaseVersion.getFullName());
+            Release release = Release.fromString(repository.getDescription());
+            EmailThread voteThread = voteThreadFinder.findVoteThread(release.getFullName());
 
             // TODO - validate which voters are binding and list them separately in the email
             String bindingVoters = voteThread.getEmails().stream()
@@ -74,7 +74,7 @@ public class TallyVotesCommand implements Command {
                 .collect(Collectors.joining(", "));
             
             String email = EMAIL_TEMPLATE
-                .replace("##RELEASE_NAME##", releaseVersion.getFullName())
+                .replace("##RELEASE_NAME##", release.getFullName())
                 .replace("##BINDING_VOTERS##", bindingVoters);
             
             logger.info(email);
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
index 613afe0..4bf4530 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
@@ -58,14 +58,14 @@ public class UpdateLocalSiteCommand implements Command {
             try ( Git git = Git.open(new File(GIT_CHECKOUT)) ) {
                 
                 StagingRepository repository = repoFinder.find(Integer.parseInt(target));
-                ReleaseVersion releaseVersion = ReleaseVersion.fromRepositoryDescription(repository.getDescription());
+                Release release = Release.fromString(repository.getDescription());
                 
                 JBakeContentUpdater updater = new JBakeContentUpdater();
         
                 Path templatePath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "templates", "downloads.tpl");
                 Path releasesPath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "content", "releases.md");
-                updater.updateDownloads(templatePath, releaseVersion.getComponent(), releaseVersion.getVersion());
-                updater.updateReleases(releasesPath, releaseVersion.getComponent(), releaseVersion.getVersion(), LocalDateTime.now());
+                updater.updateDownloads(templatePath, release.getComponent(), release.getVersion());
+                updater.updateReleases(releasesPath, release.getComponent(), release.getVersion(), LocalDateTime.now());
         
                 git.diff()
                     .setOutputStream(System.out)
diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
new file mode 100644
index 0000000..182191a
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.cli.impl.release;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class ReleaseTest {
+
+    @Test
+    public void fromRepositoryDescription() {
+        
+        Release rel1 = Release.fromString("Apache Sling Resource Merger 1.3.10 RC1");
+        Release rel2 = Release.fromString("   Apache Sling Resource Merger    1.3.10   ");
+
+        assertEquals("Resource Merger 1.3.10", rel1.getName());
+        assertEquals("Apache Sling Resource Merger 1.3.10", rel1.getFullName());
+        assertEquals("1.3.10", rel1.getVersion());
+        assertEquals("Resource Merger", rel1.getComponent());
+
+        assertEquals(rel1, rel2);
+    }
+
+    @Test
+    public void testReleaseParsingWithJIRAInfo() throws URISyntaxException, IOException {
+        BufferedReader reader = new BufferedReader(new FileReader(new File(getClass().getResource("/jira_versions.txt").toURI())));
+        reader.lines().forEach(line -> {
+            if (!line.startsWith("#") && !"".equals(line)) {
+                Release jiraRelease = Release.fromString(line);
+                String releaseFullName = jiraRelease.getFullName();
+                if (releaseFullName == null) {
+                    fail("Failed to parse JIRA version: " + line);
+                }
+                int indexComponent = line.indexOf(jiraRelease.getComponent());
+                int indexVersion = line.indexOf(jiraRelease.getVersion());
+                assertTrue(indexComponent >= 0 && indexVersion > indexComponent);
+            }
+        });
+        reader.close();
+    }
+
+
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
deleted file mode 100644
index fc63a5f..0000000
--- a/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
+++ /dev/null
@@ -1,35 +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.cli.impl.release;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-
-public class ReleaseVersionTest {
-
-    @Test
-    public void fromRepositoryDescription() {
-        
-        ReleaseVersion rel = ReleaseVersion.fromRepositoryDescription("Apache Sling Resource Merger 1.3.10 RC1");
-        
-        assertEquals("Resource Merger 1.3.10", rel.getName());
-        assertEquals("Apache Sling Resource Merger 1.3.10", rel.getFullName());
-        assertEquals("1.3.10", rel.getVersion());
-        assertEquals("Resource Merger", rel.getComponent());
-    }
-}
diff --git a/src/test/resources/jira_versions.txt b/src/test/resources/jira_versions.txt
new file mode 100644
index 0000000..7786c55
--- /dev/null
+++ b/src/test/resources/jira_versions.txt
@@ -0,0 +1,1993 @@
+# 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.
+
+ Commons Mime 2.0.4
+ Commons Testing 2.0.4
+ Extensions Web Console Branding 1.0.0
+ JCR Classloader 2.0.4
+ JCR Jackrabbit Access Manager 2.0.4
+ JCR Jackrabbit Client 2.0.4
+ Launchpad Base 2.2.0
+ Maven JSPC Plugin 2.0.4
+ Scripting JavaScript 2.0.4
+ Servlets Resolver 2.0.6
+ i18n 2.2.0
+API 2.0.2
+API 2.0.4
+API 2.0.6
+API 2.0.8
+API 2.1.0
+API 2.11.0
+API 2.12.0
+API 2.14.0
+API 2.14.2
+API 2.15.0
+API 2.16.0
+API 2.16.2
+API 2.16.4
+API 2.18.0
+API 2.18.2
+API 2.18.4
+API 2.2.0
+API 2.2.2
+API 2.2.4
+API 2.20.0
+API 2.20.2
+API 2.3.0
+API 2.4.0
+API 2.4.2
+API 2.5.0
+API 2.6.0
+API 2.7.0
+API 2.8.0
+API 2.9.0
+Adapter 2.0.10
+Adapter 2.0.12
+Adapter 2.0.14
+Adapter 2.0.16
+Adapter 2.0.2
+Adapter 2.0.4
+Adapter 2.0.6
+Adapter 2.0.8
+Adapter 2.1.0
+Adapter 2.1.10
+Adapter 2.1.2
+Adapter 2.1.4
+Adapter 2.1.6
+Adapter 2.1.8
+Adapter Annotations 1.0.0
+Adapter Annotations 1.0.2
+Apache Sling Capabilities 0.1.0
+Apache Sling Capabilities 0.2.0
+Apache Sling Capabilities 0.4.0
+Apache Sling Capabilities JCR 0.1.0
+Apache Sling Capabilities JCR 0.2.0
+Apache Sling Capabilities JCR 0.4.0
+Apache Sling Server Setup Tools 1.0.0
+Apache Sling Server Setup Tools 1.0.1
+Apache Sling Testing Clients 1.0.0
+Apache Sling Testing Clients 1.0.1
+Apache Sling Testing Clients 1.1.0
+Apache Sling Testing Clients 1.1.12
+Apache Sling Testing Clients 1.1.4
+Apache Sling Testing Clients 1.2.0
+Apache Sling Testing Clients 1.2.2
+Apache Sling Testing Rules 1.0.0
+Apache Sling Testing Rules 1.0.1
+Apache Sling Testing Rules 1.0.10
+Apache Sling Testing Rules 1.0.6
+Apache Sling Testing Rules 1.0.8
+App CMS 0.10.0
+App CMS 0.11.0
+App CMS 0.11.2
+Archetype Parent 1
+Archetype Parent 4
+Archetype Parent 5
+Archetype Parent 6
+Auth Core 1.0.0
+Auth Core 1.0.2
+Auth Core 1.0.4
+Auth Core 1.0.6
+Auth Core 1.1.0
+Auth Core 1.1.2
+Auth Core 1.1.4
+Auth Core 1.1.6
+Auth Core 1.1.8
+Auth Core 1.2.0
+Auth Core 1.3.0
+Auth Core 1.3.10
+Auth Core 1.3.12
+Auth Core 1.3.14
+Auth Core 1.3.16
+Auth Core 1.3.18
+Auth Core 1.3.2
+Auth Core 1.3.20
+Auth Core 1.3.22
+Auth Core 1.3.24
+Auth Core 1.3.26
+Auth Core 1.3.4
+Auth Core 1.3.6
+Auth Core 1.3.8
+Auth Core 1.4.0
+Auth Core 1.4.2
+Auth Core 1.4.4
+Auth Selector 1.0.0
+Auth Selector 1.0.2
+Auth Selector 1.0.4
+Auth Selector 1.0.6
+Auth Selector 1.0.8
+Authentication XING API 0.0.2
+Authentication XING API 0.0.4
+Authentication XING Login 0.0.2
+Authentication XING Login 0.0.4
+Authentication XING OAuth 0.0.2
+Authentication XING OAuth 0.0.4
+Background Servlets 1.0.0
+Background Servlets 1.0.10
+Background Servlets 1.0.2
+Background Servlets 1.0.6
+Background Servlets 1.0.8
+Bundle Archetype 1.0.0
+Bundle Archetype 1.0.2
+Bundle Archetype 1.0.4
+Bundle Archetype 1.0.6
+Bundle Archetype 1.0.8
+Bundle Parent 35
+Bundle Resource 2.0.2
+Bundle Resource 2.0.4
+Bundle Resource 2.0.6
+Bundle Resource 2.1.0
+Bundle Resource 2.1.2
+Bundle Resource 2.2.0
+Bundle Resource 2.3.0
+Bundle Resource 2.3.2
+Clam 1.0.0
+Clam 1.0.2
+Clam 1.1.0
+ClassLoader Leak Detector 1.0.0
+Commons Clam 1.0.0
+Commons Clam 1.0.2
+Commons Clam 2.0.0
+Commons ClassLoader 0.9.0
+Commons ClassLoader 1.0.0
+Commons ClassLoader 1.1.0
+Commons ClassLoader 1.1.2
+Commons ClassLoader 1.1.4
+Commons ClassLoader 1.2.0
+Commons ClassLoader 1.2.2
+Commons ClassLoader 1.2.4
+Commons ClassLoader 1.3.0
+Commons ClassLoader 1.3.2
+Commons ClassLoader 1.3.6
+Commons ClassLoader 1.3.8
+Commons ClassLoader 1.4.0
+Commons ClassLoader 1.4.2
+Commons ClassLoader 1.4.4
+Commons ClassLoader 1.4.6
+Commons Compiler 1.0.0
+Commons Compiler 2.0.0
+Commons Compiler 2.0.2
+Commons Compiler 2.0.4
+Commons Compiler 2.0.6
+Commons Compiler 2.1.0
+Commons Compiler 2.2.0
+Commons Compiler 2.3.0
+Commons Compiler 2.3.2
+Commons Compiler 2.3.4
+Commons Compiler 2.3.6
+Commons Compiler 2.3.8
+Commons HTML 0.9.0
+Commons HTML 1.0.0
+Commons HTML 1.0.2
+Commons HTML 1.1.0
+Commons HTML 1.1.2
+Commons JCR File 1.0.0
+Commons JSON 2.0.10
+Commons JSON 2.0.12
+Commons JSON 2.0.16
+Commons JSON 2.0.18
+Commons JSON 2.0.2
+Commons JSON 2.0.20
+Commons JSON 2.0.4
+Commons JSON 2.0.6
+Commons JSON 2.0.8
+Commons Johnzon 1.0.0
+Commons Johnzon 1.1.0
+Commons Johnzon 1.1.2
+Commons Johnzon 1.1.4
+Commons Log 2.0.2
+Commons Log 2.0.4
+Commons Log 2.0.6
+Commons Log 2.1.0
+Commons Log 2.1.2
+Commons Log 3.0.0
+Commons Log 3.0.2
+Commons Log 4.0.0
+Commons Log 4.0.2
+Commons Log 4.0.4
+Commons Log 4.0.6
+Commons Log 5.0.0
+Commons Log 5.0.2
+Commons Log 5.1.0
+Commons Log 5.1.10
+Commons Log 5.1.12
+Commons Log 5.1.2
+Commons Log 5.1.4
+Commons Log 5.1.6
+Commons Log 5.1.8
+Commons Log Service 1.0.0
+Commons Log Service 1.0.2
+Commons Log Service 1.0.4
+Commons Log Service 1.0.6
+Commons Log Service 1.0.8
+Commons Log WebConsole 1.0.0
+Commons Log WebConsole 1.0.2
+Commons Messaging 1.0.0
+Commons Messaging Mail 1.0.0
+Commons Metrics 1.0.0
+Commons Metrics 1.2.0
+Commons Metrics 1.2.2
+Commons Metrics 1.2.6
+Commons Metrics 1.2.8
+Commons Metrics RRD4J 1.0.0
+Commons Metrics RRD4J 1.0.2
+Commons Metrics RRD4J 1.0.4
+Commons Mime 2.0.2
+Commons Mime 2.1.0
+Commons Mime 2.1.10
+Commons Mime 2.1.2
+Commons Mime 2.1.4
+Commons Mime 2.1.6
+Commons Mime 2.1.8
+Commons Mime 2.2.0
+Commons Mime 2.2.2
+Commons OSGi 2.0.2
+Commons OSGi 2.0.4
+Commons OSGi 2.0.6
+Commons OSGi 2.1.0
+Commons OSGi 2.2.0
+Commons OSGi 2.2.2
+Commons OSGi 2.3.0
+Commons OSGi 2.4.0
+Commons OSGi 2.4.2
+Commons Scheduler 2.0.2
+Commons Scheduler 2.0.4
+Commons Scheduler 2.1.0
+Commons Scheduler 2.2.0
+Commons Scheduler 2.3.0
+Commons Scheduler 2.3.2
+Commons Scheduler 2.3.4
+Commons Scheduler 2.4.0
+Commons Scheduler 2.4.10
+Commons Scheduler 2.4.12
+Commons Scheduler 2.4.14
+Commons Scheduler 2.4.2
+Commons Scheduler 2.4.4
+Commons Scheduler 2.4.6
+Commons Scheduler 2.4.8
+Commons Scheduler 2.5.0
+Commons Scheduler 2.5.2
+Commons Scheduler 2.6.0
+Commons Scheduler 2.6.2
+Commons Scheduler 2.7.0
+Commons Scheduler 2.7.2
+Commons Scheduler 2.7.4
+Commons Testing 2.0.10
+Commons Testing 2.0.12
+Commons Testing 2.0.14
+Commons Testing 2.0.16
+Commons Testing 2.0.18
+Commons Testing 2.0.2
+Commons Testing 2.0.22
+Commons Testing 2.0.24
+Commons Testing 2.0.26
+Commons Testing 2.0.6
+Commons Testing 2.0.8
+Commons Testing 2.1.0
+Commons Testing 2.1.2
+Commons Testing 2.1.4
+Commons Threads 2.0.2
+Commons Threads 2.0.4
+Commons Threads 3.0.0
+Commons Threads 3.0.2
+Commons Threads 3.1.0
+Commons Threads 3.2.0
+Commons Threads 3.2.10
+Commons Threads 3.2.16
+Commons Threads 3.2.18
+Commons Threads 3.2.2
+Commons Threads 3.2.20
+Commons Threads 3.2.4
+Commons Threads 3.2.6
+Content Detection Support 1.0.2
+Content Detection Support 1.0.4
+Content Distribution 0.1.0
+Content Distribution API 0.4.0
+Content Distribution Core 0.1.1
+Content Distribution Core 0.1.10
+Content Distribution Core 0.1.12
+Content Distribution Core 0.1.14
+Content Distribution Core 0.1.16
+Content Distribution Core 0.1.18
+Content Distribution Core 0.1.2
+Content Distribution Core 0.1.4
+Content Distribution Core 0.1.6
+Content Distribution Core 0.1.8
+Content Distribution Core 0.2.0
+Content Distribution Core 0.2.10
+Content Distribution Core 0.2.4
+Content Distribution Core 0.2.6
+Content Distribution Core 0.2.8
+Content Distribution Core 0.3.0
+Content Distribution Core 0.3.4
+Content Distribution Core 0.4.0
+Content Distribution Extensions 0.1.0
+Context-Aware Configuration API 1.0.0
+Context-Aware Configuration API 1.1.0
+Context-Aware Configuration API 1.1.2
+Context-Aware Configuration API 1.1.4
+Context-Aware Configuration Impl 1.0.0
+Context-Aware Configuration Impl 1.1.0
+Context-Aware Configuration Impl 1.2.0
+Context-Aware Configuration Impl 1.3.0
+Context-Aware Configuration Impl 1.3.2
+Context-Aware Configuration Impl 1.4.0
+Context-Aware Configuration Impl 1.4.10
+Context-Aware Configuration Impl 1.4.12
+Context-Aware Configuration Impl 1.4.14
+Context-Aware Configuration Impl 1.4.16
+Context-Aware Configuration Impl 1.4.2
+Context-Aware Configuration Impl 1.4.4
+Context-Aware Configuration Impl 1.4.6
+Context-Aware Configuration Impl 1.4.8
+Context-Aware Configuration Mock Plugin 1.0.0
+Context-Aware Configuration Mock Plugin 1.1.0
+Context-Aware Configuration Mock Plugin 1.2.0
+Context-Aware Configuration Mock Plugin 1.3.0
+Context-Aware Configuration Mock Plugin 1.3.2
+Context-Aware Configuration Mock Plugin 1.3.4
+Context-Aware Configuration SPI 1.0.0
+Context-Aware Configuration SPI 1.1.0
+Context-Aware Configuration SPI 1.2.0
+Context-Aware Configuration SPI 1.3.0
+Context-Aware Configuration SPI 1.3.2
+Context-Aware Configuration SPI 1.3.4
+Context-Aware Configuration SPI 1.3.6
+Context-Aware Configuration bnd Plugin 1.0.0
+Context-Aware Configuration bnd Plugin 1.0.2
+Context-Aware Configuration bnd Plugin 1.0.4
+Crankstart Launcher 2.0.0
+Crankstart Test Services 2.0.0
+DataSource Provider 1.0.0
+DataSource Provider 1.0.2
+DataSource Provider 1.0.4
+DataSource Provider 1.1.0
+Discovery API 1.0.0
+Discovery API 1.0.2
+Discovery API 1.0.4
+Discovery Base 1.0.0
+Discovery Base 1.0.2
+Discovery Base 1.1.0
+Discovery Base 1.1.2
+Discovery Base 1.1.4
+Discovery Base 1.1.6
+Discovery Base 2.0.0
+Discovery Base 2.0.10
+Discovery Base 2.0.4
+Discovery Base 2.0.8
+Discovery Commons 1.0.0
+Discovery Commons 1.0.10
+Discovery Commons 1.0.12
+Discovery Commons 1.0.16
+Discovery Commons 1.0.18
+Discovery Commons 1.0.2
+Discovery Commons 1.0.20
+Discovery Commons 1.0.24
+Discovery Commons 1.0.4
+Discovery Commons 1.0.6
+Discovery Commons 1.0.8
+Discovery Impl 1.0.0
+Discovery Impl 1.0.10
+Discovery Impl 1.0.12
+Discovery Impl 1.0.2
+Discovery Impl 1.0.4
+Discovery Impl 1.0.6
+Discovery Impl 1.0.8
+Discovery Impl 1.1.0
+Discovery Impl 1.1.2
+Discovery Impl 1.1.4
+Discovery Impl 1.1.6
+Discovery Impl 1.1.8
+Discovery Impl 1.2.0
+Discovery Impl 1.2.10
+Discovery Impl 1.2.12
+Discovery Impl 1.2.14
+Discovery Impl 1.2.2
+Discovery Impl 1.2.6
+Discovery Impl 1.2.8
+Discovery Oak 1.0.0
+Discovery Oak 1.0.2
+Discovery Oak 1.1.0
+Discovery Oak 1.2.0
+Discovery Oak 1.2.10
+Discovery Oak 1.2.12
+Discovery Oak 1.2.14
+Discovery Oak 1.2.16
+Discovery Oak 1.2.18
+Discovery Oak 1.2.2
+Discovery Oak 1.2.20
+Discovery Oak 1.2.22
+Discovery Oak 1.2.28
+Discovery Oak 1.2.30
+Discovery Oak 1.2.4
+Discovery Oak 1.2.6
+Discovery Oak 1.2.8
+Discovery Standalone 1.0.0
+Discovery Standalone 1.0.2
+Discovery Standalone 1.0.4
+Discovery Support 1.0.0
+Discovery Support 1.0.4
+Distributed Event Admin 1.0.0
+Distributed Event Admin 1.0.2
+Distributed Event Admin 1.0.4
+Distributed Event Admin 1.1.0
+Distributed Event Admin 1.1.2
+Distributed Event Admin 1.1.4
+Distributed Event Admin 1.1.6
+Distribution Core 0.3.6
+Dynamic Include 3.0.0
+Dynamic Include 3.1.0
+Dynamic Include 3.1.2
+Dynamic Include 3.1.4
+Engine 2.0.2
+Engine 2.0.4
+Engine 2.0.6
+Engine 2.1.0
+Engine 2.2.0
+Engine 2.2.10
+Engine 2.2.2
+Engine 2.2.4
+Engine 2.2.6
+Engine 2.2.8
+Engine 2.3.0
+Engine 2.3.10
+Engine 2.3.2
+Engine 2.3.4
+Engine 2.3.6
+Engine 2.3.8
+Engine 2.4.0
+Engine 2.4.2
+Engine 2.4.4
+Engine 2.4.6
+Engine 2.5.0
+Engine 2.6.0
+Engine 2.6.10
+Engine 2.6.12
+Engine 2.6.14
+Engine 2.6.16
+Engine 2.6.18
+Engine 2.6.2
+Engine 2.6.20
+Engine 2.6.4
+Engine 2.6.6
+Engine 2.6.8
+Event 2.0.2
+Event 2.0.4
+Event 2.0.6
+Event 2.1.0
+Event 2.2.0
+Event 2.3.0
+Event 2.4.0
+Event 2.4.2
+Event 3.0.0
+Event 3.0.2
+Event 3.1.0
+Event 3.1.2
+Event 3.1.4
+Event 3.2.0
+Event 3.3.0
+Event 3.3.10
+Event 3.3.12
+Event 3.3.14
+Event 3.3.2
+Event 3.3.4
+Event 3.3.6
+Event 3.4.0
+Event 3.4.2
+Event 3.4.4
+Event 3.5.0
+Event 3.5.2
+Event 3.5.4
+Event 3.6.0
+Event 3.7.0
+Event 3.7.2
+Event 3.7.4
+Event 3.7.6
+Event 4.0.0
+Event 4.0.2
+Event 4.1.0
+Event 4.2.0
+Event 4.2.10
+Event 4.2.12
+Event 4.2.14
+Event 4.2.2
+Event 4.2.4
+Event 4.2.6
+Event 4.2.8
+Event API 1.0.0
+Event API 1.1.0
+Extensions APT Parser 2.0.2
+Extensions APT Server 2.0.2
+Extensions APT Server 2.0.4
+Extensions Adapter 2.0.2
+Extensions OpenID Authentication 0.9.0
+Extensions Thread Dumper 0.1.2
+Extensions Thread Dumper 0.2.0
+Extensions Thread Dumper 0.2.2
+Extensions Thread Dumper 0.2.4
+Extensions Web Console Branding 1.0.2
+Extensions Web Console Branding 1.0.4
+Extensions httpauth 2.0.2
+Extensions httpauth 2.0.4
+Extensions httpauth 2.0.6
+FTP Server 1.0.0
+Failing Server-Side Tests 1.0.6
+Failing Server-Side Tests 1.0.8
+Feature Flags 1.0.0
+Feature Flags 1.0.2
+Feature Flags 1.1.0
+Feature Flags 1.2.0
+Feature Flags 1.2.2
+Feature Flags 1.2.4
+Feature Model 0.1.0
+Feature Model 0.1.2
+Feature Model 0.2.0
+Feature Model 0.8.0
+Feature Model 1.0.0
+Feature Model 1.0.2
+Feature Model Analyser 0.1.0
+Feature Model Analyser 0.1.2
+Feature Model Analyser 0.2.0
+Feature Model Analyser 0.8.0
+Feature Model Analyser 1.0.0
+Feature Model Converter 0.1.0
+Feature Model Converter 0.1.2
+Feature Model Converter 0.2.0
+Feature Model Converter 0.8.0
+Feature Model Converter 1.0.0
+Feature Model IO 0.1.0
+Feature Model IO 0.1.2
+Feature Model IO 0.2.0
+Feature Model IO 0.8.0
+Feature Model IO 1.0.0
+Feature Model IO 1.0.2
+Feature Model Launcher 0.2.0
+Feature Model Launcher 0.8.0
+Feature Model Launcher 1.0.0
+File Installer 1.0.0
+File Installer 1.0.2
+File Installer 1.0.4
+File Installer 1.1.0
+File Installer 1.1.2
+File Optimization 0.9.2
+File System ClassLoader 1.0.0
+File System ClassLoader 1.0.10
+File System ClassLoader 1.0.2
+File System ClassLoader 1.0.4
+File System ClassLoader 1.0.6
+File System ClassLoader 1.0.8
+File System Resource Provider 0.9.2
+File System Resource Provider 1.0.0
+File System Resource Provider 1.0.2
+File System Resource Provider 1.1.0
+File System Resource Provider 1.1.2
+File System Resource Provider 1.1.4
+File System Resource Provider 1.2.2
+File System Resource Provider 1.3.0
+File System Resource Provider 1.4.0
+File System Resource Provider 1.4.10
+File System Resource Provider 1.4.2
+File System Resource Provider 1.4.4
+File System Resource Provider 1.4.6
+File System Resource Provider 1.4.8
+File System Resource Provider 2.0.0
+File System Resource Provider 2.1.0
+File System Resource Provider 2.1.10
+File System Resource Provider 2.1.12
+File System Resource Provider 2.1.14
+File System Resource Provider 2.1.16
+File System Resource Provider 2.1.2
+File System Resource Provider 2.1.4
+File System Resource Provider 2.1.6
+File System Resource Provider 2.1.8
+Form Based Authentication 1.0.0
+Form Based Authentication 1.0.10
+Form Based Authentication 1.0.12
+Form Based Authentication 1.0.14
+Form Based Authentication 1.0.2
+Form Based Authentication 1.0.4
+Form Based Authentication 1.0.6
+Form Based Authentication 1.0.8
+Framework Extension Fragment Activation 1.0.0
+Framework Extension Fragment Activation 1.0.2
+Framework Extension Fragment Activation 1.0.4
+Framework Extension Fragment Servlet API 1.0.0
+Framework Extension Fragment Transaction 1.0.0
+Framework Extension Fragment Transaction 1.0.2
+Framework Extension Fragment WS 1.0.0
+Framework Extension Fragment WS 1.0.2
+Framework Extension Fragment WS 1.0.4
+Framework Extension Fragment XML 1.0.0
+Framework Extension Fragment XML 1.0.2
+Framework Extension Fragment XML 1.0.4
+GWT Support 3.0.0
+GWT Support 3.0.2
+HApi 1.0.0
+HApi 1.1.0
+HApi Client 1.0.0
+HApi Client 1.0.2
+HTL Maven Plugin 1.0.0
+HTL Maven Plugin 1.0.2
+HTL Maven Plugin 1.0.4
+HTL Maven Plugin 1.0.6
+HTL Maven Plugin 1.0.8
+HTL Maven Plugin 1.1.0
+HTL Maven Plugin 1.1.2
+HTL Maven Plugin 1.1.4-1.3.1
+HTL Maven Plugin 1.1.6-1.4.0
+HTL Maven Plugin 1.1.8-1.4.0
+HTL Maven Plugin 1.2.0-1.4.0
+HTL Maven Plugin 1.2.2-1.4.0
+HTL Maven Plugin 1.2.4-1.4.0
+Health Check API 1.0.0
+Health Check API 1.0.2
+Health Check Annotations 1.0.2
+Health Check Annotations 1.0.4
+Health Check Annotations 1.0.6
+Health Check Core 1.0.4
+Health Check Core 1.0.6
+Health Check Core 1.1.0
+Health Check Core 1.1.2
+Health Check Core 1.2.0
+Health Check Core 1.2.10
+Health Check Core 1.2.12
+Health Check Core 1.2.2
+Health Check Core 1.2.4
+Health Check Core 1.2.6
+Health Check Core 1.2.8
+Health Check JMX 1.0.4
+Health Check JMX 1.0.6
+Health Check JUnit Bridge 1.0.2
+Health Check JUnit Bridge 1.0.4
+Health Check Support 1.0.4
+Health Check Support 1.0.6
+Health Check integration tests 1.0.4
+Health Check integration tests 1.0.6
+Health Check samples 1.0.4
+Health Check samples 1.0.6
+Health Check samples 1.0.8
+Health Check webconsole 1.0.4
+Health Check webconsole 1.1.0
+Health Check webconsole 1.1.2
+Health Check webconsole 1.1.4
+I18n 2.0.2
+I18n 2.0.4
+I18n 2.1.0
+I18n 2.1.2
+Initial Content Archetype 1.0.0
+Initial Content Archetype 1.0.4
+Initial Content Archetype 1.0.6
+Initial Content Archetype 1.0.8
+Installer API 1.0.0
+Installer Configuration Factory 1.0.0
+Installer Configuration Factory 1.0.10
+Installer Configuration Factory 1.0.12
+Installer Configuration Factory 1.0.14
+Installer Configuration Factory 1.0.2
+Installer Configuration Factory 1.0.4
+Installer Configuration Factory 1.0.8
+Installer Configuration Factory 1.1.0
+Installer Configuration Factory 1.1.2
+Installer Configuration Factory 1.2.0
+Installer Configuration Factory 1.2.2
+Installer Console 1.0.0
+Installer Console 1.0.2
+Installer Console 1.0.4
+Installer Core 3.0.0
+Installer Core 3.1.0
+Installer Core 3.1.2
+Installer Core 3.2.0
+Installer Core 3.2.2
+Installer Core 3.3.0
+Installer Core 3.3.2
+Installer Core 3.3.4
+Installer Core 3.3.6
+Installer Core 3.3.8
+Installer Core 3.4.0
+Installer Core 3.4.2
+Installer Core 3.4.4
+Installer Core 3.4.6
+Installer Core 3.5.0
+Installer Core 3.5.2
+Installer Core 3.5.4
+Installer Core 3.6.0
+Installer Core 3.6.2
+Installer Core 3.6.4
+Installer Core 3.6.6
+Installer Core 3.6.8
+Installer Core 3.7.0
+Installer Core 3.8.0
+Installer Core 3.8.10
+Installer Core 3.8.12
+Installer Core 3.8.2
+Installer Core 3.8.6
+Installer Core 3.8.8
+Installer Core 3.9.0
+Installer Core 3.9.2
+Installer Health Checks 1.0.0
+Installer Health Checks 2.0.0
+Installer Health Checks 2.0.2
+Installer Packages Factory 1.0.0
+Installer Subsystem Base Factory 1.0.0
+Installer Subsystems Factory 1.0.0
+Installer Subsystems Factory 1.0.2
+Installer Vault Package Install Hook 1.0.2
+Installer Vault Package Install Hook 1.0.4
+JCR API 2.0.2
+JCR API 2.0.4
+JCR API 2.0.6
+JCR API 2.1.0
+JCR API 2.2.0
+JCR API 2.3.0
+JCR API 2.4.0
+JCR API 2.4.2
+JCR Base 2.0.2
+JCR Base 2.0.4
+JCR Base 2.0.6
+JCR Base 2.1.0
+JCR Base 2.1.2
+JCR Base 2.2.0
+JCR Base 2.2.2
+JCR Base 2.3.0
+JCR Base 2.3.2
+JCR Base 2.4.0
+JCR Base 2.4.2
+JCR Base 3.0.0
+JCR Base 3.0.2
+JCR Base 3.0.4
+JCR Base 3.0.6
+JCR Base 3.0.8
+JCR ClassLoader 3.2.0
+JCR ClassLoader 3.2.2
+JCR ClassLoader 3.2.4
+JCR ClassLoader 3.2.6
+JCR Classloader 2.0.2
+JCR Classloader 2.0.6
+JCR Classloader 3.0.0
+JCR Classloader 3.1.0
+JCR Classloader 3.1.10
+JCR Classloader 3.1.12
+JCR Classloader 3.1.2
+JCR Classloader 3.1.4
+JCR Classloader 3.1.6
+JCR Classloader 3.1.8
+JCR Compiler 1.0.0
+JCR Compiler 2.0.0
+JCR Compiler 2.0.2
+JCR Compiler 2.0.4
+JCR Compiler 2.1.0
+JCR Compiler 2.1.2
+JCR Content Parser 1.0.0
+JCR Content Parser 1.1.0
+JCR Content Parser 1.2.0
+JCR Content Parser 1.2.2
+JCR Content Parser 1.2.4
+JCR Content Parser 1.2.6
+JCR Content Parser 1.2.8
+JCR ContentLoader 2.1.0
+JCR ContentLoader 2.1.10
+JCR ContentLoader 2.1.2
+JCR ContentLoader 2.1.4
+JCR ContentLoader 2.1.6
+JCR ContentLoader 2.1.8
+JCR ContentLoader 2.2.0
+JCR ContentLoader 2.2.2
+JCR ContentLoader 2.2.4
+JCR ContentLoader 2.2.6
+JCR ContentLoader 2.3.0
+JCR ContentLoader 2.3.2
+JCR Contentloader 2.0.2
+JCR Contentloader 2.0.4
+JCR Contentloader 2.0.6
+JCR DavEx 1.0.0
+JCR DavEx 1.1.0
+JCR DavEx 1.2.0
+JCR DavEx 1.2.2
+JCR DavEx 1.3.0
+JCR DavEx 1.3.2
+JCR Davex 1.3.10
+JCR Davex 1.3.12
+JCR Davex 1.3.4
+JCR Davex 1.3.8
+JCR File Transfer 1.0.0
+JCR Installer 3.0.0
+JCR Installer 3.0.2
+JCR Installer 3.0.4
+JCR Installer 3.1.0
+JCR Installer 3.1.14
+JCR Installer 3.1.16
+JCR Installer 3.1.18
+JCR Installer 3.1.2
+JCR Installer 3.1.22
+JCR Installer 3.1.24
+JCR Installer 3.1.26
+JCR Installer 3.1.28
+JCR Installer 3.1.4
+JCR Installer 3.1.6
+JCR Installer 3.1.8
+JCR Jackrabbit API 2.0.2
+JCR Jackrabbit Access Manager 2.0.2
+JCR Jackrabbit Access Manager 2.1.0
+JCR Jackrabbit Access Manager 2.1.2
+JCR Jackrabbit Access Manager 3.0.0
+JCR Jackrabbit Access Manager 3.0.2
+JCR Jackrabbit Access Manager 3.0.4
+JCR Jackrabbit Access Manager 3.0.6
+JCR Jackrabbit Client 2.0.2
+JCR Jackrabbit Server 2.0.2
+JCR Jackrabbit Server 2.0.4
+JCR Jackrabbit Server 2.0.6
+JCR Jackrabbit Server 2.1.0
+JCR Jackrabbit Server 2.1.2
+JCR Jackrabbit Server 2.2.0
+JCR Jackrabbit Server 2.3.0
+JCR Jackrabbit Server 2.3.2
+JCR Jackrabbit User Manager 2.0.2
+JCR Jackrabbit User Manager 2.0.4
+JCR Jackrabbit User Manager 2.1.0
+JCR Jackrabbit User Manager 2.2.0
+JCR Jackrabbit User Manager 2.2.10
+JCR Jackrabbit User Manager 2.2.2
+JCR Jackrabbit User Manager 2.2.4
+JCR Jackrabbit User Manager 2.2.6
+JCR Jackrabbit User Manager 2.2.8
+JCR OCM 2.0.2
+JCR OCM 2.0.4
+JCR OCM 2.0.6
+JCR Oak Server 1.0.0
+JCR Oak Server 1.1.0
+JCR Oak Server 1.1.2
+JCR Oak Server 1.1.4
+JCR Oak Server 1.2.0
+JCR Oak Server 1.2.2
+JCR Oak Server 1.2.4
+JCR Prefs 1.0.0
+JCR Prefs 1.0.2
+JCR Registration 1.0.0
+JCR Registration 1.0.2
+JCR Registration 1.0.4
+JCR Registration 1.0.6
+JCR Registration 1.0.8
+JCR Resource 2.0.10
+JCR Resource 2.0.2
+JCR Resource 2.0.4
+JCR Resource 2.0.6
+JCR Resource 2.0.8
+JCR Resource 2.1.0
+JCR Resource 2.2.0
+JCR Resource 2.2.2
+JCR Resource 2.2.4
+JCR Resource 2.2.6
+JCR Resource 2.2.8
+JCR Resource 2.3.0
+JCR Resource 2.3.10
+JCR Resource 2.3.12
+JCR Resource 2.3.2
+JCR Resource 2.3.4
+JCR Resource 2.3.6
+JCR Resource 2.3.8
+JCR Resource 2.4.2
+JCR Resource 2.4.4
+JCR Resource 2.5.0
+JCR Resource 2.5.2
+JCR Resource 2.5.4
+JCR Resource 2.5.6
+JCR Resource 2.7.0
+JCR Resource 2.7.2
+JCR Resource 2.7.4
+JCR Resource 2.8.0
+JCR Resource 2.8.2
+JCR Resource 2.8.4
+JCR Resource 2.9.0
+JCR Resource 2.9.2
+JCR Resource 3.0.0
+JCR Resource 3.0.10
+JCR Resource 3.0.14
+JCR Resource 3.0.16
+JCR Resource 3.0.18
+JCR Resource 3.0.2
+JCR Resource 3.0.20
+JCR Resource 3.0.4
+JCR Resource 3.0.6
+JCR Resource 3.0.8
+JCR Resource Security 1.0.0
+JCR Resource Security 1.0.2
+JCR Resource Security 1.0.4
+JCR Web Console 1.0.0
+JCR Web Console 1.0.2
+JCR Web Console 1.0.4
+JCR Webdav 2.0.2
+JCR Webdav 2.0.6
+JCR Webdav 2.0.8
+JCR Webdav 2.1.0
+JCR Webdav 2.1.2
+JCR Webdav 2.2.0
+JCR Webdav 2.2.2
+JCR Webdav 2.3.0
+JCR Webdav 2.3.10
+JCR Webdav 2.3.2
+JCR Webdav 2.3.4
+JCR Webdav 2.3.8
+JCR Wrapper 2.0.0
+JCRInstall Bundle Archetype 1.0.0
+JCRInstall Bundle Archetype 1.0.2
+JCRInstall Bundle Archetype 1.0.4
+JCRInstall Bundle Archetype 1.0.6
+JCRInstall Bundle Archetype 1.0.8
+JMX Resource Provider 0.5.0
+JMX Resource Provider 0.6.0
+JMX Resource Provider 1.0.0
+JMX Resource Provider 1.0.2
+JMX Resource Provider 1.0.4
+JUnit Core 1.0.10
+JUnit Core 1.0.12
+JUnit Core 1.0.14
+JUnit Core 1.0.16
+JUnit Core 1.0.18
+JUnit Core 1.0.20
+JUnit Core 1.0.22
+JUnit Core 1.0.23
+JUnit Core 1.0.24
+JUnit Core 1.0.26
+JUnit Core 1.0.6
+JUnit Core 1.0.8
+JUnit Health Check 1.0.6
+JUnit Health Check 1.0.8
+JUnit Remote Test Runners 1.0.12
+JUnit Remote Tests Runners 1.0.10
+JUnit Remote Tests Runners 1.0.6
+JUnit Remote Tests Runners 1.0.8
+JUnit Scriptable Tests Provider 1.0.10
+JUnit Scriptable Tests Provider 1.0.12
+JUnit Scriptable Tests Provider 1.0.6
+JUnit Scriptable Tests Provider 1.0.8
+JUnit Tests Teleporter 1.0.10
+JUnit Tests Teleporter 1.0.12
+JUnit Tests Teleporter 1.0.14
+JUnit Tests Teleporter 1.0.16
+JUnit Tests Teleporter 1.0.18
+JUnit Tests Teleporter 1.0.2
+JUnit Tests Teleporter 1.0.20
+JUnit Tests Teleporter 1.0.4
+JUnit Tests Teleporter 1.0.6
+JUnit Tests Teleporter 1.0.8
+Java Version Maven Plugin 1.0.0
+Java Version Maven Plugin 1.0.2
+Karaf 0.2.0
+Karaf Configs 0.2.0
+Karaf Distribution 0.2.0
+Karaf Features 0.2.0
+Karaf Integration Tests 0.2.0
+Karaf Launchpad Integration Tests (Oak Tar) 0.0.2
+Karaf repoinit 0.2.0
+Karaf repoinit 0.2.2
+Launchpad API 1.0.0
+Launchpad API 1.1.0
+Launchpad API 1.2.0
+Launchpad API 1.2.2
+Launchpad API 1.3.0
+Launchpad App 3
+Launchpad App 5
+Launchpad Base 2.0.2
+Launchpad Base 2.0.4
+Launchpad Base 2.1.0
+Launchpad Base 2.3.0
+Launchpad Base 2.4.0
+Launchpad Base 2.5.0
+Launchpad Base 2.5.2
+Launchpad Base 2.5.4
+Launchpad Base 2.5.6
+Launchpad Base 2.5.8
+Launchpad Base 2.6.0
+Launchpad Base 2.6.10
+Launchpad Base 2.6.12
+Launchpad Base 2.6.14
+Launchpad Base 2.6.16
+Launchpad Base 2.6.18
+Launchpad Base 2.6.2
+Launchpad Base 2.6.20
+Launchpad Base 2.6.22
+Launchpad Base 2.6.24
+Launchpad Base 2.6.26
+Launchpad Base 2.6.28
+Launchpad Base 2.6.30
+Launchpad Base 2.6.32
+Launchpad Base 2.6.34
+Launchpad Base 2.6.36
+Launchpad Base 2.6.4
+Launchpad Base 2.6.6
+Launchpad Base 2.6.8
+Launchpad Builder 6
+Launchpad Builder 7
+Launchpad Builder 8
+Launchpad Builder 9
+Launchpad Bundles 5
+Launchpad Content 2.0.12
+Launchpad Content 2.0.14
+Launchpad Content 2.0.2
+Launchpad Content 2.0.4
+Launchpad Content 2.0.6
+Launchpad Content 2.0.8
+Launchpad Installer 1.0.0
+Launchpad Installer 1.0.2
+Launchpad Installer 1.0.4
+Launchpad Installer 1.0.6
+Launchpad Installer 1.1.0
+Launchpad Installer 1.1.2
+Launchpad Installer 1.1.4
+Launchpad Installer 1.2.0
+Launchpad Installer 1.2.2
+Launchpad Installer 1.2.4
+Launchpad Integration Tests 1.0.0
+Launchpad Integration Tests 1.0.2
+Launchpad Integration Tests 1.0.4
+Launchpad Integration Tests 1.0.6
+Launchpad Integration Tests 1.0.8
+Launchpad Integration Tests 12
+Launchpad Standalone Archetype 1.0.0
+Launchpad Standalone Archetype 1.0.2
+Launchpad Test Fragment 2.0.16
+Launchpad Test Services 2.0.12
+Launchpad Test Services 2.0.14
+Launchpad Test Services 2.0.6
+Launchpad Testing 10
+Launchpad Testing 11
+Launchpad Testing 5
+Launchpad Testing 6
+Launchpad Testing Services 2.0.16
+Launchpad Testing War 10
+Launchpad Testing War 11
+Launchpad Webapp 3
+Launchpad Webapp 5
+Launchpad Webapp Archetype 1.0.0
+Launchpad Webapp Archetype 1.0.2
+Log Tail 1.0.0
+Log Tracer 0.0.2
+Log Tracer 1.0.0
+Log Tracer 1.0.2
+Log Tracer 1.0.4
+Log Tracer 1.0.6
+Log Tracer 1.0.8
+Maven JCROCM Plugin 2.0.2
+Maven JCROCM Plugin 2.0.4
+Maven JCROCM Plugin 2.0.6
+Maven JSPC Plugin 2.0.2
+Maven JSPC Plugin 2.0.6
+Maven JSPC Plugin 2.0.8
+Maven JSPC Plugin 2.1.0
+Maven JSPC Plugin 2.1.2
+Maven Launchpad Plugin 2.0.10
+Maven Launchpad Plugin 2.0.6
+Maven Launchpad Plugin 2.0.8
+Maven Launchpad Plugin 2.1.0
+Maven Launchpad Plugin 2.1.2
+Maven Launchpad Plugin 2.2.0
+Maven Launchpad Plugin 2.3.0
+Maven Launchpad Plugin 2.3.2
+Maven Launchpad Plugin 2.3.4
+Maven Launchpad Plugin 2.3.6
+Maven Sling Plugin 2.0.2
+Maven Sling Plugin 2.0.4
+Maven Sling Plugin 2.0.6
+Maven Sling Plugin 2.1.0
+Maven Sling Plugin 2.1.10
+Maven Sling Plugin 2.1.2
+Maven Sling Plugin 2.1.6
+Maven Sling Plugin 2.1.8
+Maven Sling Plugin 2.2.0
+Maven Sling Plugin 2.2.2
+Maven Sling Plugin 2.3.0
+Maven Sling Plugin 2.3.2
+Maven Sling Plugin 2.3.4
+Maven Sling Plugin 2.3.6
+Maven Sling Plugin 2.3.8
+MoM API 1.0.0
+MoM API 1.0.2
+MoM JMS 1.0.0
+MoM JMS 1.0.2
+MoM Jobs 1.0.0
+MoM Jobs 1.0.2
+Mongo Resource Provider 1.0.0
+NoSQL Couchbase Client 1.0.0
+NoSQL Couchbase Client 1.0.2
+NoSQL Couchbase Client 1.0.4
+NoSQL Couchbase Resource Provider 1.0.0
+NoSQL Couchbase Resource Provider 1.1.0
+NoSQL Couchbase Resource Provider 1.1.2
+NoSQL Generic Resource Provider 1.0.0
+NoSQL Generic Resource Provider 1.1.0
+NoSQL Generic Resource Provider 1.1.2
+NoSQL MongoDB Resource Provider 1.0.0
+NoSQL MongoDB Resource Provider 1.1.0
+NoSQL MongoDB Resource Provider 1.1.2
+Oak Restrictions 1.0.0
+Oak Restrictions 1.0.2
+Oak Restrictions 1.0.4
+OpenID Authentication 1.0.0
+OpenID Authentication 1.0.2
+OpenID Authentication 1.0.4
+OpenID Authentication 1.0.6
+Parent 10
+Parent 11
+Parent 12
+Parent 13
+Parent 14
+Parent 15
+Parent 16
+Parent 17
+Parent 18
+Parent 19
+Parent 20
+Parent 22
+Parent 23
+Parent 24
+Parent 25
+Parent 26
+Parent 27
+Parent 28
+Parent 29
+Parent 30
+Parent 31
+Parent 32
+Parent 33
+Parent 34
+Parent 35
+Parent 5
+Parent 6
+Parent 7
+Parent 8
+Parent 9
+Path Based RTP 2.0.2
+Path based RTP 2.0.4
+Pipes 0.0.10
+Pipes 1.0.4
+Pipes 1.1.0
+Pipes 2.0.2
+Pipes 3.0.2
+Pipes 3.1.0
+Pipes 3.2.0
+Portal Container 1.0.0
+Repoinit JCR 1.0.0
+Repoinit JCR 1.0.2
+Repoinit JCR 1.1.0
+Repoinit JCR 1.1.10
+Repoinit JCR 1.1.2
+Repoinit JCR 1.1.4
+Repoinit JCR 1.1.6
+Repoinit JCR 1.1.8
+Repoinit Parser 1.0.2
+Repoinit Parser 1.0.4
+Repoinit Parser 1.1.0
+Repoinit Parser 1.2.0
+Repoinit Parser 1.2.2
+Repoinit Parser 1.2.4
+Request Analyzer 1.0.0
+Resource Access Security 1.0.0
+Resource Access Security 1.0.2
+Resource Builder 1.0.0
+Resource Builder 1.0.2
+Resource Builder 1.0.4
+Resource Builder 1.0.6
+Resource Collections 1.0.0
+Resource Collections 1.0.2
+Resource Collections 1.0.4
+Resource Editor 1.0.2
+Resource Filter 1.0.0
+Resource Filter 1.0.2
+Resource Inventory 0.5.0
+Resource Inventory 1.0.0
+Resource Inventory 1.0.2
+Resource Inventory 1.0.4
+Resource Inventory 1.0.6
+Resource Inventory 1.0.8
+Resource Merger 1.0.0
+Resource Merger 1.1.0
+Resource Merger 1.1.2
+Resource Merger 1.2.0
+Resource Merger 1.2.10
+Resource Merger 1.2.4
+Resource Merger 1.2.6
+Resource Merger 1.2.8
+Resource Merger 1.3.0
+Resource Merger 1.3.10
+Resource Merger 1.3.2
+Resource Merger 1.3.4
+Resource Merger 1.3.6
+Resource Merger 1.3.8
+Resource Presence 0.0.2
+Resource Presence 0.0.4
+Resource Resolver 1.0.0
+Resource Resolver 1.0.2
+Resource Resolver 1.0.4
+Resource Resolver 1.0.6
+Resource Resolver 1.1.0
+Resource Resolver 1.1.10
+Resource Resolver 1.1.12
+Resource Resolver 1.1.14
+Resource Resolver 1.1.2
+Resource Resolver 1.1.4
+Resource Resolver 1.1.6
+Resource Resolver 1.1.8
+Resource Resolver 1.2.0
+Resource Resolver 1.2.2
+Resource Resolver 1.2.4
+Resource Resolver 1.2.6
+Resource Resolver 1.4.0
+Resource Resolver 1.4.10
+Resource Resolver 1.4.12
+Resource Resolver 1.4.14
+Resource Resolver 1.4.16
+Resource Resolver 1.4.18
+Resource Resolver 1.4.2
+Resource Resolver 1.4.4
+Resource Resolver 1.4.8
+Resource Resolver 1.5.0
+Resource Resolver 1.5.10
+Resource Resolver 1.5.12
+Resource Resolver 1.5.14
+Resource Resolver 1.5.18
+Resource Resolver 1.5.2
+Resource Resolver 1.5.20
+Resource Resolver 1.5.22
+Resource Resolver 1.5.24
+Resource Resolver 1.5.26
+Resource Resolver 1.5.28
+Resource Resolver 1.5.30
+Resource Resolver 1.5.32
+Resource Resolver 1.5.34
+Resource Resolver 1.5.36
+Resource Resolver 1.5.4
+Resource Resolver 1.5.6
+Resource Resolver 1.5.8
+Resource Resolver 1.6.0
+Resource Resolver 1.6.10
+Resource Resolver 1.6.4
+Resource Resolver 1.6.6
+Resource Resolver 1.6.8
+Rewriter 1.0.0
+Rewriter 1.0.2
+Rewriter 1.0.4
+Rewriter 1.1.0
+Rewriter 1.1.2
+Rewriter 1.1.4
+Rewriter 1.2.0
+Rewriter 1.2.2
+Rewriter 1.2.4
+SLF4J MDC Filter 1.0.0
+SLF4J MDC Filter 1.0.2
+Sample Integration Tests 1.0.6
+Sample Integration Tests 1.0.8
+Sample Server-Side Tests 1.0.6
+Sample Server-Side Tests 1.0.8
+Samples Fling 0.0.2
+Samples Simple Demo 2.0.2
+Samples Webloader Service 2.0.2
+Samples Webloader UI 2.0.2
+Scripting API 2.0.2
+Scripting API 2.1.0
+Scripting API 2.1.12
+Scripting API 2.1.2
+Scripting API 2.1.4
+Scripting API 2.1.6
+Scripting API 2.1.8
+Scripting API 2.2.0
+Scripting API 2.2.2
+Scripting Bundle Maven Plugin 0.1.0
+Scripting Bundle Tracker 0.1.0
+Scripting Console 1.0.0
+Scripting Console 1.0.2
+Scripting Core 2.0.10
+Scripting Core 2.0.14
+Scripting Core 2.0.16
+Scripting Core 2.0.18
+Scripting Core 2.0.2
+Scripting Core 2.0.20
+Scripting Core 2.0.22
+Scripting Core 2.0.24
+Scripting Core 2.0.26
+Scripting Core 2.0.28
+Scripting Core 2.0.30
+Scripting Core 2.0.32
+Scripting Core 2.0.34
+Scripting Core 2.0.36
+Scripting Core 2.0.38
+Scripting Core 2.0.4
+Scripting Core 2.0.40
+Scripting Core 2.0.44
+Scripting Core 2.0.46
+Scripting Core 2.0.48
+Scripting Core 2.0.50
+Scripting Core 2.0.52
+Scripting Core 2.0.54
+Scripting Core 2.0.56
+Scripting Core 2.0.58
+Scripting Core 2.0.6
+Scripting Core 2.0.8
+Scripting EL API Wrapper 1.0.0
+Scripting ESX 0.2.0
+Scripting FreeMarker 1.0.0
+Scripting FreeMarker 1.0.2
+Scripting Groovy 1.0.0
+Scripting Groovy 1.0.2
+Scripting Groovy 1.0.4
+Scripting Groovy 1.0.6
+Scripting HTL Compiler 1.0.0
+Scripting HTL Compiler 1.0.10
+Scripting HTL Compiler 1.0.12
+Scripting HTL Compiler 1.0.14
+Scripting HTL Compiler 1.0.16
+Scripting HTL Compiler 1.0.2
+Scripting HTL Compiler 1.0.20-1.3.1
+Scripting HTL Compiler 1.0.22-1.4.0
+Scripting HTL Compiler 1.0.4
+Scripting HTL Compiler 1.0.6
+Scripting HTL Compiler 1.0.8
+Scripting HTL Compiler 1.1.0-1.4.0
+Scripting HTL Compiler 1.1.2-1.4.0
+Scripting HTL Engine 1.0.20
+Scripting HTL Engine 1.0.22
+Scripting HTL Engine 1.0.24
+Scripting HTL Engine 1.0.26
+Scripting HTL Engine 1.0.28
+Scripting HTL Engine 1.0.30
+Scripting HTL Engine 1.0.32
+Scripting HTL Engine 1.0.34
+Scripting HTL Engine 1.0.36
+Scripting HTL Engine 1.0.38
+Scripting HTL Engine 1.0.40
+Scripting HTL Engine 1.0.42
+Scripting HTL Engine 1.0.44
+Scripting HTL Engine 1.0.46
+Scripting HTL Engine 1.0.48-1.3.1
+Scripting HTL Engine 1.0.52-1.3.1
+Scripting HTL Engine 1.0.54-1.4.0
+Scripting HTL Engine 1.0.56-1.4.0
+Scripting HTL Engine 1.1.0-1.4.0
+Scripting HTL Engine 1.1.2-1.4.0
+Scripting HTL JS Use Provider 1.0.12
+Scripting HTL JS Use Provider 1.0.14
+Scripting HTL JS Use Provider 1.0.16
+Scripting HTL JS Use Provider 1.0.18
+Scripting HTL JS Use Provider 1.0.20
+Scripting HTL JS Use Provider 1.0.22
+Scripting HTL JS Use Provider 1.0.24
+Scripting HTL JS Use Provider 1.0.26
+Scripting HTL JS Use Provider 1.0.28
+Scripting HTL Java Compiler 1.0.0
+Scripting HTL Java Compiler 1.0.10
+Scripting HTL Java Compiler 1.0.12
+Scripting HTL Java Compiler 1.0.14
+Scripting HTL Java Compiler 1.0.16
+Scripting HTL Java Compiler 1.0.18
+Scripting HTL Java Compiler 1.0.2
+Scripting HTL Java Compiler 1.0.22-1.3.1
+Scripting HTL Java Compiler 1.0.24-1.4.0
+Scripting HTL Java Compiler 1.0.26-1.4.0
+Scripting HTL Java Compiler 1.0.4
+Scripting HTL Java Compiler 1.0.6
+Scripting HTL Java Compiler 1.0.8
+Scripting HTL Java Compiler 1.1.0-1.4.0
+Scripting HTL Java Compiler 1.1.2-1.4.0
+Scripting HTL Models Use Provider 1.0.2
+Scripting HTL Models Use Provider 1.0.4
+Scripting HTL Models Use Provider 1.0.6
+Scripting HTL Models Use Provider 1.0.8
+Scripting HTL REPL 1.0.4
+Scripting HTL REPL 1.0.6
+Scripting HTL Runtime 1.0.0-1.4.0
+Scripting HTL Runtime 1.1.0-1.4.0
+Scripting HTL Testing 1.0.10-1.4.0
+Scripting HTL Testing 1.0.12-1.4.0
+Scripting HTL Testing 1.0.14-1.4.0
+Scripting HTL Testing 1.0.6-1.3.1
+Scripting HTL Testing 1.0.8-1.3.1
+Scripting HTL Testing Content 1.0.10-1.4.0
+Scripting HTL Testing Content 1.0.12-1.4.0
+Scripting HTL Testing Content 1.0.14-1.4.0
+Scripting HTL Testing Content 1.0.8-1.3.1
+Scripting JSP 2.0.10
+Scripting JSP 2.0.12
+Scripting JSP 2.0.14
+Scripting JSP 2.0.16
+Scripting JSP 2.0.18
+Scripting JSP 2.0.2
+Scripting JSP 2.0.20
+Scripting JSP 2.0.22
+Scripting JSP 2.0.24
+Scripting JSP 2.0.26
+Scripting JSP 2.0.28
+Scripting JSP 2.0.6
+Scripting JSP 2.0.8
+Scripting JSP 2.1.0
+Scripting JSP 2.1.4
+Scripting JSP 2.1.6
+Scripting JSP 2.1.8
+Scripting JSP 2.2.0
+Scripting JSP 2.2.2
+Scripting JSP 2.2.4
+Scripting JSP 2.2.6
+Scripting JSP 2.3.0
+Scripting JSP 2.3.2
+Scripting JSP 2.3.4
+Scripting JSP API Wrapper 1.0.0
+Scripting JSP Taglib 2.2.6
+Scripting JSP Taglib 2.3.0
+Scripting JSP Taglib 2.4.0
+Scripting JSP Taglib 2.4.2
+Scripting JSP Taglib Compat 1.0.0
+Scripting JSP-Atom-Taglib 1.0.0
+Scripting JSP-Taglib 2.0.2
+Scripting JSP-Taglib 2.0.4
+Scripting JSP-Taglib 2.0.6
+Scripting JSP-Taglib 2.1.0
+Scripting JSP-Taglib 2.1.2
+Scripting JSP-Taglib 2.1.6
+Scripting JSP-Taglib 2.1.8
+Scripting JSP-Taglib 2.2.0
+Scripting JSP-Taglib 2.2.2
+Scripting JSP-Taglib 2.2.4
+Scripting JSP-Taglib 2.2.6
+Scripting JST 2.0.4
+Scripting JST 2.0.6
+Scripting JST 2.0.8
+Scripting Java 1.0.0
+Scripting Java 2.0.0
+Scripting Java 2.0.10
+Scripting Java 2.0.12
+Scripting Java 2.0.14
+Scripting Java 2.0.2
+Scripting Java 2.0.4
+Scripting Java 2.0.6
+Scripting Java 2.1.0
+Scripting Java 2.1.2
+Scripting Java 2.1.4
+Scripting JavaScript 2.0.10
+Scripting JavaScript 2.0.12
+Scripting JavaScript 2.0.14
+Scripting JavaScript 2.0.16
+Scripting JavaScript 2.0.18
+Scripting JavaScript 2.0.2
+Scripting JavaScript 2.0.20
+Scripting JavaScript 2.0.22
+Scripting JavaScript 2.0.24
+Scripting JavaScript 2.0.26
+Scripting JavaScript 2.0.28
+Scripting JavaScript 2.0.30
+Scripting JavaScript 2.0.6
+Scripting JavaScript 2.0.8
+Scripting JavaScript 3.0.0
+Scripting JavaScript 3.0.2
+Scripting JavaScript 3.0.4
+Scripting JavaScript 3.0.6
+Scripting Scala 1.0.0
+Scripting Sightly Engine 1.0.0
+Scripting Sightly Engine 1.0.10
+Scripting Sightly Engine 1.0.12
+Scripting Sightly Engine 1.0.14
+Scripting Sightly Engine 1.0.16
+Scripting Sightly Engine 1.0.18
+Scripting Sightly Engine 1.0.2
+Scripting Sightly Engine 1.0.4
+Scripting Sightly Engine 1.0.6
+Scripting Sightly JS Use Provider 1.0.0
+Scripting Sightly JS Use Provider 1.0.10
+Scripting Sightly JS Use Provider 1.0.4
+Scripting Sightly JS Use Provider 1.0.6
+Scripting Sightly JS Use Provider 1.0.8
+Scripting Sightly Models Use Provider 1.0.0
+Scripting Sightly REPL 1.0.0
+Scripting Sightly REPL 1.0.2
+Scripting Thymeleaf 0.0.2
+Scripting Thymeleaf 0.0.4
+Scripting Thymeleaf 0.0.6
+Scripting Thymeleaf 1.0.0
+Scripting Thymeleaf 1.1.0
+Scripting Thymeleaf 2.0.0
+Scripting Thymeleaf 2.0.2
+Scripting Velocity 2.0.0
+Scripting Velocity 2.0.2
+Security 1.0.0
+Security 1.0.10
+Security 1.0.12
+Security 1.0.14
+Security 1.0.16
+Security 1.0.18
+Security 1.0.2
+Security 1.0.4
+Security 1.0.6
+Security 1.0.8
+Security 1.1.0
+Security 1.1.10
+Security 1.1.12
+Security 1.1.16
+Security 1.1.18
+Security 1.1.2
+Security 1.1.4
+Security 1.1.6
+Security 1.1.8
+Service User Mapper 1.0.0
+Service User Mapper 1.0.2
+Service User Mapper 1.0.4
+Service User Mapper 1.1.0
+Service User Mapper 1.2.0
+Service User Mapper 1.2.2
+Service User Mapper 1.2.4
+Service User Mapper 1.2.6
+Service User Mapper 1.3.0
+Service User Mapper 1.3.2
+Service User Mapper 1.3.4
+Service User Mapper 1.3.6
+Service User Mapper 1.4.0
+Service User Mapper 1.4.2
+Service User Mapper 1.4.4
+Service User Mapper 1.4.6
+Service User WebConsole 1.0.2
+Servlet Archetype 1.0.0
+Servlet Archetype 1.0.2
+Servlet Archetype 1.0.4
+Servlet Archetype 1.0.6
+Servlet Helpers 1.0.0
+Servlet Helpers 1.0.2
+Servlet Helpers 1.1.0
+Servlet Helpers 1.1.10
+Servlet Helpers 1.1.12
+Servlet Helpers 1.1.2
+Servlet Helpers 1.1.4
+Servlet Helpers 1.1.6
+Servlet Helpers 1.1.8
+Servlets Compat 1.0.0
+Servlets Get 2.0.2
+Servlets Get 2.0.4
+Servlets Get 2.0.6
+Servlets Get 2.0.8
+Servlets Get 2.1.0
+Servlets Get 2.1.10
+Servlets Get 2.1.12
+Servlets Get 2.1.14
+Servlets Get 2.1.18
+Servlets Get 2.1.2
+Servlets Get 2.1.20
+Servlets Get 2.1.22
+Servlets Get 2.1.24
+Servlets Get 2.1.26
+Servlets Get 2.1.28
+Servlets Get 2.1.30
+Servlets Get 2.1.32
+Servlets Get 2.1.34
+Servlets Get 2.1.36
+Servlets Get 2.1.38
+Servlets Get 2.1.4
+Servlets Get 2.1.40
+Servlets Get 2.1.42
+Servlets Get 2.1.6
+Servlets Get 2.1.8
+Servlets Post 2.0.2
+Servlets Post 2.0.4
+Servlets Post 2.1.0
+Servlets Post 2.1.2
+Servlets Post 2.2.0
+Servlets Post 2.3.0
+Servlets Post 2.3.10
+Servlets Post 2.3.12
+Servlets Post 2.3.14
+Servlets Post 2.3.16
+Servlets Post 2.3.18
+Servlets Post 2.3.2
+Servlets Post 2.3.20
+Servlets Post 2.3.22
+Servlets Post 2.3.24
+Servlets Post 2.3.26
+Servlets Post 2.3.28
+Servlets Post 2.3.30
+Servlets Post 2.3.4
+Servlets Post 2.3.6
+Servlets Post 2.3.8
+Servlets Resolver 2.0.4
+Servlets Resolver 2.0.8
+Servlets Resolver 2.1.0
+Servlets Resolver 2.1.2
+Servlets Resolver 2.2.0
+Servlets Resolver 2.2.4
+Servlets Resolver 2.3.0
+Servlets Resolver 2.3.2
+Servlets Resolver 2.3.4
+Servlets Resolver 2.3.6
+Servlets Resolver 2.3.8
+Servlets Resolver 2.4.0
+Servlets Resolver 2.4.10
+Servlets Resolver 2.4.12
+Servlets Resolver 2.4.14
+Servlets Resolver 2.4.2
+Servlets Resolver 2.4.20
+Servlets Resolver 2.4.22
+Servlets Resolver 2.4.24
+Servlets Resolver 2.4.4
+Servlets Resolver 2.4.6
+Servlets Resolver 2.4.8
+Servlets Resolver 2.5.2
+Settings 1.0.0
+Settings 1.0.2
+Settings 1.1.0
+Settings 1.2.0
+Settings 1.2.2
+Settings 1.3.0
+Settings 1.3.10
+Settings 1.3.12
+Settings 1.3.2
+Settings 1.3.4
+Settings 1.3.6
+Settings 1.3.8
+Sling Eclipse IDE 1.0.0
+Sling Eclipse IDE 1.0.10
+Sling Eclipse IDE 1.0.2
+Sling Eclipse IDE 1.0.4
+Sling Eclipse IDE 1.0.6
+Sling Eclipse IDE 1.0.8
+Sling Eclipse IDE 1.1.0
+Sling Eclipse IDE 1.2.0
+Sling Eclipse IDE 1.2.2
+Sling Eclipse IDE 1.2.4
+Sling Explorer 1.0.0
+Sling Explorer 1.0.2
+Sling Explorer 1.0.4
+Sling Explorer 1.0.6
+Sling JUnit Performance 1.0.2
+Sling JUnit Performance 1.0.4
+Sling Maven Plugin 2.4.0
+Sling Maven Plugin 2.4.2
+Sling Models API 1.0.0
+Sling Models API 1.0.2
+Sling Models API 1.1.0
+Sling Models API 1.2.0
+Sling Models API 1.2.2
+Sling Models API 1.3.0
+Sling Models API 1.3.10
+Sling Models API 1.3.2
+Sling Models API 1.3.4
+Sling Models API 1.3.6
+Sling Models API 1.3.8
+Sling Models Impl 1.2.0
+Sling Models Impl 1.2.2
+Sling Models Impl 1.2.4
+Sling Models Impl 1.2.6
+Sling Models Impl 1.2.8
+Sling Models Impl 1.3.0
+Sling Models Impl 1.3.2
+Sling Models Impl 1.3.4
+Sling Models Impl 1.3.6
+Sling Models Impl 1.3.8
+Sling Models Impl 1.4.0
+Sling Models Impl 1.4.10
+Sling Models Impl 1.4.12
+Sling Models Impl 1.4.2
+Sling Models Impl 1.4.4
+Sling Models Impl 1.4.6
+Sling Models Impl 1.4.8
+Sling Models Implementation 1.0.0
+Sling Models Implementation 1.0.2
+Sling Models Implementation 1.0.4
+Sling Models Implementation 1.0.6
+Sling Models Implementation 1.1.0
+Sling Models Jackson Exporter 1.0.0
+Sling Models Jackson Exporter 1.0.10
+Sling Models Jackson Exporter 1.0.2
+Sling Models Jackson Exporter 1.0.4
+Sling Models Jackson Exporter 1.0.6
+Sling Models Jackson Exporter 1.0.8
+Sling Models Validation Impl 1.0.0
+Sling Models bnd Plugin 1.0.0
+Sling Pax Exam Utilities 1.0.2
+Sling Pax Exam Utilities 1.0.4
+Sling Pax Exam Utilities 1.0.6
+Sling Provisioning Model 1.0.0
+Sling Provisioning Model 1.1.0
+Sling Provisioning Model 1.2.0
+Sling Provisioning Model 1.3.0
+Sling Provisioning Model 1.4.0
+Sling Provisioning Model 1.4.2
+Sling Provisioning Model 1.4.4
+Sling Provisioning Model 1.5.0
+Sling Provisioning Model 1.6.0
+Sling Provisioning Model 1.7.0
+Sling Provisioning Model 1.8.0
+Sling Provisioning Model 1.8.2
+Sling Provisioning Model 1.8.4
+Sling Provisioning Model 1.8.6
+Sling Query 2.0.0
+Sling Query 3.0.0
+Sling Query 4.0.0
+Sling Query 4.0.2
+Sling Query 4.0.4
+Sling Servlet Annotations 1.0.0
+Sling Servlet Annotations 1.1.0
+Sling Servlet Annotations 1.2.4
+Slingshot 0.8.0
+Slingshot 0.9.0
+Slingshot 0.9.2
+Slingstart Archetype 1.0.0
+Slingstart Archetype 1.0.2
+Slingstart Archetype 1.0.6
+Slingstart Archetype 1.0.8
+Slingstart Maven Plugin 1.0.0
+Slingstart Maven Plugin 1.0.2
+Slingstart Maven Plugin 1.0.4
+Slingstart Maven Plugin 1.1.0
+Slingstart Maven Plugin 1.2.0
+Slingstart Maven Plugin 1.3.0
+Slingstart Maven Plugin 1.3.2
+Slingstart Maven Plugin 1.3.4
+Slingstart Maven Plugin 1.3.6
+Slingstart Maven Plugin 1.4.0
+Slingstart Maven Plugin 1.4.2
+Slingstart Maven Plugin 1.4.4
+Slingstart Maven Plugin 1.5.0
+Slingstart Maven Plugin 1.6.0
+Slingstart Maven Plugin 1.7.0
+Slingstart Maven Plugin 1.7.10
+Slingstart Maven Plugin 1.7.14
+Slingstart Maven Plugin 1.7.16
+Slingstart Maven Plugin 1.7.2
+Slingstart Maven Plugin 1.7.4
+Slingstart Maven Plugin 1.7.6
+Slingstart Maven Plugin 1.7.8
+Slingstart Maven Plugin 1.8.2
+Slingstart Maven Plugin 1.8.4
+Starter 10
+Starter 11
+Starter 12
+Starter Content 1.0.0
+Starter Content 1.0.2
+Starter Content 1.0.4
+Starter Startup 1.0.2
+Starter Startup 1.0.4
+Starter Startup 1.0.6
+Starter Startup 1.0.8
+Superimposing Resource Provider 0.2.0
+Superimposing Resource Provider 0.4.0
+Taglib Archetype 1.0.0
+Tenant 1.0.0
+Tenant 1.0.2
+Tenant 1.1.0
+Tenant 1.1.2
+Tenant 1.1.4
+Tenant 1.1.6
+Testing Clients 1.1.6
+Testing Email 1.0.0
+Testing Email 1.0.2
+Testing Hamcrest 1.0.0
+Testing Hamcrest 1.0.2
+Testing Hamcrest 1.0.4
+Testing JCR Mock 1.0.0
+Testing JCR Mock 1.1.0
+Testing JCR Mock 1.1.10
+Testing JCR Mock 1.1.12
+Testing JCR Mock 1.1.14
+Testing JCR Mock 1.1.16
+Testing JCR Mock 1.1.2
+Testing JCR Mock 1.1.4
+Testing JCR Mock 1.1.6
+Testing JCR Mock 1.1.8
+Testing JCR Mock 1.2.0
+Testing JCR Mock 1.3.0
+Testing JCR Mock 1.3.2
+Testing JCR Mock 1.3.4
+Testing JCR Mock 1.3.6
+Testing JCR Mock 1.4.0
+Testing JCR Mock 1.4.2
+Testing JCR Mock 1.4.4
+Testing JCR Mock 1.4.6
+Testing Logging Mock 1.0.0
+Testing Logging Mock 2.0.0
+Testing Logging Mock 2.0.2
+Testing OSGi Mock 1.0.0
+Testing OSGi Mock 1.1.0
+Testing OSGi Mock 1.2.0
+Testing OSGi Mock 1.3.0
+Testing OSGi Mock 1.4.0
+Testing OSGi Mock 1.5.0
+Testing OSGi Mock 1.6.0
+Testing OSGi Mock 1.7.0
+Testing OSGi Mock 1.7.2
+Testing OSGi Mock 1.8.0
+Testing OSGi Mock 1.9.0
+Testing OSGi Mock 1.9.2
+Testing OSGi Mock 1.9.4
+Testing OSGi Mock 1.9.6
+Testing OSGi Mock 1.9.8
+Testing OSGi Mock 2.0.0
+Testing OSGi Mock 2.0.2
+Testing OSGi Mock 2.0.4
+Testing OSGi Mock 2.1.0
+Testing OSGi Mock 2.2.0
+Testing OSGi Mock 2.2.2
+Testing OSGi Mock 2.2.4
+Testing OSGi Mock 2.3.0
+Testing OSGi Mock 2.3.10
+Testing OSGi Mock 2.3.2
+Testing OSGi Mock 2.3.4
+Testing OSGi Mock 2.3.6
+Testing OSGi Mock 2.3.8
+Testing OSGi Mock 2.4.0
+Testing OSGi Mock 2.4.10
+Testing OSGi Mock 2.4.2
+Testing OSGi Mock 2.4.4
+Testing OSGi Mock 2.4.6
+Testing OSGi Mock 2.4.8
+Testing PaxExam 0.0.2
+Testing PaxExam 0.0.4
+Testing PaxExam 1.0.0
+Testing PaxExam 2.0.0
+Testing PaxExam 3.0.0
+Testing ResourceResolver Mock 0.1.0
+Testing ResourceResolver Mock 0.2.0
+Testing ResourceResolver Mock 0.3.0
+Testing ResourceResolver Mock 1.0.0
+Testing ResourceResolver Mock 1.1.0
+Testing ResourceResolver Mock 1.1.10
+Testing ResourceResolver Mock 1.1.12
+Testing ResourceResolver Mock 1.1.14
+Testing ResourceResolver Mock 1.1.16
+Testing ResourceResolver Mock 1.1.18
+Testing ResourceResolver Mock 1.1.2
+Testing ResourceResolver Mock 1.1.20
+Testing ResourceResolver Mock 1.1.22
+Testing ResourceResolver Mock 1.1.24
+Testing ResourceResolver Mock 1.1.26
+Testing ResourceResolver Mock 1.1.4
+Testing ResourceResolver Mock 1.1.6
+Testing ResourceResolver Mock 1.1.8
+Testing Rules 1.0.0
+Testing Sling Mock 1.0.0
+Testing Sling Mock 1.1.0
+Testing Sling Mock 1.1.2
+Testing Sling Mock 1.2.0
+Testing Sling Mock 1.3.0
+Testing Sling Mock 1.4.0
+Testing Sling Mock 1.5.0
+Testing Sling Mock 1.6.0
+Testing Sling Mock 1.6.2
+Testing Sling Mock 1.7.0
+Testing Sling Mock 1.8.0
+Testing Sling Mock 1.9.0
+Testing Sling Mock 1.9.10
+Testing Sling Mock 1.9.12
+Testing Sling Mock 1.9.2
+Testing Sling Mock 1.9.4
+Testing Sling Mock 1.9.6
+Testing Sling Mock 1.9.8
+Testing Sling Mock 2.0.0
+Testing Sling Mock 2.1.0
+Testing Sling Mock 2.1.2
+Testing Sling Mock 2.2.0
+Testing Sling Mock 2.2.10
+Testing Sling Mock 2.2.12
+Testing Sling Mock 2.2.14
+Testing Sling Mock 2.2.16
+Testing Sling Mock 2.2.18
+Testing Sling Mock 2.2.2
+Testing Sling Mock 2.2.20
+Testing Sling Mock 2.2.4
+Testing Sling Mock 2.2.6
+Testing Sling Mock 2.2.8
+Testing Sling Mock 2.3.0
+Testing Sling Mock 2.3.2
+Testing Sling Mock 2.3.4
+Testing Sling Mock 2.3.6
+Testing Sling Mock Jackrabbit 0.1.0
+Testing Sling Mock Jackrabbit 0.1.2
+Testing Sling Mock Jackrabbit 1.0.0
+Testing Sling Mock Oak 1.0.0
+Testing Sling Mock Oak 1.0.2
+Testing Sling Mock Oak 2.0.0
+Testing Sling Mock Oak 2.0.2
+Testing Sling Mock Oak 2.1.0
+Testing Sling Mock Oak 2.1.2
+Testing Sling Mock Oak 2.1.4
+Tooling Support Install 1.0.0
+Tooling Support Install 1.0.2
+Tooling Support Install 1.0.4
+Tooling Support Install 1.0.6
+Tooling Support Source 1.0.0
+Tooling Support Source 1.0.2
+Tooling Support Source 1.0.4
+Tooling Support Source 1.0.6
+URL Rewriter 0.0.2
+URL Rewriter 0.0.4
+Validation 1.0.0
+Validation API 1.0.2
+Validation Core 1.0.4
+Validation Core 1.0.6
+Version App CMS 0.11.4
+Web Console Security Provider 1.0.0
+Web Console Security Provider 1.1.0
+Web Console Security Provider 1.1.2
+Web Console Security Provider 1.1.4
+Web Console Security Provider 1.1.6
+Web Console Security Provider 1.2.0
+Web Console Security Provider 1.2.2
+XSS Protection API 1.0.0
+XSS Protection API 1.0.12
+XSS Protection API 1.0.14
+XSS Protection API 1.0.16
+XSS Protection API 1.0.18
+XSS Protection API 1.0.2
+XSS Protection API 1.0.4
+XSS Protection API 1.0.6
+XSS Protection API 1.0.8
+XSS Protection API 2.0.0
+XSS Protection API 2.0.10
+XSS Protection API 2.0.12
+XSS Protection API 2.0.14
+XSS Protection API 2.0.4
+XSS Protection API 2.0.6
+XSS Protection API 2.0.8
+XSS Protection API 2.1.0
+XSS Protection API 2.1.4
+XSS Protection API 2.2.0
+XSS Protection API Compat 1.1.0
+bnd Plugins 0.0.2
+commons metrics 1.2.4
+i18n 2.2.10
+i18n 2.2.2
+i18n 2.2.4
+i18n 2.2.6
+i18n 2.2.8
+i18n 2.3.2
+i18n 2.4.10
+i18n 2.4.2
+i18n 2.4.4
+i18n 2.4.6
+i18n 2.4.8
+i18n 2.5.0
+i18n 2.5.10
+i18n 2.5.12
+i18n 2.5.14
+i18n 2.5.2
+i18n 2.5.4
+i18n 2.5.6
+i18n 2.5.8
+javax.activation 0.1.0
+javax.activation 0.2.0
+org.apache.sling.testing.tools 1.0.10
+org.apache.sling.testing.tools 1.0.12
+org.apache.sling.testing.tools 1.0.14
+org.apache.sling.testing.tools 1.0.16
+org.apache.sling.testing.tools 1.0.4
+org.apache.sling.testing.tools 1.0.6
+org.apache.sling.testing.tools 1.0.8
+slingfeature-maven-plugin 0.8.0
+slingfeature-maven-plugin 1.0.0


[sling-org-apache-sling-committer-cli] 07/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 57f09426ae89abf713272307be1d7be0dcd771f3
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Mar 19 16:22:32 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Update README for update-local-site
---
 README.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/README.md b/README.md
index 45336b1..9dbad6e 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,10 @@ Generating a release vote email
 Generating a release vote result email
 
     docker run --env-file=./docker-env apache/sling-cli release tally-votes $STAGING_REPOSITORY_ID
+    
+Generating the website update (only diff for now)
+
+	docker run --env-file=docker-env apache/sling-cli release update-local-site $STAGING_REPOSITORY_ID
 
 ## Assumptions
 


[sling-org-apache-sling-committer-cli] 43/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 50b88dd5533da4522caaa49ead638dcb76d90cc6
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Apr 24 13:51:31 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Slightly better test for VersionClient.findUnresolvedIssues
---
 src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
index d279a43..6e98b4a 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
@@ -111,5 +111,7 @@ public class VersionClientTest {
         List<Issue> issues = versionClient.findUnresolvedIssues(Release.fromString("Committer CLI 1.0.0").get(0));
         
         assertThat(issues, hasSize(2));
+        assertThat(issues.get(0).getKey(), equalTo("SLING-8338"));
+        assertThat(issues.get(1).getKey(), equalTo("SLING-8337"));
     }
 }


[sling-org-apache-sling-committer-cli] 16/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 9da1f2c23f6265b62a3fbe68051d0cedf0a4d43c
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu Mar 28 17:48:08 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * no need to customise the SSLContext
---
 .../cli/impl/release/UpdateReporterCommand.java    | 23 ++--------------------
 1 file changed, 2 insertions(+), 21 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
index 4c0c127..0a33043 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
@@ -20,17 +20,11 @@ package org.apache.sling.cli.impl.release;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLEngine;
-import javax.net.ssl.SSLSocket;
-
 import org.apache.http.NameValuePair;
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
@@ -38,8 +32,6 @@ import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.conn.ssl.DefaultHostnameVerifier;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
@@ -79,19 +71,8 @@ public class UpdateReporterCommand implements Command {
         try {
             StagingRepository repository = repoFinder.find(Integer.parseInt(target));
             Release release = Release.fromString(repository.getDescription());
-            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
-            sslContext.init(null, null, null);
-            SSLContext.setDefault(sslContext);
-            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
-                    sslContext,
-                    new String[] {"TLSv1.2"},
-                    new String[] {
-                            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
-                    },
-                    new DefaultHostnameVerifier()
-            );
             try (CloseableHttpClient client =
-                         HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).setSSLSocketFactory(sslConnectionSocketFactory).build()) {
+                         HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).build()) {
                 HttpPost post = new HttpPost("https://reporter.apache.org/addrelease.py");
                 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
                 List<NameValuePair> parameters = new ArrayList<>();
@@ -108,7 +89,7 @@ public class UpdateReporterCommand implements Command {
                     }
                 }
             }
-        } catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {
+        } catch (IOException e) {
             LOGGER.error(String.format("Unable to update reporter service; passed command: %s.", target), e);
         }
 


[sling-org-apache-sling-committer-cli] 37/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 7deca18cc89a9f6b1c7eff019ce87c04c0d441cc
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 16:14:37 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting
    a release
    
    Move the VersionClient to be an HTTP-based test in preparation for
    testing commands that change state.
---
 pom.xml                                            |  9 +--
 .../sling/cli/impl/ComponentContextHelper.java     | 47 ++++++++++++
 .../sling/cli/impl/http/HttpClientFactory.java     | 17 ++++-
 .../apache/sling/cli/impl/jira/VersionClient.java  | 28 ++++---
 .../org/apache/sling/cli/impl/jira/MockJira.java   | 86 ++++++++++++++++++++++
 .../sling/cli/impl/jira/VersionClientTest.java     | 50 +++----------
 6 files changed, 182 insertions(+), 55 deletions(-)

diff --git a/pom.xml b/pom.xml
index 6fa560a..bf1224a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -164,6 +164,10 @@
             <artifactId>osgi.core</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.feature.launcher</artifactId>
             <version>0.8.0</version>
@@ -231,10 +235,5 @@
             <version>1.3.0</version>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>org.osgi</groupId>
-            <artifactId>osgi.cmpn</artifactId>
-            <scope>test</scope>
-        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/cli/impl/ComponentContextHelper.java b/src/main/java/org/apache/sling/cli/impl/ComponentContextHelper.java
new file mode 100644
index 0000000..b996e88
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/ComponentContextHelper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.cli.impl;
+
+import org.osgi.service.component.ComponentContext;
+
+public class ComponentContextHelper {
+
+    public static ComponentContextHelper wrap(ComponentContext wrapped) {
+   
+        return new ComponentContextHelper(wrapped);
+    }
+
+    private final ComponentContext wrapped;
+
+    public ComponentContextHelper(ComponentContext wrapped) {
+        this.wrapped = wrapped;
+    }
+    
+    public String getProperty(String name, String fallback) {
+        Object prop = wrapped.getProperties().get(name);
+        if ( prop != null) {
+            return prop.toString();
+        }
+        
+        return fallback;
+    }
+    
+    public int getProperty(String name, int fallback) {
+        
+        return Integer.parseInt(getProperty(name, String.valueOf(fallback)));
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java b/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java
index 7975145..f881594 100644
--- a/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java
+++ b/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java
@@ -21,16 +21,31 @@ import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.ComponentContextHelper;
 import org.apache.sling.cli.impl.Credentials;
 import org.apache.sling.cli.impl.CredentialsService;
+import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
 @Component(service = HttpClientFactory.class)
 public class HttpClientFactory {
     
+    private static final String DEFAULT_JIRA_HOST = "jira.apache.org";
+    private static final int DEFAULT_JIRA_PORT = 443;
+    
     @Reference
     private CredentialsService credentialsService;
+    
+    private String jiraHost;
+    private int jiraPort;
+    
+    protected void activate(ComponentContext ctx) {
+        
+        ComponentContextHelper helper = ComponentContextHelper.wrap(ctx);
+        jiraHost = helper.getProperty("jira.host", DEFAULT_JIRA_HOST);
+        jiraPort = helper.getProperty("jira.port", DEFAULT_JIRA_PORT);
+    }
 
     public CloseableHttpClient newClient() {
         
@@ -42,7 +57,7 @@ public class HttpClientFactory {
                 new UsernamePasswordCredentials(asf.getUsername(), asf.getPassword()));
         credentialsProvider.setCredentials(new AuthScope("reporter.apache.org", 443), 
                 new UsernamePasswordCredentials(asf.getUsername(), asf.getPassword()));
-        credentialsProvider.setCredentials(new AuthScope("jira.apache.org", 443), 
+        credentialsProvider.setCredentials(new AuthScope(jiraHost, jiraPort), 
                 new UsernamePasswordCredentials(jira.getUsername(), jira.getPassword()));
         
         return HttpClients.custom()
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 77e6881..0c11c3c 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -32,8 +32,10 @@ import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.cli.impl.ComponentContextHelper;
 import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.release.Release;
+import org.osgi.service.component.ComponentContext;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
@@ -47,11 +49,17 @@ import com.google.gson.stream.JsonWriter;
 @Component(service = VersionClient.class)
 public class VersionClient {
     
-    private static final String JIRA_URL_PREFIX = "https://issues.apache.org/jira/rest/api/2/";
+    private static final String DEFAULT_JIRA_URL_PREFIX = "https://issues.apache.org/jira/rest/api/2/";
     private static final String CONTENT_TYPE_JSON = "application/json";
     
     @Reference
     private HttpClientFactory httpClientFactory;
+    private String jiraUrlPrefix;
+    
+    protected void activate(ComponentContext ctx) {
+        ComponentContextHelper helper = ComponentContextHelper.wrap(ctx);
+        jiraUrlPrefix = helper.getProperty("jira.url.prefix", DEFAULT_JIRA_URL_PREFIX);
+    }
 
     /**
      * Finds a Jira version which matches the specified release
@@ -98,12 +106,8 @@ public class VersionClient {
         
         try (CloseableHttpClient client = httpClientFactory.newClient()) {
             Optional<Version> opt = findVersion ( 
-                    v -> {
-                        Release releaseFromVersion = Release.fromString(v.getName()).get(0);
-                        return 
-                            releaseFromVersion.getComponent().equals(release.getComponent())
-                                && new org.osgi.framework.Version(releaseFromVersion.getVersion()).compareTo(new org.osgi.framework.Version(release.getVersion())) > 0;
-                    },client);
+                    v -> isFollowingVersion(Release.fromString(v.getName()).get(0), release)
+                    ,client);
             if ( !opt.isPresent() )
                 return null;
             version = opt.get();
@@ -115,6 +119,12 @@ public class VersionClient {
         return version;
     }
     
+    private boolean isFollowingVersion(Release base, Release candidate) {
+        return base.getComponent().equals(candidate.getComponent())
+                && new org.osgi.framework.Version(base.getVersion())
+                    .compareTo(new org.osgi.framework.Version(candidate.getVersion())) > 0;
+    }
+    
     public void create(String versionName) throws IOException {
         StringWriter w = new StringWriter();
         try ( JsonWriter jw = new Gson().newJsonWriter(w) ) {
@@ -124,7 +134,7 @@ public class VersionClient {
             jw.endObject();
         }
         
-        HttpPost post = new HttpPost(JIRA_URL_PREFIX + "version");
+        HttpPost post = new HttpPost(jiraUrlPrefix + "version");
         post.addHeader("Content-Type", CONTENT_TYPE_JSON);
         post.addHeader("Accept", CONTENT_TYPE_JSON);
         post.setEntity(new StringEntity(w.toString()));
@@ -190,7 +200,7 @@ public class VersionClient {
     }
     
     private HttpGet newGet(String suffix) {
-        HttpGet get = new HttpGet(JIRA_URL_PREFIX + suffix);
+        HttpGet get = new HttpGet(jiraUrlPrefix + suffix);
         get.addHeader("Accept", CONTENT_TYPE_JSON);
         return get;
     }
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
new file mode 100644
index 0000000..903f8b0
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
@@ -0,0 +1,86 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.rules.ExternalResource;
+
+import com.sun.net.httpserver.HttpServer;
+
+public class MockJira extends ExternalResource {
+    
+    private static final Pattern VERSION_RELATED_ISSUES = Pattern.compile("^/jira/rest/api/2/version/(\\d+)/relatedIssueCounts$");
+    
+    public static void main(String[] args) throws Throwable {
+        
+        MockJira mj = new MockJira();
+        mj.before();
+        System.out.println(mj.getBoundPort());
+    }
+
+    private HttpServer server;
+    
+    @Override
+    protected void before() throws Throwable {
+        server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
+        server.createContext("/", httpExchange -> {
+            
+            if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/project/SLING/versions") ) {
+                httpExchange.sendResponseHeaders(200, 0);
+                try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
+                        OutputStream out = httpExchange.getResponseBody() ) {
+                    IOUtils.copy(in, out);
+                }
+            } else {
+                Matcher matcher = VERSION_RELATED_ISSUES.matcher(httpExchange.getRequestURI().getPath());
+                if ( matcher.matches() ) {
+                    int version = Integer.parseInt(matcher.group(1));
+                    InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
+                    if ( in == null  ) {
+                        httpExchange.sendResponseHeaders(404, -1);
+                    } else {
+                        httpExchange.sendResponseHeaders(200, 0);
+                        try ( OutputStream out = httpExchange.getResponseBody() ) {
+                            IOUtils.copy(in, out);
+                        }
+                    }
+                } else {
+                    httpExchange.sendResponseHeaders(400, -1);
+                }
+            }
+        });
+        
+        server.start();
+    }
+    
+    @Override
+    protected void after() {
+        
+        server.stop(0);
+    }
+    
+    public int getBoundPort() {
+        
+        return server.getAddress().getPort();
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
index 0cac72d..0d67dee 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
@@ -21,15 +21,9 @@ import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertThat;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.function.Function;
 
-import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.sling.cli.impl.CredentialsService;
 import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.release.Release;
@@ -42,10 +36,10 @@ public class VersionClientTest {
 
     private static final Map<String, String> SYSTEM_PROPS = new HashMap<>();
     static {
-        SYSTEM_PROPS.put("asf.username", "user");
-        SYSTEM_PROPS.put("asf.password", "password");
-        SYSTEM_PROPS.put("jira.username", "user");
-        SYSTEM_PROPS.put("jira.password", "password");
+        SYSTEM_PROPS.put("asf.username", "asf-user");
+        SYSTEM_PROPS.put("asf.password", "asf-password");
+        SYSTEM_PROPS.put("jira.username", "jira-user");
+        SYSTEM_PROPS.put("jira.password", "jira-password");
     }
     
     @Rule
@@ -54,17 +48,17 @@ public class VersionClientTest {
     @Rule
     public final SystemPropertiesRule sysProps = new SystemPropertiesRule(SYSTEM_PROPS);
     
+    @Rule
+    public final MockJira mockJira = new MockJira();
+    
     private VersionClient versionClient;
     
     @Before
     public void prepareDependencies() throws ReflectiveOperationException {
-        context.registerInjectActivateService(new CredentialsService());
-        context.registerInjectActivateService(new HttpClientFactory());
         
-        versionClient = new StubVersionClient();
-        Field factoryField = VersionClient.class.getDeclaredField("httpClientFactory");
-        factoryField.setAccessible(true);
-        factoryField.set(versionClient, context.getService(HttpClientFactory.class));
+        context.registerInjectActivateService(new CredentialsService());
+        context.registerInjectActivateService(new HttpClientFactory(), "jira.host", "localhost", "jira.port", mockJira.getBoundPort());
+        versionClient = context.registerInjectActivateService(new VersionClient(), "jira.url.prefix", "http://localhost:" + mockJira.getBoundPort() + "/jira/rest/api/2/");
     }
     
     @Test
@@ -98,28 +92,4 @@ public class VersionClientTest {
         
         assertThat("successor", successor, nullValue());
     }
-    
-    private static final class StubVersionClient extends VersionClient {
-        @Override
-        protected <T> T doWithJiraVersions(CloseableHttpClient client, Function<InputStreamReader, T> parserCallback)
-                throws IOException {
-            
-            try ( InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/jira/versions.json")) ) {
-                return parserCallback.apply(reader);
-            }
-        }
-        
-        @Override
-        protected <T> T doWithRelatedIssueCounts(CloseableHttpClient client, Version version,
-                Function<InputStreamReader, T> parserCallback) throws IOException {
-            
-            InputStream stream = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version.getId()+".json");
-            if ( stream == null )
-                throw new IllegalArgumentException("No related issues count for version " + version.getId() + " (" + version.getName() + ")");
-            
-            try ( InputStreamReader reader = new InputStreamReader(stream) ) {
-                return parserCallback.apply(reader);
-            }
-        }
-    }
 }


[sling-org-apache-sling-committer-cli] 38/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit eeb567eb906f6afdbf11082f02a326a6dd23f524
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 16:22:03 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Simplify the VersionClient
---
 .../apache/sling/cli/impl/jira/VersionClient.java  | 62 ++++++++--------------
 1 file changed, 23 insertions(+), 39 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 0c11c3c..39419a9 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -24,7 +24,6 @@ import java.io.StringWriter;
 import java.lang.reflect.Type;
 import java.util.List;
 import java.util.Optional;
-import java.util.function.Function;
 import java.util.function.Predicate;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
@@ -119,12 +118,6 @@ public class VersionClient {
         return version;
     }
     
-    private boolean isFollowingVersion(Release base, Release candidate) {
-        return base.getComponent().equals(candidate.getComponent())
-                && new org.osgi.framework.Version(base.getVersion())
-                    .compareTo(new org.osgi.framework.Version(candidate.getVersion())) > 0;
-    }
-    
     public void create(String versionName) throws IOException {
         StringWriter w = new StringWriter();
         try ( JsonWriter jw = new Gson().newJsonWriter(w) ) {
@@ -161,19 +154,6 @@ public class VersionClient {
 
     private Optional<Version> findVersion(Predicate<Version> matcher, CloseableHttpClient client) throws IOException {
         
-        return doWithJiraVersions(client, reader -> {
-            Gson gson = new Gson();
-            Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
-            List<Version> versions = gson.fromJson(reader, collectionType);
-            return versions.stream()
-                    .filter( v -> v.getName().length() > 1) // avoid old '3' release
-                    .filter(matcher)
-                    .sorted(VersionClient::compare)
-                    .findFirst();
-        });
-    }
-    
-    protected <T> T doWithJiraVersions(CloseableHttpClient client, Function<InputStreamReader, T> parserCallback) throws IOException {
         HttpGet get = newGet("project/SLING/versions");
         try (CloseableHttpResponse response = client.execute(get)) {
             try (InputStream content = response.getEntity().getContent();
@@ -181,12 +161,25 @@ public class VersionClient {
                 if (response.getStatusLine().getStatusCode() != 200)
                     throw new IOException("Status line : " + response.getStatusLine());
                 
-                return parserCallback.apply(reader);
+                Gson gson = new Gson();
+                Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
+                List<Version> versions = gson.fromJson(reader, collectionType);
+                return versions.stream()
+                        .filter( v -> v.getName().length() > 1) // avoid old '3' release
+                        .filter(matcher)
+                        .sorted(VersionClient::compare)
+                        .findFirst();
             }
         }
     }
+        
+    private HttpGet newGet(String suffix) {
+        HttpGet get = new HttpGet(jiraUrlPrefix + suffix);
+        get.addHeader("Accept", CONTENT_TYPE_JSON);
+        return get;
+    }
     
-    protected <T> T doWithRelatedIssueCounts(CloseableHttpClient client, Version version, Function<InputStreamReader, T> parserCallback) throws IOException {
+    private void populateRelatedIssuesCount(CloseableHttpClient client, Version version) throws IOException {
         
         HttpGet get = newGet("version/" + version.getId() +"/relatedIssueCounts");
         try (CloseableHttpResponse response = client.execute(get)) {
@@ -194,27 +187,18 @@ public class VersionClient {
                     InputStreamReader reader = new InputStreamReader(content)) {
                 if (response.getStatusLine().getStatusCode() != 200)
                     throw new IOException("Status line : " + response.getStatusLine());
-                return parserCallback.apply(reader);
+                Gson gson = new Gson();
+                VersionRelatedIssuesCount issuesCount = gson.fromJson(reader, VersionRelatedIssuesCount.class);
+                
+                version.setRelatedIssuesCount(issuesCount.getIssuesFixedCount());
             }
         }
     }
-    
-    private HttpGet newGet(String suffix) {
-        HttpGet get = new HttpGet(jiraUrlPrefix + suffix);
-        get.addHeader("Accept", CONTENT_TYPE_JSON);
-        return get;
-    }
-    
-    private void populateRelatedIssuesCount(CloseableHttpClient client, Version version) throws IOException {
-        
-        doWithRelatedIssueCounts(client, version, reader ->  {
-            Gson gson = new Gson();
-            VersionRelatedIssuesCount issuesCount = gson.fromJson(reader, VersionRelatedIssuesCount.class);
-            
-            version.setRelatedIssuesCount(issuesCount.getIssuesFixedCount());
 
-            return null;
-        });
+    private boolean isFollowingVersion(Release base, Release candidate) {
+        return base.getComponent().equals(candidate.getComponent())
+                && new org.osgi.framework.Version(base.getVersion())
+                    .compareTo(new org.osgi.framework.Version(candidate.getVersion())) > 0;
     }
     
     private static int compare(Version v1, Version v2) {


[sling-org-apache-sling-committer-cli] 02/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit d37ac21fe621edbdd86d6e637cca45d712af2616
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Mar 8 14:06:42 2019 +0200

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Document assumptions.
---
 README.md | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index eaa6d43..af04f3f 100644
--- a/README.md
+++ b/README.md
@@ -20,4 +20,13 @@ This invocation produces a list of available subcommands.
 
 Currently the only implemented command is generating the release vote email, for instance
 
-    docker run --env-file=./docker-env apache/sling-cli release prepare-email $STAGING_REPOSITORY_ID
\ No newline at end of file
+    docker run --env-file=./docker-env apache/sling-cli release prepare-email $STAGING_REPOSITORY_ID
+    
+## Assumptions
+
+This tool assumes that the name of the staging repository matches the one of the version in Jira. For instance, the
+staging repositories are usually named _Apache Sling Foo 1.2.0_. It is then expected that the Jira version is
+named _Foo 1.2.0_. Otherwise the link between the staging repository and the Jira release can not be found.
+
+It is allowed for staging repository names to have an _RC_ suffix, which may include a number, so that _RC_, _RC1_, _RC25_ are
+all valid suffixes.  
\ No newline at end of file


[sling-org-apache-sling-committer-cli] 42/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 6ef8c7286e12f78b0e2c0203b274b89796432457
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Apr 24 13:48:00 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Implement VersionClient.findUnresolvedIssues
---
 .../apache/sling/cli/impl/jira/VersionClient.java  | 49 ++++++++++++++++----
 ...GetRelatedIssueCountsForVersionsJiraAction.java | 19 ++------
 .../cli/impl/jira/IssuesSearchJiraAction.java      | 53 ++++++++++++++++++++++
 .../org/apache/sling/cli/impl/jira/JiraAction.java | 16 +++++++
 .../cli/impl/jira/ListVersionsJiraAction.java      | 11 +----
 .../org/apache/sling/cli/impl/jira/MockJira.java   |  4 +-
 .../sling/cli/impl/jira/VersionClientTest.java     |  9 ++++
 src/test/resources/README.md                       |  1 +
 .../search/unresolved-committer-cli-1.0.0.json     | 26 +++++++++++
 9 files changed, 153 insertions(+), 35 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 829a4da..0cf8eea 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -21,6 +21,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.StringWriter;
 import java.lang.reflect.Type;
+import java.net.URISyntaxException;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.Predicate;
@@ -28,6 +29,7 @@ import java.util.function.Predicate;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.utils.URIBuilder;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.sling.cli.impl.ComponentContextHelper;
@@ -154,8 +156,36 @@ public class VersionClient {
             }
         }
     }
+    
+    public List<Issue> findUnresolvedIssues(Release release) throws IOException {
+        
+        try {
+            HttpGet get = newGet("search");
+            URIBuilder builder = new URIBuilder(get.getURI());
+            builder.addParameter("jql", "project = "+ PROJECT_KEY+" AND resolution = Unresolved AND fixVersion = \"" + release.getName() + "\"");
+            builder.addParameter("fields", "summary");
+            get.setURI(builder.build());
+            
+            try ( CloseableHttpClient client = httpClientFactory.newClient() ) {
+                try (CloseableHttpResponse response = client.execute(get)) {
+                    try (InputStream content = response.getEntity().getContent();
+                            InputStreamReader reader = new InputStreamReader(content)) {
+                        
+                        if (response.getStatusLine().getStatusCode() != 200) {
+                            throw newException(response, reader);
+                        }
+                        
+                        Gson gson = new Gson();
+                        return gson.fromJson(reader, IssueResponse.class).getIssues();
+                    }
+                }
+            }
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
 
-    private IOException newException(CloseableHttpResponse response, InputStreamReader reader) throws IOException {
+    private IOException newException(CloseableHttpResponse response, InputStreamReader reader) {
         
         StringBuilder message = new StringBuilder();
         message.append("Status line : " + response.getStatusLine());
@@ -163,14 +193,15 @@ public class VersionClient {
         try {
             Gson gson = new Gson();
             ErrorResponse errors = gson.fromJson(reader, ErrorResponse.class);
-            if ( !errors.getErrorMessages().isEmpty() )
-                message.append(". Error messages: ")
-                    .append(errors.getErrorMessages());
-            
-            if ( !errors.getErrors().isEmpty() )
-                errors.getErrors().entrySet().stream()
-                    .forEach( e -> message.append(". Error for "  + e.getKey() + " : " + e.getValue()));
-            
+            if ( errors != null ) {
+                if ( !errors.getErrorMessages().isEmpty() )
+                    message.append(". Error messages: ")
+                        .append(errors.getErrorMessages());
+                
+                if ( !errors.getErrors().isEmpty() )
+                    errors.getErrors().entrySet().stream()
+                        .forEach( e -> message.append(". Error for "  + e.getKey() + " : " + e.getValue()));
+            }
         } catch ( JsonIOException | JsonSyntaxException e) {
             message.append(". Failed parsing response as JSON ( ")
                 .append(e.getMessage())
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/GetRelatedIssueCountsForVersionsJiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/GetRelatedIssueCountsForVersionsJiraAction.java
index aa9616f..fd134e8 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/GetRelatedIssueCountsForVersionsJiraAction.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/GetRelatedIssueCountsForVersionsJiraAction.java
@@ -17,13 +17,9 @@
 package org.apache.sling.cli.impl.jira;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.commons.io.IOUtils;
-
 import com.sun.net.httpserver.HttpExchange;
 
 public class GetRelatedIssueCountsForVersionsJiraAction implements JiraAction {
@@ -40,17 +36,10 @@ public class GetRelatedIssueCountsForVersionsJiraAction implements JiraAction {
             return false;
         
         int version = Integer.parseInt(matcher.group(1));
-        InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
-        if ( in == null  ) {
-            ex.sendResponseHeaders(404, -1);
-        } else {
-            ex.sendResponseHeaders(200, 0);
-            try ( OutputStream out = ex.getResponseBody() ) {
-                IOUtils.copy(in, out);
-            }
-        }
-
-        return false;
+        
+        serveFileFromClasspath(ex, "/jira/relatedIssueCounts/" + version + ".json");
+
+        return true;
     }
 
 }
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/IssuesSearchJiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/IssuesSearchJiraAction.java
new file mode 100644
index 0000000..96df4c7
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/IssuesSearchJiraAction.java
@@ -0,0 +1,53 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.commons.io.Charsets;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.utils.URLEncodedUtils;
+
+import com.google.gson.Gson;
+import com.sun.net.httpserver.HttpExchange;
+
+public class IssuesSearchJiraAction implements JiraAction {
+    
+    static final String KNOWN_JQL_QUERY = "project = SLING AND resolution = Unresolved AND fixVersion = \"Committer CLI 1.0.0\""; 
+
+    @Override
+    public boolean tryHandle(HttpExchange ex) throws IOException {
+        if ( !ex.getRequestMethod().equals("GET") || 
+               !ex.getRequestURI().getPath().equals("/jira/rest/api/2/search") ) {
+            return false;
+        }
+        
+        List<NameValuePair> parsed = URLEncodedUtils.parse(ex.getRequestURI(), Charsets.UTF_8);
+        
+        for ( NameValuePair pair : parsed ) {
+            if ( "jql".equals(pair.getName()) && KNOWN_JQL_QUERY.equals(pair.getValue()) ) {
+                serveFileFromClasspath(ex, "/jira/search/unresolved-committer-cli-1.0.0.json");
+                return true;
+            }
+        }
+        error(ex, new Gson(), er -> er.getErrorMessages().add("Unable to run unknown JQL query, only available one is [" + KNOWN_JQL_QUERY +"]"));
+        
+        return true;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/JiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/JiraAction.java
index 6cbc1b9..8d84a3a 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/JiraAction.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/JiraAction.java
@@ -17,9 +17,12 @@
 package org.apache.sling.cli.impl.jira;
 
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.util.function.Consumer;
 
+import org.apache.commons.io.IOUtils;
 import org.apache.sling.cli.impl.jira.ErrorResponse;
 
 import com.google.gson.Gson;
@@ -36,5 +39,18 @@ public interface JiraAction {
         }
     }
     
+    default void serveFileFromClasspath(HttpExchange ex, String classpathLocation) throws IOException {
+        InputStream in = getClass().getResourceAsStream(classpathLocation);
+        if ( in == null  ) {
+            ex.sendResponseHeaders(404, -1);
+            return;
+        }
+     
+        ex.sendResponseHeaders(200, 0);
+        try ( OutputStream out = ex.getResponseBody() ) {
+            IOUtils.copy(in, out);
+        }
+    }
+    
     boolean tryHandle(HttpExchange ex) throws IOException;
 }
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
index 30ce879..acce035 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
@@ -1,10 +1,6 @@
 package org.apache.sling.cli.impl.jira;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.io.IOUtils;
 
 import com.sun.net.httpserver.HttpExchange;
 
@@ -18,12 +14,7 @@ public class ListVersionsJiraAction implements JiraAction {
             return false;
         }
         
-        ex.sendResponseHeaders(200, 0);
-        try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
-                OutputStream out = ex.getResponseBody() ) {
-            IOUtils.copy(in, out);
-        }
-        
+        serveFileFromClasspath(ex, "/jira/versions.json");
         return true;
     }
 }
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
index e26dd87..9dcffcd 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
@@ -57,8 +57,9 @@ public class MockJira extends ExternalResource {
             
             @Override
             public Result authenticate(HttpExchange t) {
+                // get requests are never authenticated
                 if ( t.getRequestMethod().contentEquals("GET") )
-                        return new Authenticator.Success(new HttpPrincipal("anonymous", getClass().getSimpleName()));
+                    return new Authenticator.Success(new HttpPrincipal("anonymous", getClass().getSimpleName()));
                 return super.authenticate(t);
             }
         });
@@ -67,6 +68,7 @@ public class MockJira extends ExternalResource {
         actions.add(new ListVersionsJiraAction());
         actions.add(new GetRelatedIssueCountsForVersionsJiraAction());
         actions.add(new CreateVersionJiraAction());
+        actions.add(new IssuesSearchJiraAction());
         
         // fallback, always executed
         actions.add(new JiraAction() {
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
index 2951b3b..d279a43 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
@@ -17,12 +17,14 @@
 package org.apache.sling.cli.impl.jira;
 
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
 import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertThat;
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.sling.cli.impl.CredentialsService;
@@ -103,4 +105,11 @@ public class VersionClientTest {
     public void illegalVersionFails() throws IOException {
         versionClient.create("");
     }
+    
+    @Test
+    public void findUnresolvedIssuesForVersion() throws IOException {
+        List<Issue> issues = versionClient.findUnresolvedIssues(Release.fromString("Committer CLI 1.0.0").get(0));
+        
+        assertThat(issues, hasSize(2));
+    }
 }
diff --git a/src/test/resources/README.md b/src/test/resources/README.md
index 3158b2f..76be9cb 100644
--- a/src/test/resources/README.md
+++ b/src/test/resources/README.md
@@ -8,4 +8,5 @@ These resources are downloaded from remote servers and maybe slightly tweaked to
 http https://issues.apache.org/jira/rest/api/2/project/SLING/versions | jq '.[0:199]' > src/test/resources/jira/versions.json
 http https://issues.apache.org/jira/rest/api/2/version/12329667/relatedIssueCounts | jq '.' > src/test/resources/jira/relatedIssueCounts/12329667.json
 http https://issues.apache.org/jira/rest/api/2/version/12329844/relatedIssueCounts | jq '.' > src/test/resources/jira/relatedIssueCounts/12329844.json
+http https://issues.apache.org/jira/rest/api/2/search 'jql==project = SLING AND resolution = Unresolved AND fixVersion = "Committer CLI 1.0.0"' 'fields==summary' | jq '.' > src/test/resources/jira/search/unresolved-committer-cli-1.0.0.json
 ```
\ No newline at end of file
diff --git a/src/test/resources/jira/search/unresolved-committer-cli-1.0.0.json b/src/test/resources/jira/search/unresolved-committer-cli-1.0.0.json
new file mode 100644
index 0000000..6a01267
--- /dev/null
+++ b/src/test/resources/jira/search/unresolved-committer-cli-1.0.0.json
@@ -0,0 +1,26 @@
+{
+  "expand": "schema,names",
+  "startAt": 0,
+  "maxResults": 50,
+  "total": 2,
+  "issues": [
+    {
+      "expand": "operations,versionedRepresentations,editmeta,changelog,renderedFields",
+      "id": "13225243",
+      "self": "https://issues.apache.org/jira/rest/api/2/issue/13225243",
+      "key": "SLING-8338",
+      "fields": {
+        "summary": "Create sub-command to manage the Nexus stage repository release when promoting a release"
+      }
+    },
+    {
+      "expand": "operations,versionedRepresentations,editmeta,changelog,renderedFields",
+      "id": "13224868",
+      "self": "https://issues.apache.org/jira/rest/api/2/issue/13224868",
+      "key": "SLING-8337",
+      "fields": {
+        "summary": "Create sub-command to manage the Jira update when promoting a release"
+      }
+    }
+  ]
+}


[sling-org-apache-sling-committer-cli] 41/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit ff616fd463a3820fd2e454b0d417890c90c52de5
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Apr 24 13:29:59 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Simplify MockJira
---
 .../java/org/apache/sling/cli/impl/jira/Issue.java |  45 ++++++
 .../apache/sling/cli/impl/jira/IssueResponse.java  |  28 ++++
 .../cli/impl/jira/CreateVersionJiraAction.java     | 126 ++++++++++++++++
 ...GetRelatedIssueCountsForVersionsJiraAction.java |  56 +++++++
 .../org/apache/sling/cli/impl/jira/JiraAction.java |  40 +++++
 .../cli/impl/jira/ListVersionsJiraAction.java      |  29 ++++
 .../org/apache/sling/cli/impl/jira/MockJira.java   | 166 +++------------------
 7 files changed, 344 insertions(+), 146 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/jira/Issue.java b/src/main/java/org/apache/sling/cli/impl/jira/Issue.java
new file mode 100644
index 0000000..425f5e3
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/jira/Issue.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cli.impl.jira;
+
+public class Issue {
+
+    private int id;
+    private String key;
+    private Fields fields;
+    
+    public int getId() {
+        return id;
+    }
+    
+    public String getKey() {
+        return key;
+    }
+    
+    public String getSummary() {
+        return fields.summary;
+    }
+    
+    static class Fields {
+        private String summary;    
+    }
+    
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + "[" +  key + " : " + getSummary() + " ]"; 
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/IssueResponse.java b/src/main/java/org/apache/sling/cli/impl/jira/IssueResponse.java
new file mode 100644
index 0000000..0cf5697
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/jira/IssueResponse.java
@@ -0,0 +1,28 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.util.List;
+
+public class IssueResponse {
+
+    private List<Issue> issues;
+    
+    public List<Issue> getIssues() {
+        return issues;
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/CreateVersionJiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/CreateVersionJiraAction.java
new file mode 100644
index 0000000..09837bf
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/CreateVersionJiraAction.java
@@ -0,0 +1,126 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.concurrent.ThreadLocalRandom;
+
+import com.google.gson.Gson;
+import com.sun.net.httpserver.HttpExchange;
+
+public class CreateVersionJiraAction implements JiraAction {
+
+    @Override
+    public boolean tryHandle(HttpExchange ex) throws IOException {
+        if ( !ex.getRequestMethod().equals("POST") || 
+                !ex.getRequestURI().getPath().equals("/jira/rest/api/2/version") ) {
+            return false;
+        }
+
+        Gson gson = new Gson();
+        try ( InputStreamReader reader = new InputStreamReader(ex.getRequestBody())) {
+            VersionToCreate version = gson.fromJson(reader, VersionToCreate.class);
+            if ( version.getName() == null || version.getName().isEmpty() ) {
+                error(ex, gson, 
+                        er -> er.getErrors().put("name", "You must specify a valid version name"));
+                return true;
+            }
+            
+            if ( !"SLING".equals(version.getProject()) ) {
+                error(ex, gson, 
+                        er -> er.getErrorMessages().add("Project must be specified to create a version."));
+                return true;
+            }
+            
+            // note not all fields are sent, projectId and self are missing
+            CreatedVersion createdVersion = new CreatedVersion();
+            createdVersion.archived = false;
+            createdVersion.id = ThreadLocalRandom.current().nextInt();
+            createdVersion.released = false;
+            createdVersion.name = version.getName();
+            
+            try ( OutputStreamWriter out = new OutputStreamWriter(ex.getResponseBody()) ) {
+                ex.sendResponseHeaders(201, 0);
+                gson.toJson(createdVersion, out);
+            }
+        }
+        
+        return true;
+    }
+
+    static class VersionToCreate {
+        private String name;
+        private String project;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getProject() {
+            return project;
+        }
+
+        public void setProject(String project) {
+            this.project = project;
+        }
+    }
+    
+    static class CreatedVersion {
+        private String name;
+        private int id;
+        private boolean archived;
+        private boolean released;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public boolean isArchived() {
+            return archived;
+        }
+
+        public void setArchived(boolean archived) {
+            this.archived = archived;
+        }
+
+        public boolean isReleased() {
+            return released;
+        }
+
+        public void setReleased(boolean released) {
+            this.released = released;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/GetRelatedIssueCountsForVersionsJiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/GetRelatedIssueCountsForVersionsJiraAction.java
new file mode 100644
index 0000000..aa9616f
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/GetRelatedIssueCountsForVersionsJiraAction.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+
+public class GetRelatedIssueCountsForVersionsJiraAction implements JiraAction {
+
+    private static final Pattern VERSION_RELATED_ISSUES = Pattern.compile("^/jira/rest/api/2/version/(\\d+)/relatedIssueCounts$");
+
+    @Override
+    public boolean tryHandle(HttpExchange ex) throws IOException {
+        if ( !ex.getRequestMethod().equals("GET") )
+            return false;
+
+        Matcher matcher = VERSION_RELATED_ISSUES.matcher(ex.getRequestURI().getPath());
+        if ( !matcher.matches() )
+            return false;
+        
+        int version = Integer.parseInt(matcher.group(1));
+        InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
+        if ( in == null  ) {
+            ex.sendResponseHeaders(404, -1);
+        } else {
+            ex.sendResponseHeaders(200, 0);
+            try ( OutputStream out = ex.getResponseBody() ) {
+                IOUtils.copy(in, out);
+            }
+        }
+
+        return false;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/JiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/JiraAction.java
new file mode 100644
index 0000000..6cbc1b9
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/JiraAction.java
@@ -0,0 +1,40 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.function.Consumer;
+
+import org.apache.sling.cli.impl.jira.ErrorResponse;
+
+import com.google.gson.Gson;
+import com.sun.net.httpserver.HttpExchange;
+
+public interface JiraAction {
+    
+    default void error(HttpExchange httpExchange, Gson gson, Consumer<ErrorResponse> c) throws IOException {
+        try ( OutputStreamWriter out = new OutputStreamWriter(httpExchange.getResponseBody()) ) {
+            httpExchange.sendResponseHeaders(400, 0);
+            ErrorResponse er = new ErrorResponse();
+            c.accept(er);
+            gson.toJson(er, out);
+        }
+    }
+    
+    boolean tryHandle(HttpExchange ex) throws IOException;
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
new file mode 100644
index 0000000..30ce879
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
@@ -0,0 +1,29 @@
+package org.apache.sling.cli.impl.jira;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.io.IOUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+
+public class ListVersionsJiraAction implements JiraAction {
+
+    @Override
+    public boolean tryHandle(HttpExchange ex) throws IOException {
+        
+        if ( !ex.getRequestMethod().equals("GET") ||
+                !ex.getRequestURI().getPath().equals("/jira/rest/api/2/project/SLING/versions")) {
+            return false;
+        }
+        
+        ex.sendResponseHeaders(200, 0);
+        try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
+                OutputStream out = ex.getResponseBody() ) {
+            IOUtils.copy(in, out);
+        }
+        
+        return true;
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
index 3df51ca..e26dd87 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
@@ -17,20 +17,12 @@
 package org.apache.sling.cli.impl.jira;
 
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
 import java.net.InetSocketAddress;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.function.Consumer;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.List;
 
-import org.apache.commons.io.IOUtils;
 import org.junit.rules.ExternalResource;
 
-import com.google.gson.Gson;
 import com.sun.net.httpserver.Authenticator;
 import com.sun.net.httpserver.BasicAuthenticator;
 import com.sun.net.httpserver.HttpContext;
@@ -40,8 +32,6 @@ import com.sun.net.httpserver.HttpServer;
 
 public class MockJira extends ExternalResource {
     
-    private static final Pattern VERSION_RELATED_ISSUES = Pattern.compile("^/jira/rest/api/2/version/(\\d+)/relatedIssueCounts$");
-    
     static final String AUTH_USER = "jira-user";
     static final String AUTH_PWD = "jira-password";
     
@@ -72,89 +62,33 @@ public class MockJira extends ExternalResource {
                 return super.authenticate(t);
             }
         });
+        
+        List<JiraAction> actions = new ArrayList<>();
+        actions.add(new ListVersionsJiraAction());
+        actions.add(new GetRelatedIssueCountsForVersionsJiraAction());
+        actions.add(new CreateVersionJiraAction());
+        
+        // fallback, always executed
+        actions.add(new JiraAction() {
+            @Override
+            public boolean tryHandle(HttpExchange ex) throws IOException {
+                ex.sendResponseHeaders(400, -1);
+                return true;
+            }
+        });
+        
         rootContext.setHandler(httpExchange -> {
             
-            switch ( httpExchange.getRequestMethod() ) {
-            case "GET":
-                if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/project/SLING/versions") ) {
-                    httpExchange.sendResponseHeaders(200, 0);
-                    try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
-                            OutputStream out = httpExchange.getResponseBody() ) {
-                        IOUtils.copy(in, out);
-                    }
-                } else {
-                    Matcher matcher = VERSION_RELATED_ISSUES.matcher(httpExchange.getRequestURI().getPath());
-                    if ( matcher.matches() ) {
-                        int version = Integer.parseInt(matcher.group(1));
-                        InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
-                        if ( in == null  ) {
-                            httpExchange.sendResponseHeaders(404, -1);
-                        } else {
-                            httpExchange.sendResponseHeaders(200, 0);
-                            try ( OutputStream out = httpExchange.getResponseBody() ) {
-                                IOUtils.copy(in, out);
-                            }
-                        }
-                    } else {
-                        httpExchange.sendResponseHeaders(400, -1);
-                    }
+            for ( JiraAction action : actions ) {
+                if ( action.tryHandle(httpExchange) ) {
+                    break;
                 }
-                break;
-            case "POST":
-                if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/version") ) {
-                    handleCreateVersion(httpExchange);
-                    return;
-                }
-                httpExchange.sendResponseHeaders(400, -1);
-                break;
-                default:
-                    httpExchange.sendResponseHeaders(400, -1);
             }
-            
         });
         
         server.start();
     }
 
-    private void handleCreateVersion(HttpExchange httpExchange) throws IOException {
-        Gson gson = new Gson();
-        try ( InputStreamReader reader = new InputStreamReader(httpExchange.getRequestBody())) {
-            VersionToCreate version = gson.fromJson(reader, VersionToCreate.class);
-            if ( version.getName() == null || version.getName().isEmpty() ) {
-                error(httpExchange, gson, 
-                        er -> er.getErrors().put("name", "You must specify a valid version name"));
-                return;
-            }
-            
-            if ( !"SLING".equals(version.getProject()) ) {
-                error(httpExchange, gson, 
-                        er -> er.getErrorMessages().add("Project must be specified to create a version."));
-                return;
-            }
-            
-            // note not all fields are sent, projectId and self are missing
-            CreatedVersion createdVersion = new CreatedVersion();
-            createdVersion.archived = false;
-            createdVersion.id = ThreadLocalRandom.current().nextInt();
-            createdVersion.released = false;
-            createdVersion.name = version.getName();
-            
-            try ( OutputStreamWriter out = new OutputStreamWriter(httpExchange.getResponseBody()) ) {
-                httpExchange.sendResponseHeaders(201, 0);
-                gson.toJson(createdVersion, out);
-            }
-        }
-    }
-    
-    private void error(HttpExchange httpExchange, Gson gson, Consumer<ErrorResponse> c) throws IOException {
-        try ( OutputStreamWriter out = new OutputStreamWriter(httpExchange.getResponseBody()) ) {
-            httpExchange.sendResponseHeaders(400, 0);
-            ErrorResponse er = new ErrorResponse();
-            c.accept(er);
-            gson.toJson(er, out);
-        }
-    }
-    
     @Override
     protected void after() {
         
@@ -165,64 +99,4 @@ public class MockJira extends ExternalResource {
         
         return server.getAddress().getPort();
     }
-    
-    static class VersionToCreate {
-        private String name;
-        private String project;
-
-        public String getName() {
-            return name;
-        }
-
-        public void setName(String name) {
-            this.name = name;
-        }
-
-        public String getProject() {
-            return project;
-        }
-
-        public void setProject(String project) {
-            this.project = project;
-        }
-    }
-    
-    static class CreatedVersion {
-        private String name;
-        private int id;
-        private boolean archived;
-        private boolean released;
-
-        public String getName() {
-            return name;
-        }
-
-        public void setName(String name) {
-            this.name = name;
-        }
-
-        public int getId() {
-            return id;
-        }
-
-        public void setId(int id) {
-            this.id = id;
-        }
-
-        public boolean isArchived() {
-            return archived;
-        }
-
-        public void setArchived(boolean archived) {
-            this.archived = archived;
-        }
-
-        public boolean isReleased() {
-            return released;
-        }
-
-        public void setReleased(boolean released) {
-            this.released = released;
-        }
-    }
 }


[sling-org-apache-sling-committer-cli] 17/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 1945c87a47c665e8eaec5da3ccab7e17e8431ee5
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu Mar 28 18:06:41 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * changed tabs to spaces; tabs are evil...
---
 src/main/features/app.json | 134 ++++++++++++++++++++++-----------------------
 1 file changed, 67 insertions(+), 67 deletions(-)

diff --git a/src/main/features/app.json b/src/main/features/app.json
index 5fb7cd2..9b47884 100644
--- a/src/main/features/app.json
+++ b/src/main/features/app.json
@@ -1,69 +1,69 @@
 {
-	"id": "${project.groupId}:${project.artifactId}:slingfeature:app:${project.version}",
-	"bundles": [
-		{
-			"id": "${project.groupId}:${project.artifactId}:${project.version}",
-			"start-level": "5"
-		},
-		{
-			"id": "org.apache.felix:org.apache.felix.scr:2.1.12",
-			"start-level": "1"
-		},
-		{
-			"id": "org.apache.felix:org.apache.felix.configadmin:1.9.10",
-			"start-level": "1"
-		},
-		{
-			"id": "org.apache.felix:org.apache.felix.log:1.2.0",
-			"start-level": "1"
-		},
-		{
-			"id": "ch.qos.logback:logback-classic:1.2.3",
-			"start-level": "1"
-		},
-		{
-			"id": "ch.qos.logback:logback-core:1.2.3",
-			"start-level": "1"
-		},
-		{
-			"id": "org.slf4j:jul-to-slf4j:1.7.25",
-			"start-level": "1"
-		},
-		{
-			"id": "org.slf4j:jcl-over-slf4j:1.7.25",
-			"start-level": "1"
-		},
-		{
-			"id": "org.slf4j:slf4j-api:1.7.25",
-			"start-level": "1"
-		},
-		{
-			"id": "org.apache.felix:org.apache.felix.logback:1.0.2",
-			"start-level": "1"
-		},
-		{
-			"id": "org.apache.httpcomponents:httpcore-osgi:4.4.11",
-			"start-level": "3"
-		},
-		{
-			"id": "org.apache.httpcomponents:httpclient-osgi:4.5.7",
-			"start-level": "3"
-		},
-		{
-			"id": "com.google.code.gson:gson:2.8.5",
-			"start-level": "3"
-		},
-		{
-			"id": "org.eclipse.jgit:org.eclipse.jgit:5.2.1.201812262042-r",
-			"start-level": "3"
-		},
-		{
-			"id": "com.googlecode.javaewah:JavaEWAH:1.1.6",
-			"start-level": "3"
-		},
-		{
-			"id": "org.apache.servicemix.bundles:org.apache.servicemix.bundles.jsch:0.1.55_1",
-			"start-level": "3"
-		}
-	]
+    "id"     : "${project.groupId}:${project.artifactId}:slingfeature:app:${project.version}",
+    "bundles": [
+        {
+            "id"         : "${project.groupId}:${project.artifactId}:${project.version}",
+            "start-level": "5"
+        },
+        {
+            "id"         : "org.apache.felix:org.apache.felix.scr:2.1.12",
+            "start-level": "1"
+        },
+        {
+            "id"         : "org.apache.felix:org.apache.felix.configadmin:1.9.10",
+            "start-level": "1"
+        },
+        {
+            "id"         : "org.apache.felix:org.apache.felix.log:1.2.0",
+            "start-level": "1"
+        },
+        {
+            "id"         : "ch.qos.logback:logback-classic:1.2.3",
+            "start-level": "1"
+        },
+        {
+            "id"         : "ch.qos.logback:logback-core:1.2.3",
+            "start-level": "1"
+        },
+        {
+            "id"         : "org.slf4j:jul-to-slf4j:1.7.25",
+            "start-level": "1"
+        },
+        {
+            "id"         : "org.slf4j:jcl-over-slf4j:1.7.25",
+            "start-level": "1"
+        },
+        {
+            "id"         : "org.slf4j:slf4j-api:1.7.25",
+            "start-level": "1"
+        },
+        {
+            "id"         : "org.apache.felix:org.apache.felix.logback:1.0.2",
+            "start-level": "1"
+        },
+        {
+            "id"         : "org.apache.httpcomponents:httpcore-osgi:4.4.11",
+            "start-level": "3"
+        },
+        {
+            "id"         : "org.apache.httpcomponents:httpclient-osgi:4.5.7",
+            "start-level": "3"
+        },
+        {
+            "id"         : "com.google.code.gson:gson:2.8.5",
+            "start-level": "3"
+        },
+        {
+            "id"         : "org.eclipse.jgit:org.eclipse.jgit:5.2.1.201812262042-r",
+            "start-level": "3"
+        },
+        {
+            "id"         : "com.googlecode.javaewah:JavaEWAH:1.1.6",
+            "start-level": "3"
+        },
+        {
+            "id"         : "org.apache.servicemix.bundles:org.apache.servicemix.bundles.jsch:0.1.55_1",
+            "start-level": "3"
+        }
+    ]
 }


[sling-org-apache-sling-committer-cli] 18/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 05ae0d8f253fee2cfead5e2bc0ae837594f10334
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu Mar 28 18:51:14 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * added a basic service for sending emails (needs to be tested)
---
 src/main/features/app.json                         |  3 +
 .../org/apache/sling/cli/impl/mail/Mailer.java     | 79 ++++++++++++++++++++++
 .../org/apache/sling/cli/impl/people/Member.java   | 12 +++-
 3 files changed, 91 insertions(+), 3 deletions(-)

diff --git a/src/main/features/app.json b/src/main/features/app.json
index 9b47884..5daa106 100644
--- a/src/main/features/app.json
+++ b/src/main/features/app.json
@@ -64,6 +64,9 @@
         {
             "id"         : "org.apache.servicemix.bundles:org.apache.servicemix.bundles.jsch:0.1.55_1",
             "start-level": "3"
+        },
+        {
+            "id":"javax.mail:mail:1.5.0-b01"
         }
     ]
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
new file mode 100644
index 0000000..e4bd604
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
@@ -0,0 +1,79 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.cli.impl.mail;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import javax.mail.Authenticator;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.people.MembersFinder;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(
+        service = Mailer.class
+)
+public class Mailer {
+
+    private static final Properties SMTP_PROPERTIES = new Properties() {{
+        put("mail.smtp.host", "mail-relay.apache.org");
+        put("mail.smtp.auth", "true");
+        put("mail.smtp.socketFactory.port", 465);
+        put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
+        put("mail.smtp.socketFactory.fallback", "false");
+    }};
+
+    @Reference
+    private CredentialsService credentialsService;
+
+    @Reference
+    private MembersFinder membersFinder;
+
+    public void send(String to, String subject, String body) {
+        Properties properties = new Properties(SMTP_PROPERTIES);
+        Session session = Session.getDefaultInstance(properties, new Authenticator() {
+            @Override
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(credentialsService.getCredentials().getUsername(),
+                        credentialsService.getCredentials().getPassword());
+            }
+        });
+        try {
+            MimeMessage message = new MimeMessage(session);
+            message.setFrom(membersFinder.getCurrentMember().getEmail());
+            message.setSubject(subject);
+            message.setText(body, StandardCharsets.UTF_8.name());
+            message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
+            Transport.send(message);
+        } catch (MessagingException e) {
+
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/people/Member.java b/src/main/java/org/apache/sling/cli/impl/people/Member.java
index 7b3cb16..5602c66 100644
--- a/src/main/java/org/apache/sling/cli/impl/people/Member.java
+++ b/src/main/java/org/apache/sling/cli/impl/people/Member.java
@@ -20,14 +20,16 @@ package org.apache.sling.cli.impl.people;
 
 public class Member {
 
-    private String id;
-    private String name;
-    private boolean isPMCMember;
+    private final String id;
+    private final String name;
+    private final boolean isPMCMember;
+    private final String email;
 
     Member(String id, String name, boolean isPMCMember) {
         this.id = id;
         this.name = name;
         this.isPMCMember = isPMCMember;
+        email = id + "@apache.org";
     }
 
     public String getId() {
@@ -42,6 +44,10 @@ public class Member {
         return isPMCMember;
     }
 
+    public String getEmail() {
+        return email;
+    }
+
     @Override
     public int hashCode() {
         return id.hashCode();


[sling-org-apache-sling-committer-cli] 26/44: SLING-8366 - Releases of the form 'Apache Sling Foo RC' are not properly parsed

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 63499a0a606524459795b943bff67d153b6882cb
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Apr 19 14:59:56 2019 +0200

    SLING-8366 - Releases of the form 'Apache Sling Foo RC' are not properly parsed
    
    * allow RC values without RC number
---
 src/main/java/org/apache/sling/cli/impl/release/Release.java     | 2 +-
 src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/release/Release.java b/src/main/java/org/apache/sling/cli/impl/release/Release.java
index d75f424..95786b3 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/Release.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/Release.java
@@ -31,7 +31,7 @@ public final class Release {
         Group 4: RC status (optional)
      */
     private static final Pattern RELEASE_PATTERN = Pattern.compile("^\\h*(Apache Sling\\h*)?([()a-zA-Z0-9\\-.\\h]+)\\h([0-9\\-.]+)" +
-            "\\h?(RC[0-9.]+)?\\h*$");
+            "\\h?(RC[0-9.]*)?\\h*$");
     
     public static List<Release> fromString(String repositoryDescription) {
 
diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
index fbab0a2..8d16905 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
@@ -64,7 +64,6 @@ public class ReleaseTest {
     }
     
     @Test
-    @Ignore("Broken after refactoring, needs separate issue")
     public void releaseWithRCSuffixOnly() {
         List<Release> releases = Release.fromString("Apache Sling Resource Resolver 1.6.12 RC");
         


[sling-org-apache-sling-committer-cli] 27/44: trivial: updated .gitignore

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit e79821b2c09d58cc2803e714ef5e771926da212b
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Apr 19 15:02:03 2019 +0200

    trivial: updated .gitignore
---
 .gitignore | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/.gitignore b/.gitignore
index 84d2b34..a864a38 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,18 @@
 docker-env
 /target/
+.idea
+.classpath
+.metadata
+.project
+.settings
+.externalToolBuilders
+maven-eclipse.xml
+*.swp
+*.iml
+*.ipr
+*.iws
+*.bak
+.vlt
+.DS_Store
+jcr.log
+atlassian-ide-plugin.xml


[sling-org-apache-sling-committer-cli] 19/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit d9ae57105690c4ddaf25301bbd07d816f6a0defa
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Mar 29 15:23:50 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * finished Mailer implementation
---
 Dockerfile                                         |  2 +-
 src/main/features/app.json                         |  5 +++-
 .../org/apache/sling/cli/impl/mail/Mailer.java     | 30 ++++++++++++----------
 3 files changed, 21 insertions(+), 16 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index ca438e9..48d4d4e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,7 +11,7 @@
 # ----------------------------------------------------------------------------------------
 FROM azul/zulu-openjdk-alpine:11 as builder
 MAINTAINER dev@sling.apache.org
-RUN jlink --add-modules java.logging,java.naming,java.xml,java.security.jgss,java.sql,jdk.crypto.ec \
+RUN jlink --add-modules java.logging,java.naming,java.xml,java.security.jgss,java.sql,jdk.crypto.ec,java.desktop \
           --output /opt/jre --strip-debug --compress=2 --no-header-files --no-man-pages
 
 FROM alpine
diff --git a/src/main/features/app.json b/src/main/features/app.json
index 5daa106..d8a2bb2 100644
--- a/src/main/features/app.json
+++ b/src/main/features/app.json
@@ -66,7 +66,10 @@
             "start-level": "3"
         },
         {
-            "id":"javax.mail:mail:1.5.0-b01"
+            "id": "javax.mail:mail:1.5.0-b01"
+        },
+        {
+            "id": "org.apache.sling:org.apache.sling.javax.activation:0.1.0"
         }
     ]
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
index e4bd604..0c0a4e4 100644
--- a/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
+++ b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
@@ -18,32 +18,38 @@
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 package org.apache.sling.cli.impl.mail;
 
+import java.io.UnsupportedEncodingException;
 import java.nio.charset.StandardCharsets;
 import java.util.Properties;
 
-import javax.mail.Authenticator;
+import javax.mail.Address;
 import javax.mail.Message;
 import javax.mail.MessagingException;
-import javax.mail.PasswordAuthentication;
 import javax.mail.Session;
 import javax.mail.Transport;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
 
+import org.apache.sling.cli.impl.Credentials;
 import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.people.Member;
 import org.apache.sling.cli.impl.people.MembersFinder;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Component(
         service = Mailer.class
 )
 public class Mailer {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(Mailer.class);
+
     private static final Properties SMTP_PROPERTIES = new Properties() {{
         put("mail.smtp.host", "mail-relay.apache.org");
+        put("mail.smtp.port", "465");
         put("mail.smtp.auth", "true");
-        put("mail.smtp.socketFactory.port", 465);
         put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
         put("mail.smtp.socketFactory.fallback", "false");
     }};
@@ -56,22 +62,18 @@ public class Mailer {
 
     public void send(String to, String subject, String body) {
         Properties properties = new Properties(SMTP_PROPERTIES);
-        Session session = Session.getDefaultInstance(properties, new Authenticator() {
-            @Override
-            protected PasswordAuthentication getPasswordAuthentication() {
-                return new PasswordAuthentication(credentialsService.getCredentials().getUsername(),
-                        credentialsService.getCredentials().getPassword());
-            }
-        });
+        Session session = Session.getInstance(properties);
         try {
             MimeMessage message = new MimeMessage(session);
-            message.setFrom(membersFinder.getCurrentMember().getEmail());
+            Member sender = membersFinder.getCurrentMember();
+            Credentials credentials = credentialsService.getCredentials();
+            message.setFrom(new InternetAddress(sender.getEmail(), sender.getEmail(), StandardCharsets.UTF_8.name()));
             message.setSubject(subject);
             message.setText(body, StandardCharsets.UTF_8.name());
             message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
-            Transport.send(message);
-        } catch (MessagingException e) {
-
+            Transport.send(message, new Address[] {new InternetAddress(to)}, credentials.getUsername(), credentials.getPassword());
+        } catch (MessagingException | UnsupportedEncodingException e) {
+            LOGGER.error(String.format("Unable to send email with Subject '%s' to '%s'.", subject, to), e);
         }
 
     }


[sling-org-apache-sling-committer-cli] 40/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 858dc1f557d0e563dfa2f64091a0b3f8f795607e
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 18:00:48 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Document the create method, extract 'SLING' as a constant.
---
 .../java/org/apache/sling/cli/impl/jira/VersionClient.java  | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 8db031f..829a4da 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -49,6 +49,7 @@ import com.google.gson.stream.JsonWriter;
 @Component(service = VersionClient.class)
 public class VersionClient {
     
+    private static final String PROJECT_KEY = "SLING";
     private static final String DEFAULT_JIRA_URL_PREFIX = "https://issues.apache.org/jira/rest/api/2/";
     private static final String CONTENT_TYPE_JSON = "application/json";
     
@@ -119,12 +120,20 @@ public class VersionClient {
         return version;
     }
     
+    /**
+     * Creates a version with the specified name
+     * 
+     * <p>The version will be created for the {@value #PROJECT_KEY} project.</p>
+     * 
+     * @param versionName the name of the version
+     * @throws IOException In case of any errors creating the version in Jira
+     */
     public void create(String versionName) throws IOException {
         StringWriter w = new StringWriter();
         try ( JsonWriter jw = new Gson().newJsonWriter(w) ) {
             jw.beginObject();
             jw.name("name").value(versionName);
-            jw.name("project").value("SLING");
+            jw.name("project").value(PROJECT_KEY);
             jw.endObject();
         }
         
@@ -173,7 +182,7 @@ public class VersionClient {
 
     private Optional<Version> findVersion(Predicate<Version> matcher, CloseableHttpClient client) throws IOException {
         
-        HttpGet get = newGet("project/SLING/versions");
+        HttpGet get = newGet("project/" + PROJECT_KEY + "/versions");
         try (CloseableHttpResponse response = client.execute(get)) {
             try (InputStream content = response.getEntity().getContent();
                     InputStreamReader reader = new InputStreamReader(content)) {


[sling-org-apache-sling-committer-cli] 10/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 3cba59df2786d945bf40e2fb2b911107bf13d1fc
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Wed Mar 20 14:58:05 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * removed space in subject for tally votes email
---
 src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 86742bb..cbda427 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -41,7 +41,7 @@ public class TallyVotesCommand implements Command {
     // TODO - move to file
     private static final String EMAIL_TEMPLATE =
             "To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
-            "Subject: [RESULT] [VOTE] Release ##RELEASE_NAME##\n" + 
+            "Subject: [RESULT][VOTE] Release ##RELEASE_NAME##\n" + 
             "\n" + 
             "Hi,\n" + 
             "\n" + 


[sling-org-apache-sling-committer-cli] 04/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit d5e841e05e0b1f3e8f1e1c224ab634f2ef254168
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Mar 19 15:16:23 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Implement command for listing active releases.
---
 README.md                                          |   8 +-
 .../cli/impl/nexus/StagingRepositoryFinder.java    |  37 ++++++--
 .../apache/sling/cli/impl/release/ListCommand.java |  49 ++++++++++
 .../cli/impl/release/PrepareVoteEmailCommand.java  |  15 +--
 .../sling/cli/impl/release/ReleaseVersion.java}    |  41 +++++++--
 .../sling/cli/impl/release/TallyVotesCommand.java  |   9 +-
 .../cli/impl/release/UpdateLocalSiteCommand.java   | 102 +++++++++++++++++++++
 ...ailCommandTest.java => ReleaseVersionTest.java} |  11 ++-
 8 files changed, 234 insertions(+), 38 deletions(-)

diff --git a/README.md b/README.md
index eabf20c..45336b1 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,10 @@ This invocation produces a list of available subcommands.
 
 ## Commands
 
+Listing active releases
+
+    docker run --env-file=./docker-env apache/sling-cli release list
+
 Generating a release vote email
 
     docker run --env-file=./docker-env apache/sling-cli release prepare-email $STAGING_REPOSITORY_ID
@@ -27,7 +31,7 @@ Generating a release vote email
 Generating a release vote result email
 
     docker run --env-file=./docker-env apache/sling-cli release tally-votes $STAGING_REPOSITORY_ID
-    
+
 ## Assumptions
 
 This tool assumes that the name of the staging repository matches the one of the version in Jira. For instance, the
@@ -35,4 +39,4 @@ staging repositories are usually named _Apache Sling Foo 1.2.0_. It is then expe
 named _Foo 1.2.0_. Otherwise the link between the staging repository and the Jira release can not be found.
 
 It is allowed for staging repository names to have an _RC_ suffix, which may include a number, so that _RC_, _RC1_, _RC25_ are
-all valid suffixes.  
\ No newline at end of file
+all valid suffixes.  
diff --git a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
index 3ef7992..21b30ff 100644
--- a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
@@ -19,6 +19,9 @@ package org.apache.sling.cli.impl.nexus;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import org.apache.http.auth.AuthScope;
 import org.apache.http.auth.UsernamePasswordCredentials;
@@ -43,6 +46,8 @@ import com.google.gson.Gson;
 )
 @Designate(ocd = StagingRepositoryFinder.Config.class)
 public class StagingRepositoryFinder {
+    
+    private static final String REPOSITORY_PREFIX = "orgapachesling-";
 
     @ObjectClassDefinition
     static @interface Config {
@@ -61,8 +66,30 @@ public class StagingRepositoryFinder {
         credentialsProvider.setCredentials(new AuthScope("repository.apache.org", 443), 
                 new UsernamePasswordCredentials(cfg.username(), cfg.password()));
     }
+    
+    public List<StagingRepository> list() throws IOException {
+        return this. <List<StagingRepository>> withStagingRepositories( reader -> {
+            Gson gson = new Gson();
+            return gson.fromJson(reader, StagingRepositories.class).getData().stream()
+                    .filter( r -> r.getType() == Status.closed)
+                    .filter( r -> r.getRepositoryId().startsWith(REPOSITORY_PREFIX) )
+                    .collect(Collectors.toList());            
+        });
+    }
 
     public StagingRepository find(int stagingRepositoryId) throws IOException {
+        return this.<StagingRepository> withStagingRepositories( reader -> {
+            Gson gson = new Gson();
+            return gson.fromJson(reader, StagingRepositories.class).getData().stream()
+                    .filter( r -> r.getType() == Status.closed)
+                    .filter( r -> r.getRepositoryId().startsWith(REPOSITORY_PREFIX) )
+                    .filter( r -> r.getRepositoryId().endsWith("-" + stagingRepositoryId))
+                    .findFirst()
+                    .orElseThrow(() -> new IllegalArgumentException("No repository found with id " + stagingRepositoryId));            
+        });
+    }
+    
+    private <T> T withStagingRepositories(Function<InputStreamReader, T> function) throws IOException {
         try ( CloseableHttpClient client = HttpClients.custom()
                 .setDefaultCredentialsProvider(credentialsProvider)
                 .build() ) {
@@ -73,14 +100,10 @@ public class StagingRepositoryFinder {
                         InputStreamReader reader = new InputStreamReader(content)) {
                     if ( response.getStatusLine().getStatusCode() != 200 )
                         throw new IOException("Status line : " + response.getStatusLine());
-                    Gson gson = new Gson();
-                    return gson.fromJson(reader, StagingRepositories.class).getData().stream()
-                        .filter( r -> r.getType() == Status.closed)
-                        .filter( r -> r.getRepositoryId().endsWith("-" + stagingRepositoryId))
-                        .findFirst()
-                        .orElseThrow(() -> new IllegalArgumentException("No repository found with id " + stagingRepositoryId));
+                    
+                    return function.apply(reader);
                 }
             }
-        }
+        }       
     }
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/ListCommand.java b/src/main/java/org/apache/sling/cli/impl/release/ListCommand.java
new file mode 100644
index 0000000..1d35e29
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/ListCommand.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cli.impl.release;
+
+import java.io.IOException;
+
+import org.apache.sling.cli.impl.Command;
+import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Command.class, property = {
+        Command.PROPERTY_NAME_COMMAND + "=release",
+        Command.PROPERTY_NAME_SUBCOMMAND + "=list",
+        Command.PROPERTY_NAME_SUMMARY + "=Lists all open releases" })
+public class ListCommand implements Command {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Reference
+    private StagingRepositoryFinder repoFinder;
+
+    @Override
+    public void execute(String target) {
+        try {
+            repoFinder.list().stream()
+                .forEach( r -> logger.info("{}\t{}", r.getRepositoryId(), r.getDescription()));
+        } catch (IOException e) {
+            logger.warn("Failed executing command", e);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
index eff3a3f..5b8df75 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -36,7 +36,7 @@ public class PrepareVoteEmailCommand implements Command {
 
     // TODO - replace with file template
     private static final String EMAIL_TEMPLATE ="To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
-            "Subject: [VOTE] Release Apache Sling ##RELEASE_NAME##\n" + 
+            "Subject: [VOTE] Release ##RELEASE_NAME##\n" + 
             "\n" + 
             "Hi,\n" + 
             "\n" + 
@@ -73,11 +73,11 @@ public class PrepareVoteEmailCommand implements Command {
         try {
             int repoId = Integer.parseInt(target);
             StagingRepository repo = repoFinder.find(repoId);
-            String cleanVersion = getCleanVersion(repo.getDescription());
-            Version version = versionFinder.find(cleanVersion);
+            ReleaseVersion releaseVersion = ReleaseVersion.fromRepositoryDescription(repo.getDescription());
+            Version version = versionFinder.find(releaseVersion.getName());
             
             String emailContents = EMAIL_TEMPLATE
-                    .replace("##RELEASE_NAME##", cleanVersion)
+                    .replace("##RELEASE_NAME##", releaseVersion.getFullName())
                     .replace("##RELEASE_ID##", String.valueOf(repoId))
                     .replace("##VERSION_ID##", String.valueOf(version.getId()))
                     .replace("##FIXED_ISSUES_COUNT##", String.valueOf(version.getIssuesFixedCount()));
@@ -88,11 +88,4 @@ public class PrepareVoteEmailCommand implements Command {
             logger.warn("Failed executing command", e);
         }
     }
-
-    static String getCleanVersion(String repoDescription) {
-        return repoDescription
-                .replace("Apache Sling ", "") // Apache Sling prefix
-                .replaceAll(" RC[0-9]*$", ""); // 'release candidate' suffix 
-    }
-
 }
diff --git a/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java b/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
similarity index 50%
copy from src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
copy to src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
index 8dd81aa..b629e19 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
@@ -16,16 +16,39 @@
  */
 package org.apache.sling.cli.impl.release;
 
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-
-public class PrepareVoteEmailCommandTest {
+public final class ReleaseVersion {
+    
+    public static ReleaseVersion fromRepositoryDescription(String repositoryDescription) {
+        
+        ReleaseVersion rel = new ReleaseVersion();
+        
+        rel.fullName = repositoryDescription
+            .replaceAll(" RC[0-9]*$", ""); // 'release candidate' suffix
+        rel.name = rel.fullName
+            .replace("Apache Sling ", ""); // Apache Sling prefix
+        rel.version = rel.fullName.substring(rel.fullName.lastIndexOf(' ') + 1);
+        
+        return rel;
+    }
+    
+    private String fullName;
+    private String name;
+    private String version;
 
-    @Test
-    public void cleanVersion() {
+    private ReleaseVersion() {
         
-        assertEquals("Resource Merger 1.3.10", 
-                PrepareVoteEmailCommand.getCleanVersion("Apache Sling Resource Merger 1.3.10 RC1"));
     }
+    
+    public String getFullName() {
+        return fullName;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+    public String getVersion() {
+        return version;
+    }
+    
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 8e34d87..f15f60b 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -42,7 +42,7 @@ public class TallyVotesCommand implements Command {
     private static final String EMAIL_TEMPLATE ="\n" + 
             "\n" + 
             "To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
-            "Subject: [RESULT] [VOTE] Release Apache Sling ##RELEASE_NAME##\n" + 
+            "Subject: [RESULT] [VOTE] Release ##RELEASE_NAME##\n" + 
             "\n" + 
             "Hi,\n" + 
             "\n" + 
@@ -65,9 +65,8 @@ public class TallyVotesCommand implements Command {
         try {
             
             StagingRepository repository = repoFinder.find(Integer.parseInt(target));
-            // TODO - release name cleanup does not belong here
-            String releaseName = repository.getDescription().replaceFirst(" RC[0-9]+", "");
-            EmailThread voteThread = voteThreadFinder.findVoteThread(releaseName);
+            ReleaseVersion releaseVersion = ReleaseVersion.fromRepositoryDescription(repository.getDescription()); 
+            EmailThread voteThread = voteThreadFinder.findVoteThread(releaseVersion.getFullName());
 
             // TODO - validate which voters are binding and list them separately in the email
             String bindingVoters = voteThread.getEmails().stream()
@@ -76,7 +75,7 @@ public class TallyVotesCommand implements Command {
                 .collect(Collectors.joining(", "));
             
             String email = EMAIL_TEMPLATE
-                .replace("##RELEASE_NAME##", releaseName)
+                .replace("##RELEASE_NAME##", releaseVersion.getFullName())
                 .replace("##BINDING_VOTERS##", bindingVoters);
             
             logger.info(email);
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
new file mode 100644
index 0000000..483613e
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
@@ -0,0 +1,102 @@
+/*
+ * 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.cli.impl.release;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+
+import org.apache.sling.cli.impl.Command;
+import org.apache.sling.cli.impl.jbake.JBakeContentUpdater;
+import org.apache.sling.cli.impl.nexus.StagingRepository;
+import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Command.class, property = {
+    Command.PROPERTY_NAME_COMMAND+"=release",
+    Command.PROPERTY_NAME_SUBCOMMAND+"=update-local-site",
+    Command.PROPERTY_NAME_SUMMARY+"=Updates the Sling website with the new release information, based on a local checkout"
+})
+public class UpdateLocalSiteCommand implements Command {
+    
+    public static void main(String[] args) {
+        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("MMMM uuuu", Locale.ENGLISH)));
+    }
+
+    private static final String GIT_CHECKOUT = "/tmp/sling-site";
+
+    @Reference
+    private StagingRepositoryFinder repoFinder;
+    
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    
+    @Override
+    public void execute(String target) {
+        
+        
+        try {
+            ensureRepo();
+            try ( Git git = Git.open(new File(GIT_CHECKOUT)) ) {
+                
+                StagingRepository repository = repoFinder.find(Integer.parseInt(target));
+                ReleaseVersion releaseVersion = ReleaseVersion.fromRepositoryDescription(repository.getDescription());
+                
+                JBakeContentUpdater updater = new JBakeContentUpdater();
+        
+                Path templatePath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "templates", "downloads.tpl");
+                Path releasesPath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "content", "releases.md");
+                updater.updateDownloads(templatePath, releaseVersion.getName(), releaseVersion.getVersion());
+                updater.updateReleases(releasesPath, releaseVersion.getName(), releaseVersion.getVersion(), LocalDateTime.now());
+        
+                git.diff()
+                    .setOutputStream(System.out)
+                    .call();
+            }
+        } catch (GitAPIException | IOException e) {
+            logger.warn("Failed executing command", e);
+        }
+            
+    }
+
+    private void ensureRepo() throws GitAPIException, IOException {
+        
+        if ( !Paths.get(GIT_CHECKOUT).toFile().exists() ) {
+            Git.cloneRepository()
+            .setURI("https://github.com/apache/sling-site.git")
+            .setProgressMonitor(new TextProgressMonitor())
+            .setDirectory(new File(GIT_CHECKOUT))
+            .call();
+        } else {
+            try ( Git git = Git.open(new File(GIT_CHECKOUT)) )  {
+                git.reset()
+                    .setMode(ResetType.HARD)
+                    .call();
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
similarity index 70%
rename from src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
rename to src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
index 8dd81aa..90ed3e5 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
@@ -20,12 +20,15 @@ import static org.junit.Assert.assertEquals;
 
 import org.junit.Test;
 
-public class PrepareVoteEmailCommandTest {
+public class ReleaseVersionTest {
 
     @Test
-    public void cleanVersion() {
+    public void fromRepositoryDescription() {
         
-        assertEquals("Resource Merger 1.3.10", 
-                PrepareVoteEmailCommand.getCleanVersion("Apache Sling Resource Merger 1.3.10 RC1"));
+        ReleaseVersion rel = ReleaseVersion.fromRepositoryDescription("Apache Sling Resource Merger 1.3.10 RC1");
+        
+        assertEquals("Resource Merger 1.3.10", rel.getName());
+        assertEquals("Apache Sling Resource Merger 1.3.10", rel.getFullName());
+        assertEquals("1.3.10", rel.getVersion());
     }
 }


[sling-org-apache-sling-committer-cli] 23/44: Enable Jenkins build

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit c6dffc75fc650fc2a563050971a274aa0669bb43
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Apr 18 16:51:21 2019 +0300

    Enable Jenkins build
---
 .sling-module.json |  5 +++++
 Jenkinsfile        | 20 ++++++++++++++++++++
 2 files changed, 25 insertions(+)

diff --git a/.sling-module.json b/.sling-module.json
new file mode 100644
index 0000000..bca6582
--- /dev/null
+++ b/.sling-module.json
@@ -0,0 +1,5 @@
+{
+    "jenkins": {
+        "jdks": [11]
+    }
+}
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..f582519
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+slingOsgiBundleBuild()


[sling-org-apache-sling-committer-cli] 28/44: Update to released parent version (still under vote)

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit a13047a498b659810c3a18a98862e6432c846708
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Apr 19 15:19:39 2019 +0300

    Update to released parent version (still under vote)
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 0fc0dbd..f76647e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling-bundle-parent</artifactId>
-        <version>36-SNAPSHOT</version>
+        <version>35</version>
         <relativePath />
     </parent>
 


[sling-org-apache-sling-committer-cli] 21/44: SLING-8339 - CLI hangs when an unhandled exception occurs

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit e11e596afaf7ee8a02af73b04ff3ca3ac097078e
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Apr 1 18:49:51 2019 +0200

    SLING-8339 - CLI hangs when an unhandled exception occurs
    
    Allow commands to throw exceptions and stop the framework whenever
    such an error is thrown.
---
 .../java/org/apache/sling/cli/impl/Command.java    |  2 +-
 .../apache/sling/cli/impl/CommandProcessor.java    | 22 ++++++++++++++--------
 2 files changed, 15 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/Command.java b/src/main/java/org/apache/sling/cli/impl/Command.java
index 4caeea7..4b015c4 100644
--- a/src/main/java/org/apache/sling/cli/impl/Command.java
+++ b/src/main/java/org/apache/sling/cli/impl/Command.java
@@ -22,5 +22,5 @@ public interface Command {
     String PROPERTY_NAME_SUBCOMMAND = "subcommand";
     String PROPERTY_NAME_SUMMARY = "summary";
 
-    void execute(String target);
+    void execute(String target) throws Exception;
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java b/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
index 57e5430..6ca6bca 100644
--- a/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
+++ b/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
@@ -57,16 +57,22 @@ public class CommandProcessor {
         // TODO - remove duplication from CLI parsing code
         CommandKey key = CommandKey.of(ctx.getProperty("exec.args"));
         String target = parseTarget(ctx.getProperty("exec.args"));
-        commands.getOrDefault(key, new CommandWithProps(ignored -> {
-            logger.info("Usage: sling command sub-command [target]");
-            logger.info("");
-            logger.info("Available commands:");
-            commands.forEach((k, c) -> logger.info("{} {}: {}", k.command, k.subCommand, c.summary));
-        }, "")).cmd.execute(target);
         try {
-            ctx.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(Framework.class).stop();
-        } catch (BundleException e) {
+            commands.getOrDefault(key, new CommandWithProps(ignored -> {
+                logger.info("Usage: sling command sub-command [target]");
+                logger.info("");
+                logger.info("Available commands:");
+                commands.forEach((k, c) -> logger.info("{} {}: {}", k.command, k.subCommand, c.summary));
+            }, "")).cmd.execute(target);
+        } catch (Exception e) {
             logger.warn("Failed running command", e);
+        } finally {
+            try {
+                ctx.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(Framework.class).stop();
+            } catch (BundleException e) {
+                logger.error("Failed shutting down framework, forcing exit", e);
+                System.exit(1);
+            }
         }
     }
 


[sling-org-apache-sling-committer-cli] 08/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 292bc15a2062ba333023f0de34bba9843dae7035
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Mar 19 16:25:53 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Remove trailing empty lines from TallyVotesCommand output.
---
 src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index f15f60b..86742bb 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -39,8 +39,7 @@ import org.slf4j.LoggerFactory;
 public class TallyVotesCommand implements Command {
     
     // TODO - move to file
-    private static final String EMAIL_TEMPLATE ="\n" + 
-            "\n" + 
+    private static final String EMAIL_TEMPLATE =
             "To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
             "Subject: [RESULT] [VOTE] Release ##RELEASE_NAME##\n" + 
             "\n" + 


[sling-org-apache-sling-committer-cli] 15/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 95330f1312369738740edca534816d70e763ccdc
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Thu Mar 28 17:41:06 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * added support for updating a release in the Apache Reporter System
    * added a service for extracting the user's credentials from the environment
    * switched Docker image to the openjdk11 provided by azul
    * used jlink to create a minimal JRE
---
 Dockerfile                                         |  10 +-
 pom.xml                                            |  13 ++-
 src/main/features/app.json                         |  14 +--
 .../org/apache/sling/cli/impl/Credentials.java     |  38 +++++++
 .../apache/sling/cli/impl/CredentialsService.java  |  55 +++++++++
 .../cli/impl/nexus/StagingRepositoryFinder.java    |  32 ++----
 .../sling/cli/impl/people/MembersFinder.java       |  17 +--
 .../cli/impl/release/UpdateReporterCommand.java    | 124 +++++++++++++++++++++
 src/main/resources/scripts/launcher.sh             |  24 ++--
 9 files changed, 268 insertions(+), 59 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index c29cd1e..ca438e9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,12 +9,16 @@
 # either express or implied. See the License for the specific language governing permissions
 # and limitations under the License.
 # ----------------------------------------------------------------------------------------
-
-FROM openjdk:8-jre-alpine
+FROM azul/zulu-openjdk-alpine:11 as builder
 MAINTAINER dev@sling.apache.org
+RUN jlink --add-modules java.logging,java.naming,java.xml,java.security.jgss,java.sql,jdk.crypto.ec \
+          --output /opt/jre --strip-debug --compress=2 --no-header-files --no-man-pages
+
+FROM alpine
+COPY --from=builder /opt/jre /opt/jre
 
 # Generate class data sharing
-RUN java -Xshare:dump
+RUN /opt/jre/bin/java -Xshare:dump
 
 # escaping required to properly handle arguments with spaces
 ENTRYPOINT ["/usr/share/sling-cli/bin/launcher.sh"]
diff --git a/pom.xml b/pom.xml
index 1f717d7..b27394e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,8 +16,8 @@
 
     <parent>
         <groupId>org.apache.sling</groupId>
-        <artifactId>sling</artifactId>
-        <version>34</version>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>35-SNAPSHOT</version>
         <relativePath />
     </parent>
 
@@ -28,6 +28,7 @@
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <sling.java.version>11</sling.java.version>
     </properties>
 
     <build>
@@ -123,6 +124,14 @@
                     </buildArgs>
                 </configuration>
             </plugin>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-baseline-maven-plugin</artifactId>
+                <configuration>
+                    <!-- We don't export an API, so it should be safe to skip baseline. -->
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
         </plugins>
     </build>
 
diff --git a/src/main/features/app.json b/src/main/features/app.json
index 4d310c4..5fb7cd2 100644
--- a/src/main/features/app.json
+++ b/src/main/features/app.json
@@ -1,9 +1,5 @@
 {
 	"id": "${project.groupId}:${project.artifactId}:slingfeature:app:${project.version}",
-	"variables": {
-    	"asf.username":"change-me",
-    	"asf.password": "change-me"
-    },
 	"bundles": [
 		{
 			"id": "${project.groupId}:${project.artifactId}:${project.version}",
@@ -69,11 +65,5 @@
 			"id": "org.apache.servicemix.bundles:org.apache.servicemix.bundles.jsch:0.1.55_1",
 			"start-level": "3"
 		}
-	],
-	"configurations": {
-		"org.apache.sling.cli.impl.nexus.StagingRepositoryFinder": {
-			"username": "${asf.username}",
-			"password": "${asf.password}"
-		}
-	}
-}
\ No newline at end of file
+	]
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/Credentials.java b/src/main/java/org/apache/sling/cli/impl/Credentials.java
new file mode 100644
index 0000000..2df5a32
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/Credentials.java
@@ -0,0 +1,38 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.cli.impl;
+
+public class Credentials {
+
+    private final String user;
+    private final String password;
+
+    public Credentials(String user, String password) {
+        this.user = user;
+        this.password = password;
+    }
+
+    public String getUsername() {
+        return user;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/CredentialsService.java b/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
new file mode 100644
index 0000000..3ddbcca
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
@@ -0,0 +1,55 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.cli.impl;
+
+import java.util.Optional;
+
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+
+@Component(service = CredentialsService.class)
+public class CredentialsService {
+
+    private static final String USER_SYS_PROP = "asf.username";
+    private static final String PASSWORD_SYS_PROP = "asf.password";
+    private static final String USER_ENV_PROP = "ASF_USERNAME";
+    private static final String PASSWORD_ENV_PROP = "ASF_PASSWORD";
+
+    private volatile Credentials credentials;
+
+    @Activate
+    private void activate() {
+        Optional<String> username =
+                Optional.ofNullable(System.getProperty(USER_SYS_PROP)).or(() -> Optional.ofNullable(System.getenv(USER_ENV_PROP)));
+        Optional<String> password =
+                Optional.ofNullable(System.getProperty(PASSWORD_SYS_PROP)).or(() -> Optional.ofNullable(System.getenv(PASSWORD_ENV_PROP)));
+        credentials = new Credentials(
+                username.orElseThrow(() -> new IllegalStateException(
+                        String.format("Cannot detect user information after looking for %s system property and %s environment variable.",
+                                USER_SYS_PROP, USER_ENV_PROP))),
+                password.orElseThrow(() -> new IllegalStateException(
+                        String.format("Cannot detect password after looking for %s system property and %s environment variable.",
+                                PASSWORD_SYS_PROP, PASSWORD_ENV_PROP)))
+        );
+    }
+
+    public Credentials getCredentials() {
+        return credentials;
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
index 21b30ff..9093a04 100644
--- a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
@@ -30,45 +30,35 @@ import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.Credentials;
+import org.apache.sling.cli.impl.CredentialsService;
 import org.apache.sling.cli.impl.nexus.StagingRepository.Status;
 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.metatype.annotations.AttributeDefinition;
-import org.osgi.service.metatype.annotations.Designate;
-import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.osgi.service.component.annotations.Reference;
 
 import com.google.gson.Gson;
 
-@Component(
-    configurationPolicy = ConfigurationPolicy.REQUIRE,
-    service = StagingRepositoryFinder.class
-)
-@Designate(ocd = StagingRepositoryFinder.Config.class)
+@Component(service = StagingRepositoryFinder.class)
 public class StagingRepositoryFinder {
     
     private static final String REPOSITORY_PREFIX = "orgapachesling-";
 
-    @ObjectClassDefinition
-    static @interface Config {
-        @AttributeDefinition(name="Username")
-        String username();
-        
-        @AttributeDefinition(name="Password")
-        String password();
-    }
+    @Reference
+    private CredentialsService credentialsService;
 
     private BasicCredentialsProvider credentialsProvider;
     
     @Activate
-    protected void activate(Config cfg) {
+    private void activate() {
+        Credentials credentials = credentialsService.getCredentials();
         credentialsProvider = new BasicCredentialsProvider();
         credentialsProvider.setCredentials(new AuthScope("repository.apache.org", 443), 
-                new UsernamePasswordCredentials(cfg.username(), cfg.password()));
+                new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword()));
     }
     
     public List<StagingRepository> list() throws IOException {
-        return this. <List<StagingRepository>> withStagingRepositories( reader -> {
+        return this.withStagingRepositories( reader -> {
             Gson gson = new Gson();
             return gson.fromJson(reader, StagingRepositories.class).getData().stream()
                     .filter( r -> r.getType() == Status.closed)
@@ -78,7 +68,7 @@ public class StagingRepositoryFinder {
     }
 
     public StagingRepository find(int stagingRepositoryId) throws IOException {
-        return this.<StagingRepository> withStagingRepositories( reader -> {
+        return this.withStagingRepositories( reader -> {
             Gson gson = new Gson();
             return gson.fromJson(reader, StagingRepositories.class).getData().stream()
                     .filter( r -> r.getType() == Status.closed)
diff --git a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
index a27bf6c..70b5469 100644
--- a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
@@ -29,7 +29,9 @@ import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.CredentialsService;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,6 +49,9 @@ public class MembersFinder {
     private Set<Member> members = Collections.emptySet();
     private long lastCheck = 0;
 
+    @Reference
+    private CredentialsService credentialsService;
+
     public synchronized Set<Member> findMembers() {
         final Set<Member> _members = new HashSet<>();
         if (lastCheck == 0 || System.currentTimeMillis() > lastCheck + STALENESS_IN_HOURS * 3600 * 1000) {
@@ -108,17 +113,7 @@ public class MembersFinder {
     }
 
     public Member getCurrentMember() {
-        final String currentUserId;
-        if (System.getProperty("asf.username") != null) {
-            currentUserId = System.getProperty("asf.username");
-        } else {
-            currentUserId = System.getenv("ASF_USERNAME");
-        }
-        if (currentUserId == null) {
-            throw new IllegalStateException(String.format("Expected to find the current user defined either through the %s system " +
-                    "property or through the %s environment variable.", "asf.username", "ASF_USERNAME"));
-        }
-         return getMemberById(currentUserId);
+         return getMemberById(credentialsService.getCredentials().getUsername());
     }
 
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
new file mode 100644
index 0000000..4c0c127
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
@@ -0,0 +1,124 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.cli.impl.release;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLSocket;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.DefaultHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.sling.cli.impl.Command;
+import org.apache.sling.cli.impl.Credentials;
+import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.nexus.StagingRepository;
+import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Command.class,
+    property = {
+        Command.PROPERTY_NAME_COMMAND + "=release",
+        Command.PROPERTY_NAME_SUBCOMMAND + "=update-reporter",
+        Command.PROPERTY_NAME_SUMMARY + "=Updates the Apache Reporter System with the new release information"
+    }
+)
+public class UpdateReporterCommand implements Command {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(UpdateReporterCommand.class);
+
+    @Reference
+    private StagingRepositoryFinder repoFinder;
+
+    @Reference
+    private CredentialsService credentialsService;
+
+    private CredentialsProvider credentialsProvider;
+
+    @Override
+    public void execute(String target) {
+        try {
+            StagingRepository repository = repoFinder.find(Integer.parseInt(target));
+            Release release = Release.fromString(repository.getDescription());
+            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
+            sslContext.init(null, null, null);
+            SSLContext.setDefault(sslContext);
+            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
+                    sslContext,
+                    new String[] {"TLSv1.2"},
+                    new String[] {
+                            "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256"
+                    },
+                    new DefaultHostnameVerifier()
+            );
+            try (CloseableHttpClient client =
+                         HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).setSSLSocketFactory(sslConnectionSocketFactory).build()) {
+                HttpPost post = new HttpPost("https://reporter.apache.org/addrelease.py");
+                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+                List<NameValuePair> parameters = new ArrayList<>();
+                Date now = new Date();
+                parameters.add(new BasicNameValuePair("date", Long.toString(now.getTime() / 1000)));
+                parameters.add(new BasicNameValuePair("committee", "sling"));
+                parameters.add(new BasicNameValuePair("version", release.getFullName()));
+                parameters.add(new BasicNameValuePair("xdate", simpleDateFormat.format(now)));
+                post.setEntity(new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8));
+                try (CloseableHttpResponse response = client.execute(post)) {
+                    if (response.getStatusLine().getStatusCode() != 200) {
+                        throw new IOException(String.format("The Apache Reporter System update failed. Got response code %s instead of " +
+                                "200.", response.getStatusLine().getStatusCode()));
+                    }
+                }
+            }
+        } catch (IOException | NoSuchAlgorithmException | KeyManagementException e) {
+            LOGGER.error(String.format("Unable to update reporter service; passed command: %s.", target), e);
+        }
+
+    }
+
+    @Activate
+    private void activate() {
+        Credentials credentials = credentialsService.getCredentials();
+        credentialsProvider = new BasicCredentialsProvider();
+        credentialsProvider.setCredentials(new AuthScope("reporter.apache.org", 443),
+                new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword()));
+    }
+}
diff --git a/src/main/resources/scripts/launcher.sh b/src/main/resources/scripts/launcher.sh
index 7b306ba..08f103f 100755
--- a/src/main/resources/scripts/launcher.sh
+++ b/src/main/resources/scripts/launcher.sh
@@ -18,13 +18,17 @@
 ARGS_PROP="exec.args=$@"
 
 # Use exec to become pid 1, see https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
-exec /usr/bin/java \
-     -Xshare:on \
-	 -Dorg.slf4j.simpleLogger.logFile=/dev/null \
-	 -Dlogback.configurationFile=file:/usr/share/sling-cli/conf/logback-default.xml \
-	 -jar /usr/share/sling-cli/launcher/org.apache.sling.feature.launcher.jar \
-	 -f /usr/share/sling-cli/sling-cli.feature \
-	 -c /usr/share/sling-cli/artifacts \
-	 -D "$ARGS_PROP" \
-	 -V "asf.username=${ASF_USERNAME}" \
-	 -V "asf.password=${ASF_PASSWORD}"
+exec /opt/jre/bin/java \
+    --add-opens=java.base/java.lang=ALL-UNNAMED \
+    --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED \
+    --add-opens=java.base/java.net=ALL-UNNAMED \
+    --add-opens=java.base/java.security=ALL-UNNAMED \
+    -Xshare:on \
+    -Dorg.slf4j.simpleLogger.logFile=/dev/null \
+    -Dlogback.configurationFile=file:/usr/share/sling-cli/conf/logback-default.xml \
+    -jar /usr/share/sling-cli/launcher/org.apache.sling.feature.launcher.jar \
+    -f /usr/share/sling-cli/sling-cli.feature \
+    -c /usr/share/sling-cli/artifacts \
+    -D "$ARGS_PROP" \
+    -V "asf.username=${ASF_USERNAME}" \
+    -V "asf.password=${ASF_PASSWORD}"


[sling-org-apache-sling-committer-cli] 34/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit a487fa59543eff10bdadb8611b95eeecc1e53070
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 14:10:42 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Add credentials for Jira access as well.
---
 pom.xml                                            | 28 ++++++++++
 .../apache/sling/cli/impl/CredentialsService.java  | 59 ++++++++++++++--------
 .../sling/cli/impl/http/HttpClientFactory.java     | 52 +++++++++++++++++++
 .../apache/sling/cli/impl/jira/VersionClient.java  | 12 +++--
 .../org/apache/sling/cli/impl/mail/Mailer.java     |  2 +-
 .../cli/impl/nexus/StagingRepositoryFinder.java    | 24 ++-------
 .../sling/cli/impl/people/MembersFinder.java       |  2 +-
 .../cli/impl/release/UpdateReporterCommand.java    | 23 ++-------
 src/main/resources/scripts/launcher.sh             |  4 +-
 .../sling/cli/impl/CredentialsServiceTest.java     | 56 ++++++++++++++++++++
 .../sling/cli/impl/jira/SystemPropertiesRule.java  | 51 +++++++++++++++++++
 ...rsionFinderTest.java => VersionClientTest.java} | 48 +++++++++++++++---
 12 files changed, 285 insertions(+), 76 deletions(-)

diff --git a/pom.xml b/pom.xml
index f76647e..f820ac0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -198,5 +198,33 @@
             <version>1.3</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock.junit4</artifactId>
+            <version>2.4.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.osgi-mock.junit4</artifactId>
+            <version>2.4.8</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.event</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.log</artifactId>
+            <version>1.3.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.cmpn</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/cli/impl/CredentialsService.java b/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
index 3ddbcca..127c8fe 100644
--- a/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
+++ b/src/main/java/org/apache/sling/cli/impl/CredentialsService.java
@@ -26,30 +26,49 @@ import org.osgi.service.component.annotations.Component;
 @Component(service = CredentialsService.class)
 public class CredentialsService {
 
-    private static final String USER_SYS_PROP = "asf.username";
-    private static final String PASSWORD_SYS_PROP = "asf.password";
-    private static final String USER_ENV_PROP = "ASF_USERNAME";
-    private static final String PASSWORD_ENV_PROP = "ASF_PASSWORD";
+    private static final ValueSource ASF_USER = new ValueSource("asf.username", "ASF_USERNAME", "ASF user information");
+    private static final ValueSource ASF_PWD = new ValueSource("asf.password", "ASF_PASSWORD", "ASF password");
 
-    private volatile Credentials credentials;
+    private static final ValueSource JIRA_USER = new ValueSource("jira.username", "JIRA_USERNAME", "Jira user information");
+    private static final ValueSource JIRA_PWD = new ValueSource("jira.password", "JIRA_PASSWORD", "Jira password");
+    
+    private Credentials asfCredentials;
+    private Credentials jiraCredentials;
 
     @Activate
-    private void activate() {
-        Optional<String> username =
-                Optional.ofNullable(System.getProperty(USER_SYS_PROP)).or(() -> Optional.ofNullable(System.getenv(USER_ENV_PROP)));
-        Optional<String> password =
-                Optional.ofNullable(System.getProperty(PASSWORD_SYS_PROP)).or(() -> Optional.ofNullable(System.getenv(PASSWORD_ENV_PROP)));
-        credentials = new Credentials(
-                username.orElseThrow(() -> new IllegalStateException(
-                        String.format("Cannot detect user information after looking for %s system property and %s environment variable.",
-                                USER_SYS_PROP, USER_ENV_PROP))),
-                password.orElseThrow(() -> new IllegalStateException(
-                        String.format("Cannot detect password after looking for %s system property and %s environment variable.",
-                                PASSWORD_SYS_PROP, PASSWORD_ENV_PROP)))
-        );
+    protected void activate() {
+        asfCredentials = new Credentials(ASF_USER.getValue(), ASF_PWD.getValue());
+        jiraCredentials = new Credentials(JIRA_USER.getValue(), JIRA_PWD.getValue());
     }
+    
+    public Credentials getAsfCredentials() {
+        return asfCredentials;
+    }
+    
+    public Credentials getJiraCredentials() {
+        return jiraCredentials;
+    }
+
+    static class ValueSource {
+
+        private final String sysProp;
+        private final String envVar;
+        private final String friendlyName;
+        
+        public ValueSource(String sysProp, String envVar, String friendlyName) {
 
-    public Credentials getCredentials() {
-        return credentials;
+            this.sysProp = sysProp;
+            this.envVar = envVar;
+            this.friendlyName = friendlyName;
+        }
+        
+        public String getValue() {
+            
+            return Optional.ofNullable(System.getProperty(sysProp))
+                    .or( () -> Optional.ofNullable(System.getenv(envVar)) )
+                    .orElseThrow(() -> new IllegalStateException(String.format("Cannot detect %s after looking for %s system property and %s environment variable.", 
+                            friendlyName, sysProp, envVar)));
+                    
+        }   
     }
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java b/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java
new file mode 100644
index 0000000..7975145
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/http/HttpClientFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cli.impl.http;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.Credentials;
+import org.apache.sling.cli.impl.CredentialsService;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(service = HttpClientFactory.class)
+public class HttpClientFactory {
+    
+    @Reference
+    private CredentialsService credentialsService;
+
+    public CloseableHttpClient newClient() {
+        
+        Credentials asf = credentialsService.getAsfCredentials();
+        Credentials jira = credentialsService.getJiraCredentials();
+        
+        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+        credentialsProvider.setCredentials(new AuthScope("repository.apache.org", 443), 
+                new UsernamePasswordCredentials(asf.getUsername(), asf.getPassword()));
+        credentialsProvider.setCredentials(new AuthScope("reporter.apache.org", 443), 
+                new UsernamePasswordCredentials(asf.getUsername(), asf.getPassword()));
+        credentialsProvider.setCredentials(new AuthScope("jira.apache.org", 443), 
+                new UsernamePasswordCredentials(jira.getUsername(), jira.getPassword()));
+        
+        return HttpClients.custom()
+                .setDefaultCredentialsProvider(credentialsProvider)
+                .build();
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 6a4a770..77e6881 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -32,9 +32,10 @@ import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.release.Release;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
@@ -48,6 +49,9 @@ public class VersionClient {
     
     private static final String JIRA_URL_PREFIX = "https://issues.apache.org/jira/rest/api/2/";
     private static final String CONTENT_TYPE_JSON = "application/json";
+    
+    @Reference
+    private HttpClientFactory httpClientFactory;
 
     /**
      * Finds a Jira version which matches the specified release
@@ -59,7 +63,7 @@ public class VersionClient {
     public Version find(Release release) {
         Version version;
         
-        try (CloseableHttpClient client = HttpClients.createDefault()) {
+        try (CloseableHttpClient client = httpClientFactory.newClient()) {
             version = findVersion( v -> release.getName().equals(v.getName()), client)
                     .orElseThrow( () -> new IllegalArgumentException("No version found with name " + release.getName()));
             populateRelatedIssuesCount(client, version);
@@ -92,7 +96,7 @@ public class VersionClient {
     public Version findSuccessorVersion(Release release) {
         Version version;
         
-        try ( CloseableHttpClient client = HttpClients.createDefault() ) {
+        try (CloseableHttpClient client = httpClientFactory.newClient()) {
             Optional<Version> opt = findVersion ( 
                     v -> {
                         Release releaseFromVersion = Release.fromString(v.getName()).get(0);
@@ -125,7 +129,7 @@ public class VersionClient {
         post.addHeader("Accept", CONTENT_TYPE_JSON);
         post.setEntity(new StringEntity(w.toString()));
 
-        try (CloseableHttpClient client = HttpClients.createDefault() ) {
+        try (CloseableHttpClient client = httpClientFactory.newClient()) {
             try (CloseableHttpResponse response = client.execute(post)) {
                 try (InputStream content = response.getEntity().getContent();
                         InputStreamReader reader = new InputStreamReader(content)) {
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
index 0c0a4e4..e7749d9 100644
--- a/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
+++ b/src/main/java/org/apache/sling/cli/impl/mail/Mailer.java
@@ -66,7 +66,7 @@ public class Mailer {
         try {
             MimeMessage message = new MimeMessage(session);
             Member sender = membersFinder.getCurrentMember();
-            Credentials credentials = credentialsService.getCredentials();
+            Credentials credentials = credentialsService.getAsfCredentials();
             message.setFrom(new InternetAddress(sender.getEmail(), sender.getEmail(), StandardCharsets.UTF_8.name()));
             message.setSubject(subject);
             message.setText(body, StandardCharsets.UTF_8.name());
diff --git a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
index 9093a04..1926181 100644
--- a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
@@ -23,17 +23,11 @@ import java.util.List;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.sling.cli.impl.Credentials;
-import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.nexus.StagingRepository.Status;
-import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
@@ -45,18 +39,8 @@ public class StagingRepositoryFinder {
     private static final String REPOSITORY_PREFIX = "orgapachesling-";
 
     @Reference
-    private CredentialsService credentialsService;
+    private HttpClientFactory httpClientFactory;
 
-    private BasicCredentialsProvider credentialsProvider;
-    
-    @Activate
-    private void activate() {
-        Credentials credentials = credentialsService.getCredentials();
-        credentialsProvider = new BasicCredentialsProvider();
-        credentialsProvider.setCredentials(new AuthScope("repository.apache.org", 443), 
-                new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword()));
-    }
-    
     public List<StagingRepository> list() throws IOException {
         return this.withStagingRepositories( reader -> {
             Gson gson = new Gson();
@@ -80,9 +64,7 @@ public class StagingRepositoryFinder {
     }
     
     private <T> T withStagingRepositories(Function<InputStreamReader, T> function) throws IOException {
-        try ( CloseableHttpClient client = HttpClients.custom()
-                .setDefaultCredentialsProvider(credentialsProvider)
-                .build() ) {
+        try ( CloseableHttpClient client = httpClientFactory.newClient() ) {
             HttpGet get = new HttpGet("https://repository.apache.org/service/local/staging/profile_repositories");
             get.addHeader("Accept", "application/json");
             try ( CloseableHttpResponse response = client.execute(get)) {
diff --git a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
index 70b5469..fc15455 100644
--- a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
@@ -113,7 +113,7 @@ public class MembersFinder {
     }
 
     public Member getCurrentMember() {
-         return getMemberById(credentialsService.getCredentials().getUsername());
+         return getMemberById(credentialsService.getAsfCredentials().getUsername());
     }
 
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
index fdf0ef2..1042c5c 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
@@ -26,22 +26,15 @@ import java.util.Date;
 import java.util.List;
 
 import org.apache.http.NameValuePair;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.CredentialsProvider;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpPost;
-import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
 import org.apache.http.message.BasicNameValuePair;
 import org.apache.sling.cli.impl.Command;
-import org.apache.sling.cli.impl.Credentials;
-import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
 import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
-import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -62,17 +55,15 @@ public class UpdateReporterCommand implements Command {
     private StagingRepositoryFinder repoFinder;
 
     @Reference
-    private CredentialsService credentialsService;
+    private HttpClientFactory httpClientFactory;
 
-    private CredentialsProvider credentialsProvider;
 
     @Override
     public void execute(String target) {
         try {
             StagingRepository repository = repoFinder.find(Integer.parseInt(target));
             
-            try (CloseableHttpClient client =
-                         HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).build()) {
+            try (CloseableHttpClient client = httpClientFactory.newClient() ) {
                 for ( Release release : Release.fromString(repository.getDescription()) ) {
                     HttpPost post = new HttpPost("https://reporter.apache.org/addrelease.py");
                     SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
@@ -96,12 +87,4 @@ public class UpdateReporterCommand implements Command {
         }
 
     }
-
-    @Activate
-    private void activate() {
-        Credentials credentials = credentialsService.getCredentials();
-        credentialsProvider = new BasicCredentialsProvider();
-        credentialsProvider.setCredentials(new AuthScope("reporter.apache.org", 443),
-                new UsernamePasswordCredentials(credentials.getUsername(), credentials.getPassword()));
-    }
 }
diff --git a/src/main/resources/scripts/launcher.sh b/src/main/resources/scripts/launcher.sh
index 08f103f..e5094b5 100755
--- a/src/main/resources/scripts/launcher.sh
+++ b/src/main/resources/scripts/launcher.sh
@@ -31,4 +31,6 @@ exec /opt/jre/bin/java \
     -c /usr/share/sling-cli/artifacts \
     -D "$ARGS_PROP" \
     -V "asf.username=${ASF_USERNAME}" \
-    -V "asf.password=${ASF_PASSWORD}"
+    -V "asf.password=${ASF_PASSWORD}" \
+    -V "jira.username=${JIRA_USERNAME}" \
+    -V "jira.password=${JIRA_PASSWORD}"
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/cli/impl/CredentialsServiceTest.java b/src/test/java/org/apache/sling/cli/impl/CredentialsServiceTest.java
new file mode 100644
index 0000000..571adba
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/CredentialsServiceTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cli.impl;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class CredentialsServiceTest {
+
+    private static final String[] VALID_PROPS = new String[] { "asf.username", "asf.password", "jira.username", "jira.password" };
+
+    @Test(expected = IllegalStateException.class)
+    public void noCredentialSourcesFound() {
+        new CredentialsService().activate();
+    }
+    
+    @Test
+    public void credentialsFromSystemProps() {
+        for ( String prop : VALID_PROPS ) {
+            System.setProperty(prop, prop + ".val");
+        }
+        
+        try {
+            
+            CredentialsService creds = new CredentialsService();
+            creds.activate();
+            
+            assertThat(creds.getAsfCredentials().getUsername(), equalTo("asf.username.val"));
+            assertThat(creds.getAsfCredentials().getPassword(), equalTo("asf.password.val"));
+            
+            assertThat(creds.getJiraCredentials().getUsername(), equalTo("jira.username.val"));
+            assertThat(creds.getJiraCredentials().getPassword(), equalTo("jira.password.val"));
+            
+        } finally {
+            for ( String prop : VALID_PROPS ) {
+                System.clearProperty(prop);
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/SystemPropertiesRule.java b/src/test/java/org/apache/sling/cli/impl/jira/SystemPropertiesRule.java
new file mode 100644
index 0000000..c6273b5
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/SystemPropertiesRule.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.rules.ExternalResource;
+
+public class SystemPropertiesRule extends ExternalResource {
+
+    private final Map<String, String> propsToRestore = new HashMap<String, String>();
+    private final Map<String, String> propsToOverride;
+    
+    public SystemPropertiesRule(Map<String, String> propsToOverride) {
+        this.propsToOverride = propsToOverride;
+    }
+    
+    @Override
+    protected void before() throws Throwable {
+        for (Map.Entry<String, String> prop : propsToOverride.entrySet() )
+            propsToRestore.put(prop.getKey(), set(prop));
+    }
+
+    private String set(Map.Entry<String, String> prop) {
+        if ( prop.getValue() != null )
+            return System.setProperty(prop.getKey(), prop.getValue());
+        
+        return System.clearProperty(prop.getKey());
+    }
+    
+    @Override
+    protected void after() {
+        for (Map.Entry<String, String> prop : propsToRestore.entrySet() )
+            set(prop);
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
similarity index 63%
rename from src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
rename to src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
index 6f11f6c..0cac72d 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
@@ -24,21 +24,53 @@ import static org.junit.Assert.assertThat;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.function.Function;
 
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.cli.impl.CredentialsService;
+import org.apache.sling.cli.impl.http.HttpClientFactory;
 import org.apache.sling.cli.impl.release.Release;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
-public class VersionFinderTest {
+public class VersionClientTest {
 
-    private VersionClient finder = new StubVersionFinder();
+    private static final Map<String, String> SYSTEM_PROPS = new HashMap<>();
+    static {
+        SYSTEM_PROPS.put("asf.username", "user");
+        SYSTEM_PROPS.put("asf.password", "password");
+        SYSTEM_PROPS.put("jira.username", "user");
+        SYSTEM_PROPS.put("jira.password", "password");
+    }
+    
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+    
+    @Rule
+    public final SystemPropertiesRule sysProps = new SystemPropertiesRule(SYSTEM_PROPS);
+    
+    private VersionClient versionClient;
+    
+    @Before
+    public void prepareDependencies() throws ReflectiveOperationException {
+        context.registerInjectActivateService(new CredentialsService());
+        context.registerInjectActivateService(new HttpClientFactory());
+        
+        versionClient = new StubVersionClient();
+        Field factoryField = VersionClient.class.getDeclaredField("httpClientFactory");
+        factoryField.setAccessible(true);
+        factoryField.set(versionClient, context.getService(HttpClientFactory.class));
+    }
     
     @Test
     public void findMatchingVersion() {
         
-        finder = new StubVersionFinder();
-        Version version = finder.find(Release.fromString("XSS Protection API 1.0.2").get(0));
+        Version version = versionClient.find(Release.fromString("XSS Protection API 1.0.2").get(0));
         
         assertThat("version", version, notNullValue());
         assertThat("version.name", version.getName(), equalTo("XSS Protection API 1.0.2"));
@@ -49,12 +81,12 @@ public class VersionFinderTest {
     @Test(expected = IllegalArgumentException.class)
     public void missingVersionNotFound() {
         
-        finder.find(Release.fromString("XSS Protection API 1.0.3").get(0));
+        versionClient.find(Release.fromString("XSS Protection API 1.0.3").get(0));
     }
     
     @Test
     public void findSuccessorVersion() {
-        Version successor = finder.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.2").get(0));
+        Version successor = versionClient.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.2").get(0));
         
         assertThat("successor", successor, notNullValue());
         assertThat("successor.name", successor.getName(), equalTo("XSS Protection API 1.0.4"));
@@ -62,12 +94,12 @@ public class VersionFinderTest {
 
     @Test
     public void noSuccessorVersion() {
-        Version successor = finder.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.16").get(0));
+        Version successor = versionClient.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.16").get(0));
         
         assertThat("successor", successor, nullValue());
     }
     
-    private static final class StubVersionFinder extends VersionClient {
+    private static final class StubVersionClient extends VersionClient {
         @Override
         protected <T> T doWithJiraVersions(CloseableHttpClient client, Function<InputStreamReader, T> parserCallback)
                 throws IOException {


[sling-org-apache-sling-committer-cli] 44/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 57239f1f32913758ef0f75263795ef1d91766cd3
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Apr 24 15:31:23 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Add missing copyright headers
---
 .../sling/cli/impl/jira/ListVersionsJiraAction.java      | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java b/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
index acce035..49986d4 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/ListVersionsJiraAction.java
@@ -1,3 +1,19 @@
+/*
+ * 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.cli.impl.jira;
 
 import java.io.IOException;


[sling-org-apache-sling-committer-cli] 33/44: Use release version of the parent pom

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit d38cfe1e7b9e2f6f0054ea5130dd36c837ec1187
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Apr 22 15:12:51 2019 +0300

    Use release version of the parent pom
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 6cdcf27..1a3a1fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling-bundle-parent</artifactId>
-        <version>36-SNAPSHOT</version>
+        <version>35</version>
         <relativePath />
     </parent>
 


[sling-org-apache-sling-committer-cli] 35/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit ea8878d583848b70495a3666af15d38cda4a62cf
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 14:14:10 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Rename UpdateJiraCommand to better reflect its purpose.
---
 ...pdateJiraCommand.java => CreateJiraVersionCommand.java} | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java b/src/main/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommand.java
similarity index 81%
rename from src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java
rename to src/main/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommand.java
index a636ee8..e7b1708 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/CreateJiraVersionCommand.java
@@ -30,16 +30,16 @@ import org.slf4j.LoggerFactory;
 
 @Component(service = Command.class, property = {
         Command.PROPERTY_NAME_COMMAND+"=release",
-        Command.PROPERTY_NAME_SUBCOMMAND+"=update-jira",
-        Command.PROPERTY_NAME_SUMMARY+"=Releases the current version, closes versions fixed in the current version and creates a new version if needed"
+        Command.PROPERTY_NAME_SUBCOMMAND+"=create-jira-new-version",
+        Command.PROPERTY_NAME_SUMMARY+"=Creates a new Jira version, if needed, and transitions any unresolved issues from the version being released to the next one."
     })
-public class UpdateJiraCommand implements Command {
+public class CreateJiraVersionCommand implements Command {
 
     @Reference
     private StagingRepositoryFinder repoFinder;
     
     @Reference
-    private VersionClient versionFinder;
+    private VersionClient versionClient;
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -48,14 +48,14 @@ public class UpdateJiraCommand implements Command {
         try {
             StagingRepository repo = repoFinder.find(Integer.parseInt(target));
             for (Release release : Release.fromString(repo.getDescription()) ) {
-                Version version = versionFinder.find(release);
+                Version version = versionClient.find(release);
                 logger.info("Found version {}", version);
-                Version successorVersion = versionFinder.findSuccessorVersion(release);
+                Version successorVersion = versionClient.findSuccessorVersion(release);
                 logger.info("Found successor version {}", successorVersion);
                 if ( successorVersion == null ) {
                     Release next = release.next();
                     logger.info("Would create version {}", next.getName());
-                    versionFinder.create(next.getName());
+                    versionClient.create(next.getName());
                     logger.info("Created version {}", next.getName());
                 }
                     


[sling-org-apache-sling-committer-cli] 36/44: Merge branch 'master' into feature/SLING-8337

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit eb94e9a87c60ded98c4b7489eb49e3728875cc83
Merge: ea8878d d38cfe1
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 14:47:50 2019 +0300

    Merge branch 'master' into feature/SLING-8337

 pom.xml                                            | 10 +++
 .../java/org/apache/sling/cli/impl/mail/Email.java | 88 +++++++++++++++++++---
 .../apache/sling/cli/impl/mail/EmailThread.java    | 31 --------
 .../sling/cli/impl/mail/VoteThreadFinder.java      | 17 ++++-
 .../sling/cli/impl/people/MembersFinder.java       | 17 ++++-
 .../sling/cli/impl/release/TallyVotesCommand.java  | 33 ++++----
 6 files changed, 135 insertions(+), 61 deletions(-)

diff --cc src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
index fc15455,c26ab9a..b483f93
--- a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
@@@ -112,8 -114,19 +114,19 @@@ public class MembersFinder 
          return null;
      }
  
+     public Member findByNameOrEmail(String name, String email) {
+         Collator collator = Collator.getInstance(Locale.US);
+         collator.setDecomposition(Collator.NO_DECOMPOSITION);
+         for (Member member : findMembers()) {
+             if (email.equals(member.getEmail()) || collator.compare(name, member.getName()) == 0) {
+                 return member;
+             }
+         }
+         return null;
+     }
+ 
      public Member getCurrentMember() {
-          return getMemberById(credentialsService.getAsfCredentials().getUsername());
 -        return findById(credentialsService.getCredentials().getUsername());
++        return findById(credentialsService.getAsfCredentials().getUsername());
      }
  
  }


[sling-org-apache-sling-committer-cli] 20/44: Add explicity dependecy on javax.mail

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 2a914e4ae524921e612b35e544963d0c8c7e8e65
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Mar 29 17:41:41 2019 +0100

    Add explicity dependecy on javax.mail
    
    We should not rely on the classpath populated by the feature model.
---
 pom.xml | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/pom.xml b/pom.xml
index b27394e..735bc46 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,6 +71,9 @@
                 <artifactId>slingfeature-maven-plugin</artifactId>
                 <version>0.8.0</version>
                 <extensions>true</extensions>
+                <configuration>
+                    <skipAddFeatureDependencies>true</skipAddFeatureDependencies>
+                </configuration>
                 <executions>
                     <execution>
                         <id>feature-dependencies</id>
@@ -180,6 +183,12 @@
           <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>javax.mail</groupId>
+            <artifactId>mail</artifactId>
+            <version>1.5.0-b01</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
         </dependency>


[sling-org-apache-sling-committer-cli] 24/44: SLING-8364 - Support releases with multiple artifacts

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit b4162bf8be940e12cbcc127e7731cb783868a5b5
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Apr 19 11:07:21 2019 +0300

    SLING-8364 - Support releases with multiple artifacts
    
    Teach Release.fromString to return multiple versions, if found, and
    adapt the rest of the codebase to that change.
---
 .gitignore                                         |  1 +
 .../apache/sling/cli/impl/jira/VersionFinder.java  | 13 +++++--
 .../cli/impl/release/PrepareVoteEmailCommand.java  | 36 +++++++++++++++----
 .../org/apache/sling/cli/impl/release/Release.java | 42 +++++++++++++---------
 .../sling/cli/impl/release/TallyVotesCommand.java  | 13 +++++--
 .../cli/impl/release/UpdateLocalSiteCommand.java   | 10 ++++--
 .../cli/impl/release/UpdateReporterCommand.java    | 30 ++++++++--------
 .../apache/sling/cli/impl/release/ReleaseTest.java | 38 +++++++++++++++++---
 8 files changed, 132 insertions(+), 51 deletions(-)

diff --git a/.gitignore b/.gitignore
index a49f72d..84d2b34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 docker-env
+/target/
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
index 7dbcbee..a752be0 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
@@ -21,6 +21,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.lang.reflect.Type;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -35,12 +36,14 @@ import com.google.gson.reflect.TypeToken;
 @Component(service = VersionFinder.class)
 public class VersionFinder {
 
-    public Version find(String versionName) throws IOException {
+    public Version find(String versionName) {
         Version version;
         
         try (CloseableHttpClient client = HttpClients.createDefault()) {
             version = findVersion(versionName, client);
             populateRelatedIssuesCount(client, version);
+        } catch ( IOException e ) {
+            throw new RuntimeException(e);
         }
         
         return version;
@@ -58,9 +61,13 @@ public class VersionFinder {
                 Gson gson = new Gson();
                 Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
                 List<Version> versions = gson.fromJson(reader, collectionType);
-                Release filter = Release.fromString(versionName);
+                List<String> filter = Release.fromString(versionName)
+                        .stream()
+                        .map( Release::getName )
+                        .collect(Collectors.toList());
+                
                 version = versions.stream()
-                    .filter(v -> filter.equals(Release.fromString(v.getName())))
+                    .filter(v -> filter.contains(v.getName()))
                     .findFirst()
                     .orElseThrow( () -> new IllegalArgumentException("No version found with name " + versionName));
             }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
index 21b72f8..4cc4222 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -17,6 +17,8 @@
 package org.apache.sling.cli.impl.release;
 
 import java.io.IOException;
+import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.jira.Version;
@@ -44,8 +46,9 @@ public class PrepareVoteEmailCommand implements Command {
             "\n" + 
             "Hi,\n" + 
             "\n" + 
-            "We solved ##FIXED_ISSUES_COUNT## issues in this release:\n" + 
-            "https://issues.apache.org/jira/browse/SLING/fixforversion/##VERSION_ID##\n" + 
+            "We solved ##FIXED_ISSUES_COUNT## issue(s) in ##RELEASE_OR_RELEASES##:\n" +
+            "\n" + 
+            "##RELEASE_JIRA_LINKS##\n" +
             "\n" + 
             "Staging repository:\n" + 
             "https://repository.apache.org/content/repositories/orgapachesling-##RELEASE_ID##/\n" + 
@@ -68,6 +71,9 @@ public class PrepareVoteEmailCommand implements Command {
             "##USER_NAME##\n" +
             "\n";
 
+    private static final String RELEASE_TEMPLATE = 
+            "https://issues.apache.org/jira/browse/SLING/fixforversion/##VERSION_ID##";
+    
     private final Logger logger = LoggerFactory.getLogger(getClass());
     
     @Reference
@@ -81,14 +87,30 @@ public class PrepareVoteEmailCommand implements Command {
         try {
             int repoId = Integer.parseInt(target);
             StagingRepository repo = repoFinder.find(repoId);
-            Release release = Release.fromString(repo.getDescription());
-            Version version = versionFinder.find(release.getName());
+            List<Release> releases = Release.fromString(repo.getDescription());
+            List<Version> versions = releases.stream()
+                    .map( r -> versionFinder.find(r.getName()))
+                    .collect(Collectors.toList());
+            
+            String releaseName = releases.stream()
+                    .map( Release::getFullName )
+                    .collect(Collectors.joining(", "));
+            
+            int fixedIssueCounts = versions.stream().mapToInt( Version::getIssuesFixedCount).sum();
+            String releaseOrReleases = versions.size() > 1 ?
+                    "these releases" : "this release";
+            
+            String releaseJiraLinks = versions.stream()
+                .map( v -> RELEASE_TEMPLATE.replace("##VERSION_ID##", String.valueOf(v.getId())))
+                .collect(Collectors.joining("\n"));
+                
             
             String emailContents = EMAIL_TEMPLATE
-                    .replace("##RELEASE_NAME##", release.getFullName())
+                    .replace("##RELEASE_NAME##", releaseName)
                     .replace("##RELEASE_ID##", String.valueOf(repoId))
-                    .replace("##VERSION_ID##", String.valueOf(version.getId()))
-                    .replace("##FIXED_ISSUES_COUNT##", String.valueOf(version.getIssuesFixedCount()))
+                    .replace("##RELEASE_OR_RELEASES##", releaseOrReleases)
+                    .replace("##RELEASE_JIRA_LINKS##", releaseJiraLinks)
+                    .replace("##FIXED_ISSUES_COUNT##", String.valueOf(fixedIssueCounts))
                     .replace("##USER_NAME##", membersFinder.getCurrentMember().getName());
                     
             logger.info(emailContents);
diff --git a/src/main/java/org/apache/sling/cli/impl/release/Release.java b/src/main/java/org/apache/sling/cli/impl/release/Release.java
index 5086962..d75f424 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/Release.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/Release.java
@@ -16,6 +16,8 @@
  */
 package org.apache.sling.cli.impl.release;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -31,24 +33,32 @@ public final class Release {
     private static final Pattern RELEASE_PATTERN = Pattern.compile("^\\h*(Apache Sling\\h*)?([()a-zA-Z0-9\\-.\\h]+)\\h([0-9\\-.]+)" +
             "\\h?(RC[0-9.]+)?\\h*$");
     
-    public static Release fromString(String repositoryDescription) {
-        
-        Release rel = new Release();
-        Matcher matcher = RELEASE_PATTERN.matcher(repositoryDescription);
-        if (matcher.matches()) {
-            rel.component = matcher.group(2).trim();
-            rel.version = matcher.group(3);
-            rel.name = rel.component + " " + rel.version;
-            StringBuilder fullName = new StringBuilder();
-            if (matcher.group(1) != null) {
-                fullName.append(matcher.group(1).trim()).append(" ");
-            }
-            fullName.append(rel.name);
-            rel.fullName = fullName.toString();
-
+    public static List<Release> fromString(String repositoryDescription) {
 
+        List<Release> releases = new ArrayList<>();
+        for (String item  : repositoryDescription.split(",") ) {
+            
+            Matcher matcher = RELEASE_PATTERN.matcher(item);
+            if (matcher.matches()) {
+                Release rel = new Release();
+                rel.component = matcher.group(2).trim();
+                rel.version = matcher.group(3);
+                rel.name = rel.component + " " + rel.version;
+                StringBuilder fullName = new StringBuilder();
+                if (matcher.group(1) != null) {
+                    fullName.append(matcher.group(1).trim()).append(" ");
+                }
+                fullName.append(rel.name);
+                rel.fullName = fullName.toString();
+                
+                releases.add(rel);
+            }
         }
-        return rel;
+        
+        if ( releases.isEmpty() )
+            throw new IllegalArgumentException("No releases found in '" + repositoryDescription + "'");
+        
+        return releases;
     }
     
     private String fullName;
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 90244eb..fb3ca95 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -76,8 +76,15 @@ public class TallyVotesCommand implements Command {
         try {
             
             StagingRepository repository = repoFinder.find(Integer.parseInt(target));
-            Release release = Release.fromString(repository.getDescription());
-            EmailThread voteThread = voteThreadFinder.findVoteThread(release.getFullName());
+            String releaseName = Release.fromString(repository.getDescription())
+                    .stream()
+                    .map(Release::getName)
+                    .collect(Collectors.joining(", "));
+            String releaseFullName = Release.fromString(repository.getDescription())
+                    .stream()
+                    .map(Release::getFullName)
+                    .collect(Collectors.joining(", "));
+            EmailThread voteThread = voteThreadFinder.findVoteThread(releaseName);
 
             Set<String> bindingVoters = new HashSet<>();
             Set<String> nonBindingVoters = new HashSet<>();
@@ -96,7 +103,7 @@ public class TallyVotesCommand implements Command {
                 }
             }
             String email = EMAIL_TEMPLATE
-                .replace("##RELEASE_NAME##", release.getFullName())
+                .replace("##RELEASE_NAME##", releaseFullName)
                 .replace("##BINDING_VOTERS##", String.join(", ", bindingVoters))
                 .replace("##USER_NAME##", membersFinder.getCurrentMember().getName());
             if (nonBindingVoters.isEmpty()) {
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
index 4bf4530..99ab368 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.time.LocalDateTime;
+import java.util.List;
 
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.jbake.JBakeContentUpdater;
@@ -58,14 +59,17 @@ public class UpdateLocalSiteCommand implements Command {
             try ( Git git = Git.open(new File(GIT_CHECKOUT)) ) {
                 
                 StagingRepository repository = repoFinder.find(Integer.parseInt(target));
-                Release release = Release.fromString(repository.getDescription());
+                List<Release> releases = Release.fromString(repository.getDescription());
                 
                 JBakeContentUpdater updater = new JBakeContentUpdater();
         
                 Path templatePath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "templates", "downloads.tpl");
                 Path releasesPath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "content", "releases.md");
-                updater.updateDownloads(templatePath, release.getComponent(), release.getVersion());
-                updater.updateReleases(releasesPath, release.getComponent(), release.getVersion(), LocalDateTime.now());
+                LocalDateTime now = LocalDateTime.now();
+                for ( Release release : releases ) {
+                    updater.updateDownloads(templatePath, release.getComponent(), release.getVersion());
+                    updater.updateReleases(releasesPath, release.getComponent(), release.getVersion(), now);
+                }
         
                 git.diff()
                     .setOutputStream(System.out)
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
index 0a33043..fdf0ef2 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateReporterCommand.java
@@ -70,22 +70,24 @@ public class UpdateReporterCommand implements Command {
     public void execute(String target) {
         try {
             StagingRepository repository = repoFinder.find(Integer.parseInt(target));
-            Release release = Release.fromString(repository.getDescription());
+            
             try (CloseableHttpClient client =
                          HttpClients.custom().setDefaultCredentialsProvider(credentialsProvider).build()) {
-                HttpPost post = new HttpPost("https://reporter.apache.org/addrelease.py");
-                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
-                List<NameValuePair> parameters = new ArrayList<>();
-                Date now = new Date();
-                parameters.add(new BasicNameValuePair("date", Long.toString(now.getTime() / 1000)));
-                parameters.add(new BasicNameValuePair("committee", "sling"));
-                parameters.add(new BasicNameValuePair("version", release.getFullName()));
-                parameters.add(new BasicNameValuePair("xdate", simpleDateFormat.format(now)));
-                post.setEntity(new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8));
-                try (CloseableHttpResponse response = client.execute(post)) {
-                    if (response.getStatusLine().getStatusCode() != 200) {
-                        throw new IOException(String.format("The Apache Reporter System update failed. Got response code %s instead of " +
-                                "200.", response.getStatusLine().getStatusCode()));
+                for ( Release release : Release.fromString(repository.getDescription()) ) {
+                    HttpPost post = new HttpPost("https://reporter.apache.org/addrelease.py");
+                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+                    List<NameValuePair> parameters = new ArrayList<>();
+                    Date now = new Date();
+                    parameters.add(new BasicNameValuePair("date", Long.toString(now.getTime() / 1000)));
+                    parameters.add(new BasicNameValuePair("committee", "sling"));
+                    parameters.add(new BasicNameValuePair("version", release.getFullName()));
+                    parameters.add(new BasicNameValuePair("xdate", simpleDateFormat.format(now)));
+                    post.setEntity(new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8));
+                    try (CloseableHttpResponse response = client.execute(post)) {
+                        if (response.getStatusLine().getStatusCode() != 200) {
+                            throw new IOException(String.format("The Apache Reporter System update failed. Got response code %s instead of " +
+                                    "200.", response.getStatusLine().getStatusCode()));
+                        }
                     }
                 }
             }
diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
index 182191a..fbab0a2 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
@@ -18,16 +18,16 @@ package org.apache.sling.cli.impl.release;
 
 import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
 import java.net.URISyntaxException;
+import java.util.List;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class ReleaseTest {
@@ -35,8 +35,14 @@ public class ReleaseTest {
     @Test
     public void fromRepositoryDescription() {
         
-        Release rel1 = Release.fromString("Apache Sling Resource Merger 1.3.10 RC1");
-        Release rel2 = Release.fromString("   Apache Sling Resource Merger    1.3.10   ");
+        List<Release> releases1 = Release.fromString("Apache Sling Resource Merger 1.3.10 RC1");
+        List<Release> releases2 = Release.fromString("   Apache Sling Resource Merger    1.3.10   ");
+        
+        assertEquals(1, releases1.size());
+        assertEquals(1, releases2.size());
+        
+        Release rel1 = releases1.get(0);
+        Release rel2 = releases2.get(0);
 
         assertEquals("Resource Merger 1.3.10", rel1.getName());
         assertEquals("Apache Sling Resource Merger 1.3.10", rel1.getFullName());
@@ -47,11 +53,33 @@ public class ReleaseTest {
     }
 
     @Test
+    public void fromRepositoryDescriptionWithMultipleArtifacts() {
+        List<Release> releases = Release.fromString("Apache Sling Parent 35, Apache Sling Bundle Parent 35");
+        assertEquals(2, releases.size());
+    }
+    
+    @Test(expected = IllegalArgumentException.class)
+    public void noReleasesFailsFast() {
+        Release.fromString("");
+    }
+    
+    @Test
+    @Ignore("Broken after refactoring, needs separate issue")
+    public void releaseWithRCSuffixOnly() {
+        List<Release> releases = Release.fromString("Apache Sling Resource Resolver 1.6.12 RC");
+        
+        assertEquals(1, releases.size());
+        assertEquals("Apache Sling Resource Resolver 1.6.12", releases.get(0).getFullName());
+    }
+
+    @Test
     public void testReleaseParsingWithJIRAInfo() throws URISyntaxException, IOException {
         BufferedReader reader = new BufferedReader(new FileReader(new File(getClass().getResource("/jira_versions.txt").toURI())));
         reader.lines().forEach(line -> {
             if (!line.startsWith("#") && !"".equals(line)) {
-                Release jiraRelease = Release.fromString(line);
+                List<Release> jiraReleases = Release.fromString(line);
+                assertEquals(1, jiraReleases.size());
+                Release jiraRelease = jiraReleases.get(0);
                 String releaseFullName = jiraRelease.getFullName();
                 if (releaseFullName == null) {
                     fail("Failed to parse JIRA version: " + line);


[sling-org-apache-sling-committer-cli] 25/44: releng: updated parent pom version

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 31652fee1f98a0ded7aef2f26a103dc0fb75178b
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Apr 19 13:42:27 2019 +0200

    releng: updated parent pom version
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index a0458e6..0fc0dbd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,7 +17,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling-bundle-parent</artifactId>
-        <version>35-SNAPSHOT</version>
+        <version>36-SNAPSHOT</version>
         <relativePath />
     </parent>
 


[sling-org-apache-sling-committer-cli] 03/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit e54afe118b167cbb113c137be29d7093b0e9ebc2
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Mar 8 14:55:14 2019 +0200

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Implement command for generating release result email, still WIP.
---
 README.md                                          |  8 ++-
 docker-env.sample                                  |  1 -
 .../TallyVotesCommand.java => mail/Email.java}     | 39 ++++++++-----
 .../EmailThread.java}                              | 26 +++------
 .../sling/cli/impl/mail/VoteThreadFinder.java      | 61 ++++++++++++++++++++
 .../sling/cli/impl/release/TallyVotesCommand.java  | 67 +++++++++++++++++++++-
 6 files changed, 166 insertions(+), 36 deletions(-)

diff --git a/README.md b/README.md
index af04f3f..eabf20c 100644
--- a/README.md
+++ b/README.md
@@ -18,10 +18,16 @@ The image is built using `mvn package`. Afterwards it may be run with
     
 This invocation produces a list of available subcommands.
 
-Currently the only implemented command is generating the release vote email, for instance
+## Commands
+
+Generating a release vote email
 
     docker run --env-file=./docker-env apache/sling-cli release prepare-email $STAGING_REPOSITORY_ID
     
+Generating a release vote result email
+
+    docker run --env-file=./docker-env apache/sling-cli release tally-votes $STAGING_REPOSITORY_ID
+    
 ## Assumptions
 
 This tool assumes that the name of the staging repository matches the one of the version in Jira. For instance, the
diff --git a/docker-env.sample b/docker-env.sample
index 15454cf..7a2a892 100644
--- a/docker-env.sample
+++ b/docker-env.sample
@@ -12,4 +12,3 @@
 # ----------------------------------------------------------------------------------------
 ASF_USERNAME=changeme
 ASF_PASSWORD=changeme
-RELEASE_ID=42
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/mail/Email.java
similarity index 54%
copy from src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
copy to src/main/java/org/apache/sling/cli/impl/mail/Email.java
index 690a4d2..54ec66e 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/mail/Email.java
@@ -14,26 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sling.cli.impl.release;
+package org.apache.sling.cli.impl.mail;
 
-import org.apache.sling.cli.impl.Command;
-import org.osgi.service.component.annotations.Component;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+public class Email {
+    private String from;
+    private String subject;
+    private String body;
 
-@Component(service = Command.class, property = {
-    Command.PROPERTY_NAME_COMMAND+"=release",
-    Command.PROPERTY_NAME_SUBCOMMAND+"=tally-votes",
-    Command.PROPERTY_NAME_SUMMARY+"=Counts votes cast for a release and generates the result email"
-})
-public class TallyVotesCommand implements Command {
+    public String getFrom() {
+        return from;
+    }
+
+    public void setFrom(String from) {
+        this.from = from;
+    }
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+    public String getSubject() {
+        return subject;
+    }
 
-    @Override
-    public void execute(String target) {
-        logger.info("Tallying votes for release {}", target);
+    public void setSubject(String subject) {
+        this.subject = subject;
+    }
+
+    public String getBody() {
+        return body;
+    }
 
+    public void setBody(String body) {
+        this.body = body;
     }
 
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/mail/EmailThread.java
similarity index 54%
copy from src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
copy to src/main/java/org/apache/sling/cli/impl/mail/EmailThread.java
index 690a4d2..03ac673 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/mail/EmailThread.java
@@ -14,26 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sling.cli.impl.release;
+package org.apache.sling.cli.impl.mail;
 
-import org.apache.sling.cli.impl.Command;
-import org.osgi.service.component.annotations.Component;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import java.util.List;
 
-@Component(service = Command.class, property = {
-    Command.PROPERTY_NAME_COMMAND+"=release",
-    Command.PROPERTY_NAME_SUBCOMMAND+"=tally-votes",
-    Command.PROPERTY_NAME_SUMMARY+"=Counts votes cast for a release and generates the result email"
-})
-public class TallyVotesCommand implements Command {
-
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    @Override
-    public void execute(String target) {
-        logger.info("Tallying votes for release {}", target);
+public class EmailThread {
+    private List<Email> emails;
 
+    public List<Email> getEmails() {
+        return emails;
     }
 
+    public void setEmails(List<Email> emails) {
+        this.emails = emails;
+    }
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/VoteThreadFinder.java b/src/main/java/org/apache/sling/cli/impl/mail/VoteThreadFinder.java
new file mode 100644
index 0000000..0d39968
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/mail/VoteThreadFinder.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cli.impl.mail;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.osgi.service.component.annotations.Component;
+
+import com.google.gson.Gson;
+
+@Component(service = VoteThreadFinder.class)
+public class VoteThreadFinder {
+    
+    public EmailThread findVoteThread(String releaseName) throws IOException {
+        try ( CloseableHttpClient client = HttpClients.createDefault() ) {
+            
+            URI uri = new URIBuilder("https://lists.apache.org/api/stats.lua")
+                .addParameter("domain", "sling.apache.org")
+                .addParameter("list", "dev")
+                .addParameter("d", "lte=1M")
+                .addParameter("q", "[VOTE] Release " + releaseName)
+                .build();
+            
+            HttpGet get = new HttpGet(uri);
+            try ( CloseableHttpResponse response = client.execute(get)) {
+                try ( InputStream content = response.getEntity().getContent();
+                        InputStreamReader reader = new InputStreamReader(content)) {
+                    if ( response.getStatusLine().getStatusCode() != 200 )
+                        throw new IOException("Status line : " + response.getStatusLine());
+                    Gson gson = new Gson();
+                    return gson.fromJson(reader, EmailThread.class);
+                }
+            }
+        } catch (URISyntaxException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 690a4d2..8e34d87 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -16,8 +16,18 @@
  */
 package org.apache.sling.cli.impl.release;
 
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
 import org.apache.sling.cli.impl.Command;
+import org.apache.sling.cli.impl.mail.Email;
+import org.apache.sling.cli.impl.mail.EmailThread;
+import org.apache.sling.cli.impl.mail.VoteThreadFinder;
+import org.apache.sling.cli.impl.nexus.StagingRepository;
+import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
 import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -27,13 +37,66 @@ import org.slf4j.LoggerFactory;
     Command.PROPERTY_NAME_SUMMARY+"=Counts votes cast for a release and generates the result email"
 })
 public class TallyVotesCommand implements Command {
-
+    
+    // TODO - move to file
+    private static final String EMAIL_TEMPLATE ="\n" + 
+            "\n" + 
+            "To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
+            "Subject: [RESULT] [VOTE] Release Apache Sling ##RELEASE_NAME##\n" + 
+            "\n" + 
+            "Hi,\n" + 
+            "\n" + 
+            "The vote has passed with the following result :\n" + 
+            "\n" + 
+            "+1 (binding): ##BINDING_VOTERS##\n" + 
+            "\n" + 
+            "I will copy this release to the Sling dist directory and\n" + 
+            "promote the artifacts to the central Maven repository.\n";
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
+    @Reference
+    private StagingRepositoryFinder repoFinder;
+    
+    @Reference
+    private VoteThreadFinder voteThreadFinder;
+    
     @Override
     public void execute(String target) {
-        logger.info("Tallying votes for release {}", target);
+        try {
+            
+            StagingRepository repository = repoFinder.find(Integer.parseInt(target));
+            // TODO - release name cleanup does not belong here
+            String releaseName = repository.getDescription().replaceFirst(" RC[0-9]+", "");
+            EmailThread voteThread = voteThreadFinder.findVoteThread(releaseName);
+
+            // TODO - validate which voters are binding and list them separately in the email
+            String bindingVoters = voteThread.getEmails().stream()
+                .filter( e -> isPositiveVote(e) )
+                .map ( e -> e.getFrom().replaceAll("<.*>", "").trim() )
+                .collect(Collectors.joining(", "));
+            
+            String email = EMAIL_TEMPLATE
+                .replace("##RELEASE_NAME##", releaseName)
+                .replace("##BINDING_VOTERS##", bindingVoters);
+            
+            logger.info(email);
+            
+        } catch (IOException e) {
+            logger.warn("Command execution failed", e);
+        }
+    }
+
+    // TODO - better detection of '+1' votes
+    private boolean isPositiveVote(Email e) {
+        return cleanup(e.getBody()).contains("+1");
+    }
 
+    private String cleanup(String subject) {
+        String[] lines = subject.split("\\n");
+        return Arrays.stream(lines)
+            .filter( l -> !l.isEmpty() )
+            .filter( l -> !l.startsWith(">"))
+            .collect(Collectors.joining("\n"));
     }
 
 }


[sling-org-apache-sling-committer-cli] 30/44: Removed unused import

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 387ba6e1e76c5966aa750873eec92f03b330a930
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Apr 19 17:45:41 2019 +0300

    Removed unused import
---
 src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
index d6d9c51..a414f39 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
@@ -27,7 +27,6 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class ReleaseTest {


[sling-org-apache-sling-committer-cli] 39/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 28c81f3cc96b0f1aef38f48f2b5e924b4493874f
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 23 17:53:34 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Implement proper error handling in the VersionClient and MockJira.
---
 .../apache/sling/cli/impl/jira/ErrorResponse.java  |  36 ++++
 .../apache/sling/cli/impl/jira/VersionClient.java  |  42 +++--
 .../org/apache/sling/cli/impl/jira/MockJira.java   | 182 ++++++++++++++++++---
 .../sling/cli/impl/jira/VersionClientTest.java     |  15 +-
 4 files changed, 242 insertions(+), 33 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/jira/ErrorResponse.java b/src/main/java/org/apache/sling/cli/impl/jira/ErrorResponse.java
new file mode 100644
index 0000000..a0dc3eb
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/jira/ErrorResponse.java
@@ -0,0 +1,36 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+class ErrorResponse {
+    
+    private List<String> errorMessages = new ArrayList<>();
+    private Map<String, String> errors = new HashMap<>();
+    
+    public List<String> getErrorMessages() {
+        return errorMessages;
+    }
+    
+    public Map<String, String> getErrors() {
+        return errors;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 39419a9..8db031f 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -16,7 +16,6 @@
  */
 package org.apache.sling.cli.impl.jira;
 
-import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -39,6 +38,8 @@ import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
 import com.google.gson.reflect.TypeToken;
 import com.google.gson.stream.JsonWriter;
 
@@ -138,20 +139,38 @@ public class VersionClient {
                         InputStreamReader reader = new InputStreamReader(content)) {
                     
                     if (response.getStatusLine().getStatusCode() != 201) {
-                        // TODO - try and parse JSON error message, fall back to status code
-                        try ( BufferedReader bufferedReader = new BufferedReader(reader)) {
-                            String line;
-                            while  ( (line = bufferedReader.readLine()) != null )
-                                System.out.println(line);
-                        }
-                        
-                        throw new IOException("Status line : " + response.getStatusLine());
+                        throw newException(response, reader);
                     }
                 }
             }
         }
     }
 
+    private IOException newException(CloseableHttpResponse response, InputStreamReader reader) throws IOException {
+        
+        StringBuilder message = new StringBuilder();
+        message.append("Status line : " + response.getStatusLine());
+        
+        try {
+            Gson gson = new Gson();
+            ErrorResponse errors = gson.fromJson(reader, ErrorResponse.class);
+            if ( !errors.getErrorMessages().isEmpty() )
+                message.append(". Error messages: ")
+                    .append(errors.getErrorMessages());
+            
+            if ( !errors.getErrors().isEmpty() )
+                errors.getErrors().entrySet().stream()
+                    .forEach( e -> message.append(". Error for "  + e.getKey() + " : " + e.getValue()));
+            
+        } catch ( JsonIOException | JsonSyntaxException e) {
+            message.append(". Failed parsing response as JSON ( ")
+                .append(e.getMessage())
+                .append(" )");
+        }
+        
+        return new IOException(message.toString());
+    }
+
     private Optional<Version> findVersion(Predicate<Version> matcher, CloseableHttpClient client) throws IOException {
         
         HttpGet get = newGet("project/SLING/versions");
@@ -159,7 +178,7 @@ public class VersionClient {
             try (InputStream content = response.getEntity().getContent();
                     InputStreamReader reader = new InputStreamReader(content)) {
                 if (response.getStatusLine().getStatusCode() != 200)
-                    throw new IOException("Status line : " + response.getStatusLine());
+                    throw newException(response, reader);
                 
                 Gson gson = new Gson();
                 Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
@@ -186,7 +205,8 @@ public class VersionClient {
             try (InputStream content = response.getEntity().getContent();
                     InputStreamReader reader = new InputStreamReader(content)) {
                 if (response.getStatusLine().getStatusCode() != 200)
-                    throw new IOException("Status line : " + response.getStatusLine());
+                    throw newException(response, reader);
+
                 Gson gson = new Gson();
                 VersionRelatedIssuesCount issuesCount = gson.fromJson(reader, VersionRelatedIssuesCount.class);
                 
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
index 903f8b0..3df51ca 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/MockJira.java
@@ -16,21 +16,35 @@
  */
 package org.apache.sling.cli.impl.jira;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.net.InetSocketAddress;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.apache.commons.io.IOUtils;
 import org.junit.rules.ExternalResource;
 
+import com.google.gson.Gson;
+import com.sun.net.httpserver.Authenticator;
+import com.sun.net.httpserver.BasicAuthenticator;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpPrincipal;
 import com.sun.net.httpserver.HttpServer;
 
 public class MockJira extends ExternalResource {
     
     private static final Pattern VERSION_RELATED_ISSUES = Pattern.compile("^/jira/rest/api/2/version/(\\d+)/relatedIssueCounts$");
     
+    static final String AUTH_USER = "jira-user";
+    static final String AUTH_PWD = "jira-password";
+    
     public static void main(String[] args) throws Throwable {
         
         MockJira mj = new MockJira();
@@ -43,35 +57,103 @@ public class MockJira extends ExternalResource {
     @Override
     protected void before() throws Throwable {
         server = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
-        server.createContext("/", httpExchange -> {
+        HttpContext rootContext = server.createContext("/");
+        rootContext.setAuthenticator(new BasicAuthenticator(getClass().getSimpleName()) {
             
-            if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/project/SLING/versions") ) {
-                httpExchange.sendResponseHeaders(200, 0);
-                try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
-                        OutputStream out = httpExchange.getResponseBody() ) {
-                    IOUtils.copy(in, out);
-                }
-            } else {
-                Matcher matcher = VERSION_RELATED_ISSUES.matcher(httpExchange.getRequestURI().getPath());
-                if ( matcher.matches() ) {
-                    int version = Integer.parseInt(matcher.group(1));
-                    InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
-                    if ( in == null  ) {
-                        httpExchange.sendResponseHeaders(404, -1);
-                    } else {
-                        httpExchange.sendResponseHeaders(200, 0);
-                        try ( OutputStream out = httpExchange.getResponseBody() ) {
-                            IOUtils.copy(in, out);
-                        }
+            @Override
+            public boolean checkCredentials(String username, String password) {
+                return AUTH_USER.equals(username) && AUTH_PWD.equals(password);
+            }
+            
+            @Override
+            public Result authenticate(HttpExchange t) {
+                if ( t.getRequestMethod().contentEquals("GET") )
+                        return new Authenticator.Success(new HttpPrincipal("anonymous", getClass().getSimpleName()));
+                return super.authenticate(t);
+            }
+        });
+        rootContext.setHandler(httpExchange -> {
+            
+            switch ( httpExchange.getRequestMethod() ) {
+            case "GET":
+                if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/project/SLING/versions") ) {
+                    httpExchange.sendResponseHeaders(200, 0);
+                    try ( InputStream in = getClass().getResourceAsStream("/jira/versions.json");
+                            OutputStream out = httpExchange.getResponseBody() ) {
+                        IOUtils.copy(in, out);
                     }
                 } else {
-                    httpExchange.sendResponseHeaders(400, -1);
+                    Matcher matcher = VERSION_RELATED_ISSUES.matcher(httpExchange.getRequestURI().getPath());
+                    if ( matcher.matches() ) {
+                        int version = Integer.parseInt(matcher.group(1));
+                        InputStream in = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version + ".json");
+                        if ( in == null  ) {
+                            httpExchange.sendResponseHeaders(404, -1);
+                        } else {
+                            httpExchange.sendResponseHeaders(200, 0);
+                            try ( OutputStream out = httpExchange.getResponseBody() ) {
+                                IOUtils.copy(in, out);
+                            }
+                        }
+                    } else {
+                        httpExchange.sendResponseHeaders(400, -1);
+                    }
+                }
+                break;
+            case "POST":
+                if ( httpExchange.getRequestURI().getPath().equals("/jira/rest/api/2/version") ) {
+                    handleCreateVersion(httpExchange);
+                    return;
                 }
+                httpExchange.sendResponseHeaders(400, -1);
+                break;
+                default:
+                    httpExchange.sendResponseHeaders(400, -1);
             }
+            
         });
         
         server.start();
     }
+
+    private void handleCreateVersion(HttpExchange httpExchange) throws IOException {
+        Gson gson = new Gson();
+        try ( InputStreamReader reader = new InputStreamReader(httpExchange.getRequestBody())) {
+            VersionToCreate version = gson.fromJson(reader, VersionToCreate.class);
+            if ( version.getName() == null || version.getName().isEmpty() ) {
+                error(httpExchange, gson, 
+                        er -> er.getErrors().put("name", "You must specify a valid version name"));
+                return;
+            }
+            
+            if ( !"SLING".equals(version.getProject()) ) {
+                error(httpExchange, gson, 
+                        er -> er.getErrorMessages().add("Project must be specified to create a version."));
+                return;
+            }
+            
+            // note not all fields are sent, projectId and self are missing
+            CreatedVersion createdVersion = new CreatedVersion();
+            createdVersion.archived = false;
+            createdVersion.id = ThreadLocalRandom.current().nextInt();
+            createdVersion.released = false;
+            createdVersion.name = version.getName();
+            
+            try ( OutputStreamWriter out = new OutputStreamWriter(httpExchange.getResponseBody()) ) {
+                httpExchange.sendResponseHeaders(201, 0);
+                gson.toJson(createdVersion, out);
+            }
+        }
+    }
+    
+    private void error(HttpExchange httpExchange, Gson gson, Consumer<ErrorResponse> c) throws IOException {
+        try ( OutputStreamWriter out = new OutputStreamWriter(httpExchange.getResponseBody()) ) {
+            httpExchange.sendResponseHeaders(400, 0);
+            ErrorResponse er = new ErrorResponse();
+            c.accept(er);
+            gson.toJson(er, out);
+        }
+    }
     
     @Override
     protected void after() {
@@ -83,4 +165,64 @@ public class MockJira extends ExternalResource {
         
         return server.getAddress().getPort();
     }
+    
+    static class VersionToCreate {
+        private String name;
+        private String project;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public String getProject() {
+            return project;
+        }
+
+        public void setProject(String project) {
+            this.project = project;
+        }
+    }
+    
+    static class CreatedVersion {
+        private String name;
+        private int id;
+        private boolean archived;
+        private boolean released;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        public void setId(int id) {
+            this.id = id;
+        }
+
+        public boolean isArchived() {
+            return archived;
+        }
+
+        public void setArchived(boolean archived) {
+            this.archived = archived;
+        }
+
+        public boolean isReleased() {
+            return released;
+        }
+
+        public void setReleased(boolean released) {
+            this.released = released;
+        }
+    }
 }
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
index 0d67dee..2951b3b 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionClientTest.java
@@ -21,6 +21,7 @@ import static org.hamcrest.Matchers.notNullValue;
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertThat;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -38,8 +39,8 @@ public class VersionClientTest {
     static {
         SYSTEM_PROPS.put("asf.username", "asf-user");
         SYSTEM_PROPS.put("asf.password", "asf-password");
-        SYSTEM_PROPS.put("jira.username", "jira-user");
-        SYSTEM_PROPS.put("jira.password", "jira-password");
+        SYSTEM_PROPS.put("jira.username", MockJira.AUTH_USER);
+        SYSTEM_PROPS.put("jira.password", MockJira.AUTH_PWD);
     }
     
     @Rule
@@ -92,4 +93,14 @@ public class VersionClientTest {
         
         assertThat("successor", successor, nullValue());
     }
+    
+    @Test
+    public void createVersion() throws IOException {
+        versionClient.create("XSS Protection API 2.0.10");
+    }
+    
+    @Test(expected = IOException.class)
+    public void illegalVersionFails() throws IOException {
+        versionClient.create("");
+    }
 }


[sling-org-apache-sling-committer-cli] 09/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit c2beebd3b7902a4f6c8e1cdbff13fc488fd4e268
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Wed Mar 20 10:14:33 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Enable class data sharing. This yields a small (~120ms) startup time improvement
    in local testing.
---
 Dockerfile                             | 6 +++++-
 src/main/resources/scripts/launcher.sh | 3 ++-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 1338fcb..c29cd1e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,6 +12,10 @@
 
 FROM openjdk:8-jre-alpine
 MAINTAINER dev@sling.apache.org
+
+# Generate class data sharing
+RUN java -Xshare:dump
+
 # escaping required to properly handle arguments with spaces
 ENTRYPOINT ["/usr/share/sling-cli/bin/launcher.sh"]
 
@@ -27,4 +31,4 @@ ADD target/classes/conf /usr/share/sling-cli/conf
 ADD target/artifacts /usr/share/sling-cli/artifacts
 # Add the service itself
 ARG FEATURE_FILE
-ADD ${FEATURE_FILE} /usr/share/sling-cli/sling-cli.feature
\ No newline at end of file
+ADD ${FEATURE_FILE} /usr/share/sling-cli/sling-cli.feature
diff --git a/src/main/resources/scripts/launcher.sh b/src/main/resources/scripts/launcher.sh
index 6f0bcfb..7b306ba 100755
--- a/src/main/resources/scripts/launcher.sh
+++ b/src/main/resources/scripts/launcher.sh
@@ -19,6 +19,7 @@ ARGS_PROP="exec.args=$@"
 
 # Use exec to become pid 1, see https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
 exec /usr/bin/java \
+     -Xshare:on \
 	 -Dorg.slf4j.simpleLogger.logFile=/dev/null \
 	 -Dlogback.configurationFile=file:/usr/share/sling-cli/conf/logback-default.xml \
 	 -jar /usr/share/sling-cli/launcher/org.apache.sling.feature.launcher.jar \
@@ -26,4 +27,4 @@ exec /usr/bin/java \
 	 -c /usr/share/sling-cli/artifacts \
 	 -D "$ARGS_PROP" \
 	 -V "asf.username=${ASF_USERNAME}" \
-	 -V "asf.password=${ASF_PASSWORD}"
\ No newline at end of file
+	 -V "asf.password=${ASF_PASSWORD}"


[sling-org-apache-sling-committer-cli] 29/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 0ec8b05aed0c59bd821bb94195e407ba67d1b29f
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Apr 19 17:40:12 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    Initial work on read-only Jira release command.
---
 .../org/apache/sling/cli/impl/jira/Version.java    |    6 +
 .../apache/sling/cli/impl/jira/VersionFinder.java  |  134 +-
 .../cli/impl/release/PrepareVoteEmailCommand.java  |    2 +-
 .../org/apache/sling/cli/impl/release/Release.java |   50 +
 .../sling/cli/impl/release/UpdateJiraCommand.java  |   65 +
 .../sling/cli/impl/jira/VersionFinderTest.java     |   93 +
 .../apache/sling/cli/impl/release/ReleaseTest.java |   15 +
 src/test/resources/README.md                       |   11 +
 .../jira/relatedIssueCounts/12329667.json          |    6 +
 .../jira/relatedIssueCounts/12329844.json          |    6 +
 src/test/resources/jira/versions.json              | 2087 ++++++++++++++++++++
 11 files changed, 2447 insertions(+), 28 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/jira/Version.java b/src/main/java/org/apache/sling/cli/impl/jira/Version.java
index 7cce8d5..173971e 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/Version.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/Version.java
@@ -44,4 +44,10 @@ public class Version {
     public void setRelatedIssuesCount(int relatedIssuesCount) {
         this.issuesFixedCount = relatedIssuesCount;
     }
+    
+    @Override
+    public String toString() {
+        
+        return "Version: " + name + " (id=" + id+", fixed issues="+issuesFixedCount+")";
+    }
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
index a752be0..2c2be18 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
@@ -21,7 +21,9 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.lang.reflect.Type;
 import java.util.List;
-import java.util.stream.Collectors;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -33,14 +35,25 @@ import org.osgi.service.component.annotations.Component;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 
+/**
+ * Access the ASF <em>Jira</em> instance and looks up project version data.
+ */
 @Component(service = VersionFinder.class)
 public class VersionFinder {
 
-    public Version find(String versionName) {
+    /**
+     * Finds a Jira version which matches the specified release
+     * 
+     * @param release the release
+     * @return the version
+     * @throws IllegalArgumentException when no matching Jira release is found
+     */
+    public Version find(Release release) {
         Version version;
         
         try (CloseableHttpClient client = HttpClients.createDefault()) {
-            version = findVersion(versionName, client);
+            version = findVersion( v -> release.getName().equals(v.getName()), client)
+                    .orElseThrow( () -> new IllegalArgumentException("No version found with name " + release.getName()));
             populateRelatedIssuesCount(client, version);
         } catch ( IOException e ) {
             throw new RuntimeException(e);
@@ -49,48 +62,115 @@ public class VersionFinder {
         return version;
     }
 
-    private Version findVersion(String versionName, CloseableHttpClient client) throws IOException {
+    /**
+     * Finds a version that is the successor of the version of the specified
+     * <tt>release</tt>
+     * 
+     * <p>
+     * A successor has the same base name but a higher version. For instance, the
+     * <em>XSS Protection API 2.1.6</em> is succeeded by <em>XSS Protection API
+     * 2.1.8</em>.
+     * </p>
+     * 
+     * <p>
+     * If multiple successors are found the one which is closest in terms of
+     * versioning is returned.
+     * </p>
+     * 
+     * @param release the release to find a successor for
+     * @return the successor version, possibly <code>null</code>
+     * @throws IOException in case of communication errors with Jira
+     */
+    public Version findSuccessorVersion(Release release) {
         Version version;
-        HttpGet get = new HttpGet("https://issues.apache.org/jira/rest/api/2/project/SLING/versions");
-        get.addHeader("Accept", "application/json");
+        
+        try ( CloseableHttpClient client = HttpClients.createDefault() ) {
+            Optional<Version> opt = findVersion ( 
+                    v -> {
+                        Release releaseFromVersion = Release.fromString(v.getName()).get(0);
+                        return 
+                            releaseFromVersion.getComponent().equals(release.getComponent())
+                                && new org.osgi.framework.Version(releaseFromVersion.getVersion()).compareTo(new org.osgi.framework.Version(release.getVersion())) > 0;
+                    },client);
+            if ( !opt.isPresent() )
+                return null;
+            version = opt.get();
+            populateRelatedIssuesCount(client, version);
+        } catch ( IOException e ) {
+            throw new RuntimeException(e);
+        }
+        
+        return version;
+    }
+
+    private Optional<Version> findVersion(Predicate<Version> matcher, CloseableHttpClient client) throws IOException {
+        
+        return doWithJiraVersions(client, reader -> {
+            Gson gson = new Gson();
+            Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
+            List<Version> versions = gson.fromJson(reader, collectionType);
+            return versions.stream()
+                    .filter( v -> v.getName().length() > 1) // avoid old '3' release
+                    .filter(matcher)
+                    .sorted(VersionFinder::compare)
+                    .findFirst();
+        });
+    }
+    
+    protected <T> T doWithJiraVersions(CloseableHttpClient client, Function<InputStreamReader, T> parserCallback) throws IOException {
+        HttpGet get = newGet("project/SLING/versions");
         try (CloseableHttpResponse response = client.execute(get)) {
             try (InputStream content = response.getEntity().getContent();
                     InputStreamReader reader = new InputStreamReader(content)) {
                 if (response.getStatusLine().getStatusCode() != 200)
                     throw new IOException("Status line : " + response.getStatusLine());
-                Gson gson = new Gson();
-                Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
-                List<Version> versions = gson.fromJson(reader, collectionType);
-                List<String> filter = Release.fromString(versionName)
-                        .stream()
-                        .map( Release::getName )
-                        .collect(Collectors.toList());
                 
-                version = versions.stream()
-                    .filter(v -> filter.contains(v.getName()))
-                    .findFirst()
-                    .orElseThrow( () -> new IllegalArgumentException("No version found with name " + versionName));
+                return parserCallback.apply(reader);
             }
         }
-        return version;
     }
-
-    private void populateRelatedIssuesCount(CloseableHttpClient client, Version version) throws IOException {
-
-        HttpGet get = new HttpGet("https://issues.apache.org/jira/rest/api/2/version/" + version.getId() +"/relatedIssueCounts");
-        get.addHeader("Accept", "application/json");
+    
+    protected <T> T doWithRelatedIssueCounts(CloseableHttpClient client, Version version, Function<InputStreamReader, T> parserCallback) throws IOException {
+        
+        HttpGet get = newGet("version/" + version.getId() +"/relatedIssueCounts");
         try (CloseableHttpResponse response = client.execute(get)) {
             try (InputStream content = response.getEntity().getContent();
                     InputStreamReader reader = new InputStreamReader(content)) {
                 if (response.getStatusLine().getStatusCode() != 200)
                     throw new IOException("Status line : " + response.getStatusLine());
-                Gson gson = new Gson();
-                VersionRelatedIssuesCount issuesCount = gson.fromJson(reader, VersionRelatedIssuesCount.class);
-                
-                version.setRelatedIssuesCount(issuesCount.getIssuesFixedCount());
+                return parserCallback.apply(reader);
             }
         }
     }
+    
+    private HttpGet newGet(String suffix) {
+        HttpGet get = new HttpGet("https://issues.apache.org/jira/rest/api/2/" + suffix);
+        get.addHeader("Accept", "application/json");
+        return get;
+    }
+    
+    private void populateRelatedIssuesCount(CloseableHttpClient client, Version version) throws IOException {
+        
+        doWithRelatedIssueCounts(client, version, reader ->  {
+            Gson gson = new Gson();
+            VersionRelatedIssuesCount issuesCount = gson.fromJson(reader, VersionRelatedIssuesCount.class);
+            
+            version.setRelatedIssuesCount(issuesCount.getIssuesFixedCount());
+
+            return null;
+        });
+    }
+    
+    private static int compare(Version v1, Version v2) {
+        // version names will never map to multiple release names
+        Release r1 = Release.fromString(v1.getName()).get(0);
+        Release r2 = Release.fromString(v2.getName()).get(0);
+        
+        org.osgi.framework.Version ver1 = new org.osgi.framework.Version(r1.getVersion());
+        org.osgi.framework.Version ver2 = new org.osgi.framework.Version(r2.getVersion());
+        
+        return ver1.compareTo(ver2);
+    }
 
     static class VersionRelatedIssuesCount {
 
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
index 4cc4222..8656362 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -89,7 +89,7 @@ public class PrepareVoteEmailCommand implements Command {
             StagingRepository repo = repoFinder.find(repoId);
             List<Release> releases = Release.fromString(repo.getDescription());
             List<Version> versions = releases.stream()
-                    .map( r -> versionFinder.find(r.getName()))
+                    .map( r -> versionFinder.find(r))
                     .collect(Collectors.toList());
             
             String releaseName = releases.stream()
diff --git a/src/main/java/org/apache/sling/cli/impl/release/Release.java b/src/main/java/org/apache/sling/cli/impl/release/Release.java
index 95786b3..2428996 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/Release.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/Release.java
@@ -22,6 +22,9 @@ import java.util.Objects;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+/**
+ * Provides structured access to the components of a release name.
+ */
 public final class Release {
 
     /*
@@ -70,22 +73,69 @@ public final class Release {
         
     }
     
+    /**
+     * Returns the full name, e.g. <em>Apache Sling Foo 1.0.2</em>
+     * 
+     * @return the full name
+     */
     public String getFullName() {
         return fullName;
     }
     
+    /**
+     * Returns the name, e.g. <em>Foo 1.0.2</em>
+     * 
+     * @return the name
+     */
     public String getName() {
         return name;
     }
     
+    /**
+     * Returns the version, e.g. <em>1.0.2</em>
+     * 
+     * @return the version 
+     */
     public String getVersion() {
         return version;
     }
 
+    /**
+     * Returns the component, e.g. <tt>Foo</tt>
+     * 
+     * @return the component
+     */
     public String getComponent() {
         return component;
     }
 
+    /**
+     * Creates a new Release object that corresponds to the next release name
+     * 
+     * <p>The next object is identical to <tt>this</tt> object, except the fact that the
+     * micro component of the version is increased by two.</P>
+     * 
+     * <p>For instance, the next version of <tt>Apache Sling Foo 1.0.2</tt> is <tt>Apache Sling Foo 1.0.4</tt>.</p>
+     * 
+     * @return the next release
+     */
+    public Release next() {
+        
+        // assumption is that the release object is well-formed
+        int lastSeparator = fullName.lastIndexOf('.'); // Apache Sling Foo 1.0.2 -> 1.0.4
+        int increment = 2;
+        if ( lastSeparator == -1 ) {
+            lastSeparator = fullName.lastIndexOf(' '); // Apache Sling Bar 2 -> 3
+            increment = 1;
+        }
+        
+        int componentToIncrement = Integer.parseInt(fullName.substring(lastSeparator + 1));
+        
+        String unchangedPart = fullName.substring(0, lastSeparator + 1);
+        
+        return Release.fromString(unchangedPart + ( componentToIncrement + increment )).get(0);
+    }
+    
     @Override
     public int hashCode() {
         return fullName.hashCode();
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java
new file mode 100644
index 0000000..20d2acc
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java
@@ -0,0 +1,65 @@
+/*
+ * 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.cli.impl.release;
+
+import java.io.IOException;
+
+import org.apache.sling.cli.impl.Command;
+import org.apache.sling.cli.impl.jira.Version;
+import org.apache.sling.cli.impl.jira.VersionFinder;
+import org.apache.sling.cli.impl.nexus.StagingRepository;
+import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Command.class, property = {
+        Command.PROPERTY_NAME_COMMAND+"=release",
+        Command.PROPERTY_NAME_SUBCOMMAND+"=update-jira",
+        Command.PROPERTY_NAME_SUMMARY+"=Releases the current version, closes versions fixed in the current version and creates a new version if needed"
+    })
+public class UpdateJiraCommand implements Command {
+
+    @Reference
+    private StagingRepositoryFinder repoFinder;
+    
+    @Reference
+    private VersionFinder versionFinder;
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void execute(String target) {
+        try {
+            StagingRepository repo = repoFinder.find(Integer.parseInt(target));
+            for (Release release : Release.fromString(repo.getDescription()) ) {
+                Version version = versionFinder.find(release);
+                logger.info("Found version {}", version);
+                Version successorVersion = versionFinder.findSuccessorVersion(release);
+                logger.info("Found successor version {}", successorVersion);
+                if ( successorVersion == null ) {
+                    Release next = release.next();
+                    logger.info("Would create version {}", next);
+                }
+                    
+            }
+        } catch (IOException e) {
+            logger.warn("Failed executing command", e);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
new file mode 100644
index 0000000..04a97bd
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.cli.impl.jira;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.function.Function;
+
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.sling.cli.impl.release.Release;
+import org.junit.Test;
+
+public class VersionFinderTest {
+
+    private VersionFinder finder = new StubVersionFinder();
+    
+    @Test
+    public void findMatchingVersion() {
+        
+        finder = new StubVersionFinder();
+        Version version = finder.find(Release.fromString("XSS Protection API 1.0.2").get(0));
+        
+        assertThat("version", version, notNullValue());
+        assertThat("version.name", version.getName(), equalTo("XSS Protection API 1.0.2"));
+        assertThat("version.id", version.getId(), equalTo(12329667));
+        assertThat("version.issuesFixedCount", version.getIssuesFixedCount(), equalTo(1)); 
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void missingVersionNotFound() {
+        
+        finder.find(Release.fromString("XSS Protection API 1.0.3").get(0));
+    }
+    
+    @Test
+    public void findSuccessorVersion() {
+        Version successor = finder.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.2").get(0));
+        
+        assertThat("successor", successor, notNullValue());
+        assertThat("successor.name", successor.getName(), equalTo("XSS Protection API 1.0.4"));
+    }
+
+    @Test
+    public void noSuccessorVersion() {
+        Version successor = finder.findSuccessorVersion(Release.fromString("XSS Protection API 1.0.16").get(0));
+        
+        assertThat("successor", successor, nullValue());
+    }
+    
+    private static final class StubVersionFinder extends VersionFinder {
+        @Override
+        protected <T> T doWithJiraVersions(CloseableHttpClient client, Function<InputStreamReader, T> parserCallback)
+                throws IOException {
+            
+            try ( InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/jira/versions.json")) ) {
+                return parserCallback.apply(reader);
+            }
+        }
+        
+        @Override
+        protected <T> T doWithRelatedIssueCounts(CloseableHttpClient client, Version version,
+                Function<InputStreamReader, T> parserCallback) throws IOException {
+            
+            InputStream stream = getClass().getResourceAsStream("/jira/relatedIssueCounts/" + version.getId()+".json");
+            if ( stream == null )
+                throw new IllegalArgumentException("No related issues count for version " + version.getId() + " (" + version.getName() + ")");
+            
+            try ( InputStreamReader reader = new InputStreamReader(stream) ) {
+                return parserCallback.apply(reader);
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
index 8d16905..d6d9c51 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseTest.java
@@ -91,5 +91,20 @@ public class ReleaseTest {
         reader.close();
     }
 
+    @Test
+    public void nextVersion() {
+        Release release = Release.fromString("Apache Sling Foo 1.0.2").get(0);
+        Release next = release.next();
+        
+        assertEquals("Apache Sling Foo 1.0.4", next.getFullName());
+    }
+    
+    @Test
+    public void nextVersionWithSingleNumber() {
+        Release release = Release.fromString("Apache Sling Bar 2").get(0);
+        Release next = release.next();
+        
+        assertEquals("Apache Sling Bar 3", next.getFullName());
+    }
 
 }
diff --git a/src/test/resources/README.md b/src/test/resources/README.md
new file mode 100644
index 0000000..3158b2f
--- /dev/null
+++ b/src/test/resources/README.md
@@ -0,0 +1,11 @@
+# Test resources
+
+These resources are downloaded from remote servers and maybe slightly tweaked to make them usable for testing.
+
+## Jira
+
+```
+http https://issues.apache.org/jira/rest/api/2/project/SLING/versions | jq '.[0:199]' > src/test/resources/jira/versions.json
+http https://issues.apache.org/jira/rest/api/2/version/12329667/relatedIssueCounts | jq '.' > src/test/resources/jira/relatedIssueCounts/12329667.json
+http https://issues.apache.org/jira/rest/api/2/version/12329844/relatedIssueCounts | jq '.' > src/test/resources/jira/relatedIssueCounts/12329844.json
+```
\ No newline at end of file
diff --git a/src/test/resources/jira/relatedIssueCounts/12329667.json b/src/test/resources/jira/relatedIssueCounts/12329667.json
new file mode 100644
index 0000000..ac7b4e9
--- /dev/null
+++ b/src/test/resources/jira/relatedIssueCounts/12329667.json
@@ -0,0 +1,6 @@
+{
+  "self": "https://issues.apache.org/jira/rest/api/2/version/12329667",
+  "issuesFixedCount": 1,
+  "issuesAffectedCount": 4,
+  "issueCountWithCustomFieldsShowingVersion": 0
+}
diff --git a/src/test/resources/jira/relatedIssueCounts/12329844.json b/src/test/resources/jira/relatedIssueCounts/12329844.json
new file mode 100644
index 0000000..3f9c8aa
--- /dev/null
+++ b/src/test/resources/jira/relatedIssueCounts/12329844.json
@@ -0,0 +1,6 @@
+{
+  "self": "https://issues.apache.org/jira/rest/api/2/version/12329844",
+  "issuesFixedCount": 4,
+  "issuesAffectedCount": 2,
+  "issueCountWithCustomFieldsShowingVersion": 0
+}
diff --git a/src/test/resources/jira/versions.json b/src/test/resources/jira/versions.json
new file mode 100644
index 0000000..d7e5226
--- /dev/null
+++ b/src/test/resources/jira/versions.json
@@ -0,0 +1,2087 @@
+[
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12329024",
+    "id": "12329024",
+    "description": "Initial release",
+    "name": "XSS Protection API 1.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-03-20",
+    "userReleaseDate": "20/Mar/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12329667",
+    "id": "12329667",
+    "name": "XSS Protection API 1.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-03-30",
+    "userReleaseDate": "30/Mar/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12329844",
+    "id": "12329844",
+    "name": "XSS Protection API 1.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-08-03",
+    "userReleaseDate": "03/Aug/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333112",
+    "id": "12333112",
+    "name": "XSS Protection API 1.0.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-10-12",
+    "userReleaseDate": "12/Oct/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333775",
+    "id": "12333775",
+    "name": "XSS Protection API 1.0.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-02-02",
+    "userReleaseDate": "02/Feb/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338062",
+    "id": "12338062",
+    "name": "XSS Protection API 1.0.12",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-08-19",
+    "userReleaseDate": "19/Aug/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338105",
+    "id": "12338105",
+    "description": "Maintenance Release",
+    "name": "XSS Protection API 1.0.14",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-08-29",
+    "userReleaseDate": "29/Aug/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338187",
+    "id": "12338187",
+    "name": "XSS Protection API 1.0.16",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-10-31",
+    "userReleaseDate": "31/Oct/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12323492",
+    "id": "12323492",
+    "description": "First public release",
+    "name": "Tenant 1.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2013-04-26",
+    "userReleaseDate": "26/Apr/13",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12324350",
+    "id": "12324350",
+    "name": "Tenant 1.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-08-18",
+    "userReleaseDate": "18/Aug/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12327647",
+    "id": "12327647",
+    "name": "Tenant 1.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-01-16",
+    "userReleaseDate": "16/Jan/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12339163",
+    "id": "12339163",
+    "description": "Maintenance release",
+    "name": "Tenant 1.1.2",
+    "archived": false,
+    "released": true,
+    "startDate": "2017-01-17",
+    "releaseDate": "2018-09-02",
+    "userStartDate": "17/Jan/17",
+    "userReleaseDate": "02/Sep/18",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316002",
+    "id": "12316002",
+    "description": "First public release",
+    "name": "Scripting JST 2.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-12-20",
+    "userReleaseDate": "20/Dec/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316003",
+    "id": "12316003",
+    "description": "Maintenance Release",
+    "name": "Scripting JST 2.0.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-08-16",
+    "userReleaseDate": "16/Aug/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12322940",
+    "id": "12322940",
+    "description": "Maintenance Release",
+    "name": "Scripting JST 2.0.8",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12324674",
+    "id": "12324674",
+    "description": "First public release",
+    "name": "Service User Mapper 1.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-02-06",
+    "userReleaseDate": "06/Feb/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326146",
+    "id": "12326146",
+    "name": "Service User Mapper 1.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-06-02",
+    "userReleaseDate": "02/Jun/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12327057",
+    "id": "12327057",
+    "name": "Service User Mapper 1.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-07-14",
+    "userReleaseDate": "14/Jul/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12327372",
+    "id": "12327372",
+    "name": "Service User Mapper 1.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-03-02",
+    "userReleaseDate": "02/Mar/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12329544",
+    "id": "12329544",
+    "name": "Service User Mapper 1.2.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-03-16",
+    "userReleaseDate": "16/Mar/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12329687",
+    "id": "12329687",
+    "name": "Service User Mapper 1.2.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-11-11",
+    "userReleaseDate": "11/Nov/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334454",
+    "id": "12334454",
+    "name": "Service User Mapper 1.2.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-12-22",
+    "userReleaseDate": "22/Dec/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338907",
+    "id": "12338907",
+    "name": "Service User Mapper 1.2.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-03-30",
+    "userReleaseDate": "30/Mar/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340275",
+    "id": "12340275",
+    "name": "Service User Mapper 1.3.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-01",
+    "userReleaseDate": "01/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340324",
+    "id": "12340324",
+    "name": "Service User Mapper 1.3.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-22",
+    "userReleaseDate": "22/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340608",
+    "id": "12340608",
+    "name": "Service User Mapper 1.3.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-07-21",
+    "userReleaseDate": "21/Jul/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313245",
+    "id": "12313245",
+    "description": "First public release",
+    "name": "Servlets Resolver 2.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313376",
+    "id": "12313376",
+    "description": "Maintenance Release",
+    "name": " Servlets Resolver 2.0.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314051",
+    "id": "12314051",
+    "description": "Maintenance Release",
+    "name": "Servlets Resolver 2.0.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-10-10",
+    "userReleaseDate": "10/Oct/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314292",
+    "id": "12314292",
+    "description": "New Feature Release",
+    "name": "Servlets Resolver 2.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-10-08",
+    "userReleaseDate": "08/Oct/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315301",
+    "id": "12315301",
+    "description": "Maintenance Release",
+    "name": "Servlets Resolver 2.1.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-01-30",
+    "userReleaseDate": "30/Jan/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12319517",
+    "id": "12319517",
+    "description": "Maintenance Release",
+    "name": "Servlets Resolver 2.2.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-11-15",
+    "userReleaseDate": "15/Nov/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12323497",
+    "id": "12323497",
+    "name": "Servlets Resolver 2.2.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2013-04-18",
+    "userReleaseDate": "18/Apr/13",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12324330",
+    "id": "12324330",
+    "name": "Servlets Resolver 2.3.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-02-24",
+    "userReleaseDate": "24/Feb/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326267",
+    "id": "12326267",
+    "name": "Servlets Resolver 2.3.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-03-31",
+    "userReleaseDate": "31/Mar/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326701",
+    "id": "12326701",
+    "name": "Servlets Resolver 2.3.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-11-14",
+    "userReleaseDate": "14/Nov/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328993",
+    "id": "12328993",
+    "name": "Servlets Resolver 2.3.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-11-18",
+    "userReleaseDate": "18/Nov/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12329016",
+    "id": "12329016",
+    "name": "Servlets Resolver 2.3.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-10-05",
+    "userReleaseDate": "05/Oct/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333753",
+    "id": "12333753",
+    "description": "New Resource Provider API (2.11.0)",
+    "name": "Servlets Resolver 2.4.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-02-08",
+    "userReleaseDate": "08/Feb/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334794",
+    "id": "12334794",
+    "name": "Servlets Resolver 2.4.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-02-19",
+    "userReleaseDate": "19/Feb/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334853",
+    "id": "12334853",
+    "name": "Servlets Resolver 2.4.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-07-13",
+    "userReleaseDate": "13/Jul/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12337881",
+    "id": "12337881",
+    "description": "Maintenance Release",
+    "name": "Servlets Resolver 2.4.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-10-22",
+    "userReleaseDate": "22/Oct/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338547",
+    "id": "12338547",
+    "name": "Servlets Resolver 2.4.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-12-06",
+    "userReleaseDate": "06/Dec/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338893",
+    "id": "12338893",
+    "name": "Servlets Resolver 2.4.10",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-12-24",
+    "userReleaseDate": "24/Dec/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338894",
+    "id": "12338894",
+    "name": "Servlets Resolver 2.4.12",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-09",
+    "userReleaseDate": "09/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340523",
+    "id": "12340523",
+    "name": "Servlets Resolver 2.4.14",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-08-28",
+    "userReleaseDate": "28/Aug/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313247",
+    "id": "12313247",
+    "description": "First public release",
+    "name": "Servlets Post 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313275",
+    "id": "12313275",
+    "description": "Maintenance Release",
+    "name": "Servlets Post 2.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314180",
+    "id": "12314180",
+    "description": "New feature release",
+    "name": "Servlets Post 2.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-10-08",
+    "userReleaseDate": "08/Oct/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315287",
+    "id": "12315287",
+    "description": "Maintenance Release",
+    "name": "Servlets Post 2.1.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-06-28",
+    "userReleaseDate": "28/Jun/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12319641",
+    "id": "12319641",
+    "description": "Maintenance Release",
+    "name": "Servlets Post 2.2.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-11-15",
+    "userReleaseDate": "15/Nov/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12323498",
+    "id": "12323498",
+    "name": "Servlets Post 2.3.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2013-05-10",
+    "userReleaseDate": "10/May/13",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12324380",
+    "id": "12324380",
+    "name": "Servlets Post 2.3.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2013-07-18",
+    "userReleaseDate": "18/Jul/13",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12324747",
+    "id": "12324747",
+    "name": "Servlets Post 2.3.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-03-07",
+    "userReleaseDate": "07/Mar/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328043",
+    "id": "12328043",
+    "name": "Servlets Post 2.3.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-06-15",
+    "userReleaseDate": "15/Jun/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326357",
+    "id": "12326357",
+    "name": "Servlets Post 2.3.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-08-28",
+    "userReleaseDate": "28/Aug/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12332788",
+    "id": "12332788",
+    "name": "Servlets Post 2.3.10",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-04-10",
+    "userReleaseDate": "10/Apr/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12335544",
+    "id": "12335544",
+    "name": "Servlets Post 2.3.12",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-06-01",
+    "userReleaseDate": "01/Jun/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12335856",
+    "id": "12335856",
+    "name": "Servlets Post 2.3.14",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-09-13",
+    "userReleaseDate": "13/Sep/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338346",
+    "id": "12338346",
+    "name": "Servlets Post 2.3.16",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-11",
+    "userReleaseDate": "11/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340585",
+    "id": "12340585",
+    "name": "Servlets Post 2.3.18",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-19",
+    "userReleaseDate": "19/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340584",
+    "id": "12340584",
+    "name": "Servlets Post 2.3.20",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-29",
+    "userReleaseDate": "29/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12341089",
+    "id": "12341089",
+    "name": "Servlets Post 2.3.22",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-08-14",
+    "userReleaseDate": "14/Aug/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12341348",
+    "id": "12341348",
+    "name": "Servlets Post 2.3.24",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2018-01-23",
+    "userReleaseDate": "23/Jan/18",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313246",
+    "id": "12313246",
+    "description": "First public release",
+    "name": "Servlets Get 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313374",
+    "id": "12313374",
+    "description": "Maintenance Release",
+    "name": "Servlets Get 2.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313969",
+    "id": "12313969",
+    "description": "Maintenance Release",
+    "name": "Servlets Get 2.0.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-10-02",
+    "userReleaseDate": "02/Oct/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314257",
+    "id": "12314257",
+    "description": "Maintenance Release",
+    "name": "Servlets Get 2.0.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-12-21",
+    "userReleaseDate": "21/Dec/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314473",
+    "id": "12314473",
+    "description": "New Feature Release",
+    "name": "Servlets Get 2.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-10-08",
+    "userReleaseDate": "08/Oct/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315299",
+    "id": "12315299",
+    "description": "Maintenance Release",
+    "name": "Servlets Get 2.1.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2011-02-18",
+    "userReleaseDate": "18/Feb/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316181",
+    "id": "12316181",
+    "description": "Maintenance Release",
+    "name": "Servlets Get 2.1.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-11-15",
+    "userReleaseDate": "15/Nov/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12323496",
+    "id": "12323496",
+    "name": "Servlets Get 2.1.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-03-07",
+    "userReleaseDate": "07/Mar/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326356",
+    "id": "12326356",
+    "name": "Servlets Get 2.1.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-03-31",
+    "userReleaseDate": "31/Mar/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326700",
+    "id": "12326700",
+    "name": "Servlets Get 2.1.10",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-09-01",
+    "userReleaseDate": "01/Sep/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328240",
+    "id": "12328240",
+    "name": "Servlets Get 2.1.12",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-07-17",
+    "userReleaseDate": "17/Jul/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333051",
+    "id": "12333051",
+    "name": "Servlets Get 2.1.14",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-12-21",
+    "userReleaseDate": "21/Dec/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334386",
+    "id": "12334386",
+    "name": "Servlets Get 2.1.18",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-08-25",
+    "userReleaseDate": "25/Aug/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338170",
+    "id": "12338170",
+    "name": "Servlets Get 2.1.20",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-02-03",
+    "userReleaseDate": "03/Feb/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12339353",
+    "id": "12339353",
+    "name": "Servlets Get 2.1.22",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-02-09",
+    "userReleaseDate": "09/Feb/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12339558",
+    "id": "12339558",
+    "name": "Servlets Get 2.1.24",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-11",
+    "userReleaseDate": "11/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340578",
+    "id": "12340578",
+    "name": "Servlets Get 2.1.26",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-19",
+    "userReleaseDate": "19/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340579",
+    "id": "12340579",
+    "name": "Servlets Get 2.1.28",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-11-02",
+    "userReleaseDate": "02/Nov/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315438",
+    "id": "12315438",
+    "description": "Initial Version",
+    "name": "Servlet Archetype 1.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-10-19",
+    "userReleaseDate": "19/Oct/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315454",
+    "id": "12315454",
+    "description": "Maintenance Release",
+    "name": "Servlet Archetype 1.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-06-04",
+    "userReleaseDate": "04/Jun/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12317347",
+    "id": "12317347",
+    "description": "First Release",
+    "name": "Bundle Archetype 1.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2011-09-15",
+    "userReleaseDate": "15/Sep/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326805",
+    "id": "12326805",
+    "name": "Bundle Archetype 1.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-06-04",
+    "userReleaseDate": "04/Jun/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12317548",
+    "id": "12317548",
+    "name": "JCRInstall Bundle Archetype 1.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2011-09-15",
+    "userReleaseDate": "15/Sep/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326806",
+    "id": "12326806",
+    "name": "JCRInstall Bundle Archetype 1.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-06-04",
+    "userReleaseDate": "04/Jun/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315320",
+    "id": "12315320",
+    "description": "First public release",
+    "name": "Scripting Velocity 2.0.0",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315321",
+    "id": "12315321",
+    "description": "Maintenance Release",
+    "name": "Scripting Velocity 2.0.2",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315484",
+    "id": "12315484",
+    "description": "First public release",
+    "name": "Scripting Scala 1.0.0",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313252",
+    "id": "12313252",
+    "description": "First public release",
+    "name": "Scripting JSP-Taglib 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313375",
+    "id": "12313375",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP-Taglib 2.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314125",
+    "id": "12314125",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP-Taglib 2.0.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-11-28",
+    "userReleaseDate": "28/Nov/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314408",
+    "id": "12314408",
+    "description": "Feature Release",
+    "name": "Scripting JSP-Taglib 2.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-10-19",
+    "userReleaseDate": "19/Oct/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315450",
+    "id": "12315450",
+    "description": "Maintenance release",
+    "name": "Scripting JSP-Taglib 2.1.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2011-05-03",
+    "userReleaseDate": "03/May/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316405",
+    "id": "12316405",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP-Taglib 2.1.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-01-30",
+    "userReleaseDate": "30/Jan/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12319471",
+    "id": "12319471",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP-Taglib 2.1.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-10-29",
+    "userReleaseDate": "29/Oct/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12323463",
+    "id": "12323463",
+    "description": "Maintenance/Enhancement Release",
+    "name": "Scripting JSP-Taglib 2.2.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2013-08-08",
+    "userReleaseDate": "08/Aug/13",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12324863",
+    "id": "12324863",
+    "description": "Maintenance release",
+    "name": "Scripting JSP-Taglib 2.2.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-09-12",
+    "userReleaseDate": "12/Sep/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328648",
+    "id": "12328648",
+    "name": "Scripting JSP-Taglib 2.2.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-09-25",
+    "userReleaseDate": "25/Sep/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328732",
+    "id": "12328732",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP-Taglib 2.2.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-04-21",
+    "userReleaseDate": "21/Apr/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314339",
+    "id": "12314339",
+    "description": "First public release",
+    "name": "Scripting JSP-Atom-Taglib 1.0.0",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313251",
+    "id": "12313251",
+    "description": "First public release",
+    "name": "Scripting JSP 2.0.2",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313356",
+    "id": "12313356",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.6",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314069",
+    "id": "12314069",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.8",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-11-28",
+    "userReleaseDate": "28/Nov/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314407",
+    "id": "12314407",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.10",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2010-10-25",
+    "userReleaseDate": "25/Oct/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315449",
+    "id": "12315449",
+    "description": "Maintenance release",
+    "name": "Scripting JSP 2.0.12",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2010-12-20",
+    "userReleaseDate": "20/Dec/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316005",
+    "id": "12316005",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.14",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2011-01-24",
+    "userReleaseDate": "24/Jan/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316083",
+    "id": "12316083",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.16",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2011-05-03",
+    "userReleaseDate": "03/May/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316404",
+    "id": "12316404",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.18",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2011-08-15",
+    "userReleaseDate": "15/Aug/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12317602",
+    "id": "12317602",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.20",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2012-01-16",
+    "userReleaseDate": "16/Jan/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12319472",
+    "id": "12319472",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.22",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2012-05-18",
+    "userReleaseDate": "18/May/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12321295",
+    "id": "12321295",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.24",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2012-06-28",
+    "userReleaseDate": "28/Jun/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12321864",
+    "id": "12321864",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.0.26",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2012-11-30",
+    "userReleaseDate": "30/Nov/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12323580",
+    "id": "12323580",
+    "name": "Scripting JSP 2.0.28",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2013-05-16",
+    "userReleaseDate": "16/May/13",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12324445",
+    "id": "12324445",
+    "description": "Java 8 Support",
+    "name": "Scripting JSP 2.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-05-20",
+    "userReleaseDate": "20/May/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326855",
+    "id": "12326855",
+    "name": "Scripting JSP 2.1.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-07-12",
+    "userReleaseDate": "12/Jul/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12327194",
+    "id": "12327194",
+    "name": "Scripting JSP 2.1.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-11-06",
+    "userReleaseDate": "06/Nov/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328955",
+    "id": "12328955",
+    "name": "Scripting JSP 2.1.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-01-03",
+    "userReleaseDate": "03/Jan/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334593",
+    "id": "12334593",
+    "name": "Scripting JSP 2.2.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-10-20",
+    "userReleaseDate": "20/Oct/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338545",
+    "id": "12338545",
+    "name": "Scripting JSP 2.2.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-12-16",
+    "userReleaseDate": "16/Dec/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338860",
+    "id": "12338860",
+    "name": "Scripting JSP 2.2.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-01-10",
+    "userReleaseDate": "10/Jan/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338978",
+    "id": "12338978",
+    "name": "Scripting JSP 2.2.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-01-30",
+    "userReleaseDate": "30/Jan/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12339340",
+    "id": "12339340",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.3.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-03-20",
+    "userReleaseDate": "20/Mar/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340247",
+    "id": "12340247",
+    "description": "Maintenance Release",
+    "name": "Scripting JSP 2.3.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-08-24",
+    "userReleaseDate": "24/Aug/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12341395",
+    "id": "12341395",
+    "name": "Scripting JSP 2.3.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-12-04",
+    "userReleaseDate": "04/Dec/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313250",
+    "id": "12313250",
+    "description": "First public release",
+    "name": "Scripting JavaScript 2.0.2",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313390",
+    "id": "12313390",
+    "description": "Feature Release",
+    "name": " Scripting JavaScript 2.0.4",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314110",
+    "id": "12314110",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.6",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-11-28",
+    "userReleaseDate": "28/Nov/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314406",
+    "id": "12314406",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.8",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2010-10-25",
+    "userReleaseDate": "25/Oct/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315448",
+    "id": "12315448",
+    "description": "Maintenance release",
+    "name": "Scripting JavaScript 2.0.10",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2010-12-20",
+    "userReleaseDate": "20/Dec/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316006",
+    "id": "12316006",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.12",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2011-02-26",
+    "userReleaseDate": "26/Feb/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316243",
+    "id": "12316243",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.14",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2014-06-23",
+    "userReleaseDate": "23/Jun/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12327183",
+    "id": "12327183",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.16",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2015-04-20",
+    "userReleaseDate": "20/Apr/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12332071",
+    "id": "12332071",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.18",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2015-07-02",
+    "userReleaseDate": "02/Jul/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12332966",
+    "id": "12332966",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.20",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2015-07-21",
+    "userReleaseDate": "21/Jul/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333080",
+    "id": "12333080",
+    "name": "Scripting JavaScript 2.0.22",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2015-08-13",
+    "userReleaseDate": "13/Aug/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333276",
+    "id": "12333276",
+    "name": "Scripting JavaScript 2.0.24",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2015-09-07",
+    "userReleaseDate": "07/Sep/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333370",
+    "id": "12333370",
+    "name": "Scripting JavaScript 2.0.26",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2015-10-12",
+    "userReleaseDate": "12/Oct/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333862",
+    "id": "12333862",
+    "name": "Scripting JavaScript 2.0.28",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-01-08",
+    "userReleaseDate": "08/Jan/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334627",
+    "id": "12334627",
+    "description": "Maintenance Release",
+    "name": "Scripting JavaScript 2.0.30",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-06-22",
+    "userReleaseDate": "22/Jun/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12336745",
+    "id": "12336745",
+    "name": "Scripting JavaScript 3.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-11",
+    "userReleaseDate": "11/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340536",
+    "id": "12340536",
+    "name": "Scripting JavaScript 3.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-09-04",
+    "userReleaseDate": "04/Sep/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313605",
+    "id": "12313605",
+    "description": "First public release",
+    "name": "Scripting Java 1.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-12-02",
+    "userReleaseDate": "02/Dec/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314418",
+    "id": "12314418",
+    "description": "New feature release",
+    "name": "Scripting Java 2.0.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-12-20",
+    "userReleaseDate": "20/Dec/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316001",
+    "id": "12316001",
+    "description": "Maintenance Release",
+    "name": "Scripting Java 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-01-16",
+    "userReleaseDate": "16/Jan/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12319473",
+    "id": "12319473",
+    "description": "Maintenance Release",
+    "name": "Scripting Java 2.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-05-18",
+    "userReleaseDate": "18/May/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12321294",
+    "id": "12321294",
+    "description": "Maintenance Release",
+    "name": "Scripting Java 2.0.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-06-28",
+    "userReleaseDate": "28/Jun/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12321863",
+    "id": "12321863",
+    "description": "Maintenance Release",
+    "name": "Scripting Java 2.0.10",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-07-12",
+    "userReleaseDate": "12/Jul/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326856",
+    "id": "12326856",
+    "description": "Maintenance Release",
+    "name": "Scripting Java 2.0.12",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-09-30",
+    "userReleaseDate": "30/Sep/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328674",
+    "id": "12328674",
+    "description": "Maintenance Release",
+    "name": "Scripting Java 2.0.14",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-12-20",
+    "userReleaseDate": "20/Dec/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334385",
+    "id": "12334385",
+    "description": "Maintenance Release",
+    "name": "Scripting Java 2.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-10-20",
+    "userReleaseDate": "20/Oct/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338546",
+    "id": "12338546",
+    "name": "Scripting Java 2.1.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-12-16",
+    "userReleaseDate": "16/Dec/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338861",
+    "id": "12338861",
+    "name": "Scripting Java 2.1.4",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313249",
+    "id": "12313249",
+    "description": "First public release",
+    "name": "Scripting Core 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313366",
+    "id": "12313366",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314072",
+    "id": "12314072",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-10-10",
+    "userReleaseDate": "10/Oct/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314291",
+    "id": "12314291",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2009-11-28",
+    "userReleaseDate": "28/Nov/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314403",
+    "id": "12314403",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.10",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-03-01",
+    "userReleaseDate": "01/Mar/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314797",
+    "id": "12314797",
+    "description": "Maintenance release",
+    "name": "Scripting Core 2.0.14",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-11-02",
+    "userReleaseDate": "02/Nov/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315447",
+    "id": "12315447",
+    "description": "Maintenance release",
+    "name": "Scripting Core 2.0.16",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2011-01-29",
+    "userReleaseDate": "29/Jan/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316110",
+    "id": "12316110",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.18",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2011-08-15",
+    "userReleaseDate": "15/Aug/11",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12317582",
+    "id": "12317582",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.20",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-01-16",
+    "userReleaseDate": "16/Jan/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12319469",
+    "id": "12319469",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.22",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-05-26",
+    "userReleaseDate": "26/May/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12321447",
+    "id": "12321447",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.24",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-06-28",
+    "userReleaseDate": "28/Jun/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12321862",
+    "id": "12321862",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.26",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-03-07",
+    "userReleaseDate": "07/Mar/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326347",
+    "id": "12326347",
+    "name": "Scripting Core 2.0.28",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-09-22",
+    "userReleaseDate": "22/Sep/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12328697",
+    "id": "12328697",
+    "name": "Scripting Core 2.0.30",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-07-21",
+    "userReleaseDate": "21/Jul/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333079",
+    "id": "12333079",
+    "name": "Scripting Core 2.0.32",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-07-26",
+    "userReleaseDate": "26/Jul/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333099",
+    "id": "12333099",
+    "name": "Scripting Core 2.0.34",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-08-13",
+    "userReleaseDate": "13/Aug/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12333275",
+    "id": "12333275",
+    "name": "Scripting Core 2.0.36",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-02-12",
+    "userReleaseDate": "12/Feb/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12334860",
+    "id": "12334860",
+    "name": "Scripting Core 2.0.38",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-07-13",
+    "userReleaseDate": "13/Jul/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12337945",
+    "id": "12337945",
+    "name": "Scripting Core 2.0.40",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-10-17",
+    "userReleaseDate": "17/Oct/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338776",
+    "id": "12338776",
+    "name": "Scripting Core 2.0.44",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-12-15",
+    "userReleaseDate": "15/Dec/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12339253",
+    "id": "12339253",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.46",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-03-08",
+    "userReleaseDate": "08/Mar/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12339953",
+    "id": "12339953",
+    "description": "Maintenance Release",
+    "name": "Scripting Core 2.0.48",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-09-29",
+    "userReleaseDate": "29/Sep/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12341679",
+    "id": "12341679",
+    "name": "Scripting Core 2.0.50",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-10-23",
+    "userReleaseDate": "23/Oct/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313248",
+    "id": "12313248",
+    "description": "First public release",
+    "name": "Scripting API 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314561",
+    "id": "12314561",
+    "description": "Feature Release",
+    "name": "Scripting API 2.1.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-02-19",
+    "userReleaseDate": "19/Feb/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314784",
+    "id": "12314784",
+    "description": "Maintenance release",
+    "name": "Scripting API 2.1.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2010-12-20",
+    "userReleaseDate": "20/Dec/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12316004",
+    "id": "12316004",
+    "description": "Maintenance Release",
+    "name": "Scripting API 2.1.4",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2012-01-16",
+    "userReleaseDate": "16/Jan/12",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12319470",
+    "id": "12319470",
+    "description": "Maintenance Release",
+    "name": "Scripting API 2.1.6",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2014-03-07",
+    "userReleaseDate": "07/Mar/14",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12326346",
+    "id": "12326346",
+    "name": "Scripting API 2.1.8",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2015-07-21",
+    "userReleaseDate": "21/Jul/15",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12338775",
+    "id": "12338775",
+    "name": "Scripting API 2.1.12",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2016-12-15",
+    "userReleaseDate": "15/Dec/16",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340474",
+    "id": "12340474",
+    "name": "Scripting API 2.2.0",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2017-05-01",
+    "userReleaseDate": "01/May/17",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12340480",
+    "id": "12340480",
+    "name": "Scripting API 2.2.2",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313260",
+    "id": "12313260",
+    "description": "First public release",
+    "name": "Samples Simple Demo 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313262",
+    "id": "12313262",
+    "description": "First public release",
+    "name": "Samples Webloader Service 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313263",
+    "id": "12313263",
+    "description": "First public release",
+    "name": "Samples Webloader UI 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12325043",
+    "id": "12325043",
+    "description": "First public release",
+    "name": "Request Analyzer 1.0.0",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313261",
+    "id": "12313261",
+    "description": "First public release",
+    "name": "Path Based RTP 2.0.2",
+    "archived": false,
+    "released": true,
+    "releaseDate": "2008-06-23",
+    "userReleaseDate": "23/Jun/08",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314787",
+    "id": "12314787",
+    "description": "Maintenance release",
+    "name": "Path based RTP 2.0.4",
+    "archived": false,
+    "released": false,
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313662",
+    "id": "12313662",
+    "description": "Maintenance Release",
+    "name": "Parent 5",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-05-14",
+    "userReleaseDate": "14/May/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12313945",
+    "id": "12313945",
+    "description": "Maintenance Release",
+    "name": "Parent 6",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-07-03",
+    "userReleaseDate": "03/Jul/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314079",
+    "id": "12314079",
+    "description": "Maintenance Release",
+    "name": "Parent 7",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-09-28",
+    "userReleaseDate": "28/Sep/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314251",
+    "id": "12314251",
+    "description": "Maintenance Release",
+    "name": "Parent 8",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2009-11-28",
+    "userReleaseDate": "28/Nov/09",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12314432",
+    "id": "12314432",
+    "description": "Maintenance Release",
+    "name": "Parent 9",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2010-05-11",
+    "userReleaseDate": "11/May/10",
+    "projectId": 12310710
+  },
+  {
+    "self": "https://issues.apache.org/jira/rest/api/2/version/12315011",
+    "id": "12315011",
+    "description": "Maintenance release",
+    "name": "Parent 10",
+    "archived": true,
+    "released": true,
+    "releaseDate": "2010-12-13",
+    "userReleaseDate": "13/Dec/10",
+    "projectId": 12310710
+  }
+]


[sling-org-apache-sling-committer-cli] 31/44: SLING-8368 - Name comparisons fail when accents are missing

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit f884748ff3aafb3868cc63bf01c53984f0723b5d
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Apr 19 17:46:25 2019 +0200

    SLING-8368 - Name comparisons fail when accents are missing
    
    * made name comparisons ignore accents when looking for a member by name
    * if members use their apache email address for emails, then names will
    automatically be extracted from Whimsy instead of using the name from
    the From/Sender email headers
---
 pom.xml                                            | 10 +++
 .../java/org/apache/sling/cli/impl/mail/Email.java | 88 +++++++++++++++++++---
 .../apache/sling/cli/impl/mail/EmailThread.java    | 31 --------
 .../sling/cli/impl/mail/VoteThreadFinder.java      | 17 ++++-
 .../sling/cli/impl/people/MembersFinder.java       | 17 ++++-
 .../sling/cli/impl/release/TallyVotesCommand.java  | 33 ++++----
 6 files changed, 135 insertions(+), 61 deletions(-)

diff --git a/pom.xml b/pom.xml
index 0fc0dbd..6cdcf27 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,6 +34,16 @@
     <build>
         <plugins>
             <plugin>
+                <artifactId>maven-clean-plugin</artifactId>
+                <configuration>
+                    <filesets>
+                        <fileset>
+                            <directory>launcher</directory>
+                        </fileset>
+                    </filesets>
+                </configuration>
+            </plugin>
+            <plugin>
                 <groupId>biz.aQute.bnd</groupId>
                 <artifactId>bnd-maven-plugin</artifactId>
             </plugin>
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/Email.java b/src/main/java/org/apache/sling/cli/impl/mail/Email.java
index 54ec66e..e11bfaa 100644
--- a/src/main/java/org/apache/sling/cli/impl/mail/Email.java
+++ b/src/main/java/org/apache/sling/cli/impl/mail/Email.java
@@ -16,33 +16,101 @@
  */
 package org.apache.sling.cli.impl.mail;
 
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.BodyPart;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.utils.URIBuilder;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
 public class Email {
-    private String from;
+
+    private String id;
+    private InternetAddress from;
     private String subject;
     private String body;
 
-    public String getFrom() {
-        return from;
+    public Email(String id) {
+        this.id = id;
+        try (CloseableHttpClient client = HttpClients.createDefault()) {
+            URI uri = new URIBuilder("https://lists.apache.org/api/source.lua/" + URLEncoder.encode(id, StandardCharsets.UTF_8)).build();
+            HttpGet get = new HttpGet(uri);
+            try (CloseableHttpResponse response = client.execute(get)) {
+                try (InputStream content = response.getEntity().getContent()) {
+                    if (response.getStatusLine().getStatusCode() != 200) {
+                        throw new IOException("Status line : " + response.getStatusLine());
+                    }
+                    MimeMessage message = new MimeMessage(Session.getDefaultInstance(new Properties()), content);
+                    subject = message.getSubject();
+                    Address[] who = message.getFrom();
+                    if (who.length > 0) {
+                        from = (InternetAddress) who[0];
+                    }
+                    body = getContent(message);
+                }
+            }
+        } catch (URISyntaxException | IOException | MessagingException e) {
+            throw new IllegalArgumentException(e);
+        }
     }
 
-    public void setFrom(String from) {
-        this.from = from;
+    public String getId() {
+        return id;
+    }
+
+    public InternetAddress getFrom() {
+        return from;
     }
 
     public String getSubject() {
         return subject;
     }
 
-    public void setSubject(String subject) {
-        this.subject = subject;
-    }
 
     public String getBody() {
         return body;
     }
 
-    public void setBody(String body) {
-        this.body = body;
+    private String getContent(Message message) throws MessagingException, IOException {
+        String result = "";
+        if (message.isMimeType("text/plain")) {
+            result = message.getContent().toString();
+        } else if (message.isMimeType("multipart/*")) {
+            MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
+            result = getTextFromMimeMultipart(mimeMultipart);
+        }
+        return result;
+    }
+
+    private String getTextFromMimeMultipart(
+            MimeMultipart mimeMultipart) throws MessagingException, IOException {
+        StringBuilder result = new StringBuilder();
+        int count = mimeMultipart.getCount();
+        for (int i = 0; i < count; i++) {
+            BodyPart bodyPart = mimeMultipart.getBodyPart(i);
+            if (bodyPart.isMimeType("text/plain") || bodyPart.isMimeType("text/html")) {
+                result.append("\n").append(bodyPart.getContent());
+            } else if (bodyPart.getContent() instanceof MimeMultipart) {
+                result.append(getTextFromMimeMultipart((MimeMultipart) bodyPart.getContent()));
+            }
+        }
+        return result.toString();
     }
 
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/EmailThread.java b/src/main/java/org/apache/sling/cli/impl/mail/EmailThread.java
deleted file mode 100644
index 03ac673..0000000
--- a/src/main/java/org/apache/sling/cli/impl/mail/EmailThread.java
+++ /dev/null
@@ -1,31 +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.cli.impl.mail;
-
-import java.util.List;
-
-public class EmailThread {
-    private List<Email> emails;
-
-    public List<Email> getEmails() {
-        return emails;
-    }
-
-    public void setEmails(List<Email> emails) {
-        this.emails = emails;
-    }
-}
diff --git a/src/main/java/org/apache/sling/cli/impl/mail/VoteThreadFinder.java b/src/main/java/org/apache/sling/cli/impl/mail/VoteThreadFinder.java
index 0d39968..f48baa1 100644
--- a/src/main/java/org/apache/sling/cli/impl/mail/VoteThreadFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/mail/VoteThreadFinder.java
@@ -21,6 +21,8 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -29,12 +31,14 @@ import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.osgi.service.component.annotations.Component;
 
-import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
 
 @Component(service = VoteThreadFinder.class)
 public class VoteThreadFinder {
     
-    public EmailThread findVoteThread(String releaseName) throws IOException {
+    public List<Email> findVoteThread(String releaseName) throws IOException {
         try ( CloseableHttpClient client = HttpClients.createDefault() ) {
             
             URI uri = new URIBuilder("https://lists.apache.org/api/stats.lua")
@@ -50,8 +54,13 @@ public class VoteThreadFinder {
                         InputStreamReader reader = new InputStreamReader(content)) {
                     if ( response.getStatusLine().getStatusCode() != 200 )
                         throw new IOException("Status line : " + response.getStatusLine());
-                    Gson gson = new Gson();
-                    return gson.fromJson(reader, EmailThread.class);
+                    JsonParser parser = new JsonParser();
+                    JsonArray emailsArray = parser.parse(reader).getAsJsonObject().get("emails").getAsJsonArray();
+                    List<Email> emails = new ArrayList<>();
+                    for (JsonElement email : emailsArray) {
+                        emails.add(new Email(email.getAsJsonObject().get("id").getAsString()));
+                    }
+                    return emails;
                 }
             }
         } catch (URISyntaxException e) {
diff --git a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
index 70b5469..c26ab9a 100644
--- a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
@@ -21,8 +21,10 @@ package org.apache.sling.cli.impl.people;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.text.Collator;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Locale;
 import java.util.Set;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
@@ -103,7 +105,7 @@ public class MembersFinder {
         return members;
     }
 
-    public Member getMemberById(String id) {
+    public Member findById(String id) {
         for (Member member : findMembers()) {
             if (id.equals(member.getId())) {
                 return member;
@@ -112,8 +114,19 @@ public class MembersFinder {
         return null;
     }
 
+    public Member findByNameOrEmail(String name, String email) {
+        Collator collator = Collator.getInstance(Locale.US);
+        collator.setDecomposition(Collator.NO_DECOMPOSITION);
+        for (Member member : findMembers()) {
+            if (email.equals(member.getEmail()) || collator.compare(name, member.getName()) == 0) {
+                return member;
+            }
+        }
+        return null;
+    }
+
     public Member getCurrentMember() {
-         return getMemberById(credentialsService.getCredentials().getUsername());
+        return findById(credentialsService.getCredentials().getUsername());
     }
 
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index fb3ca95..93dbf0a 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -17,14 +17,15 @@
 package org.apache.sling.cli.impl.release;
 
 import java.io.IOException;
+import java.text.Collator;
 import java.util.Arrays;
-import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Locale;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.mail.Email;
-import org.apache.sling.cli.impl.mail.EmailThread;
 import org.apache.sling.cli.impl.mail.VoteThreadFinder;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
 import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
@@ -84,24 +85,28 @@ public class TallyVotesCommand implements Command {
                     .stream()
                     .map(Release::getFullName)
                     .collect(Collectors.joining(", "));
-            EmailThread voteThread = voteThreadFinder.findVoteThread(releaseName);
 
-            Set<String> bindingVoters = new HashSet<>();
-            Set<String> nonBindingVoters = new HashSet<>();
-            for (Email e : voteThread.getEmails()) {
-                if (isPositiveVote(e)) {
-                    String sender = e.getFrom().replaceAll("<.*>", "").trim();
-                    for (Member m : membersFinder.findMembers()) {
-                        if (sender.equals(m.getName())) {
+            Set<String> bindingVoters = new LinkedHashSet<>();
+            Set<String> nonBindingVoters = new LinkedHashSet<>();
+            Collator collator = Collator.getInstance(Locale.US);
+            collator.setDecomposition(Collator.NO_DECOMPOSITION);
+            voteThreadFinder.findVoteThread(releaseName).stream().skip(1).filter(this::isPositiveVote).forEachOrdered(
+                    email -> {
+                        String from = email.getFrom().getAddress();
+                        String name = email.getFrom().getPersonal();
+                        Member m = membersFinder.findByNameOrEmail(name, from);
+                        if (m != null) {
                             if (m.isPMCMember()) {
-                                bindingVoters.add(sender);
+                                bindingVoters.add(m.getName());
                             } else {
-                                nonBindingVoters.add(sender);
+                                nonBindingVoters.add(m.getName());
                             }
+                        } else {
+                            nonBindingVoters.add(name);
                         }
                     }
-                }
-            }
+            );
+
             String email = EMAIL_TEMPLATE
                 .replace("##RELEASE_NAME##", releaseFullName)
                 .replace("##BINDING_VOTERS##", String.join(", ", bindingVoters))


[sling-org-apache-sling-committer-cli] 13/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 45adcd8076d9cab47101542a3fa750a30eb2e18b
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Mon Mar 25 18:33:29 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * added support for querying Sling's project members
    * improved TallyVotesCommand to list binding and non-binding votes
---
 .../org/apache/sling/cli/impl/people/Member.java   |  58 +++++++++++
 .../sling/cli/impl/people/MembersFinder.java       | 110 +++++++++++++++++++++
 .../sling/cli/impl/release/TallyVotesCommand.java  |  57 ++++++++---
 3 files changed, 213 insertions(+), 12 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/people/Member.java b/src/main/java/org/apache/sling/cli/impl/people/Member.java
new file mode 100644
index 0000000..7b3cb16
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/people/Member.java
@@ -0,0 +1,58 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.cli.impl.people;
+
+public class Member {
+
+    private String id;
+    private String name;
+    private boolean isPMCMember;
+
+    Member(String id, String name, boolean isPMCMember) {
+        this.id = id;
+        this.name = name;
+        this.isPMCMember = isPMCMember;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public boolean isPMCMember() {
+        return isPMCMember;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof Member)) {
+            return false;
+        }
+        Member other = (Member) obj;
+        return id.equals(other.id);
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
new file mode 100644
index 0000000..10cba19
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
@@ -0,0 +1,110 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.cli.impl.people;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+
+@Component(service = MembersFinder.class)
+public class MembersFinder {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(MembersFinder.class);
+    private static final String PEOPLE_ENDPOINT = "https://whimsy.apache.org/public/public_ldap_people.json";
+    private static final String PROJECTS_ENDPOINT = "https://whimsy.apache.org/public/public_ldap_projects.json";
+    private static final int STALENESS_IN_HOURS = 3;
+    private Set<Member> members = Collections.emptySet();
+    private long lastCheck = 0;
+
+    public synchronized Set<Member> findMembers() {
+        final Set<Member> _members = new HashSet<>();
+        if (lastCheck == 0 || System.currentTimeMillis() > lastCheck + STALENESS_IN_HOURS * 3600 * 1000) {
+            lastCheck = System.currentTimeMillis();
+            try (CloseableHttpClient client = HttpClients.createDefault()) {
+                JsonParser parser = new JsonParser();
+                Set<String> memberIds;
+                Set<String> pmcMemberIds;
+                try (CloseableHttpResponse response = client.execute(new HttpGet(PROJECTS_ENDPOINT))) {
+                    try (InputStream content = response.getEntity().getContent();
+                         InputStreamReader reader = new InputStreamReader(content)) {
+                        if (response.getStatusLine().getStatusCode() != 200) {
+                            throw new IOException("Status line : " + response.getStatusLine());
+                        }
+                        JsonElement jsonElement = parser.parse(reader);
+                        JsonObject json = jsonElement.getAsJsonObject();
+                        JsonObject sling = json.get("projects").getAsJsonObject().get("sling").getAsJsonObject();
+                        memberIds = new HashSet<>();
+                        pmcMemberIds = new HashSet<>();
+                        for (JsonElement member : sling.getAsJsonArray("members")) {
+                            memberIds.add(member.getAsString());
+                        }
+                        for (JsonElement pmcMember : sling.getAsJsonArray("owners")) {
+                            pmcMemberIds.add(pmcMember.getAsString());
+                        }
+                    }
+                }
+                try (CloseableHttpResponse response = client.execute(new HttpGet(PEOPLE_ENDPOINT))) {
+                    try (InputStream content = response.getEntity().getContent();
+                         InputStreamReader reader = new InputStreamReader(content)) {
+                        if (response.getStatusLine().getStatusCode() != 200) {
+                            throw new IOException("Status line : " + response.getStatusLine());
+                        }
+                        JsonElement jsonElement = parser.parse(reader);
+                        JsonObject json = jsonElement.getAsJsonObject();
+                        JsonObject people = json.get("people").getAsJsonObject();
+                        for (String id : memberIds) {
+                            String name = people.get(id).getAsJsonObject().get("name").getAsString();
+                            _members.add(new Member(id, name, pmcMemberIds.contains(id)));
+                        }
+
+                    }
+                }
+                members = Collections.unmodifiableSet(_members);
+            } catch (IOException e) {
+                LOGGER.error("Unable to retrieve Apache Sling project members.", e);
+            }
+        }
+        return members;
+    }
+
+    public Member getMemberById(String id) {
+        for (Member member : findMembers()) {
+            if (id.equals(member.getId())) {
+                return member;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 6046405..74cdd23 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -18,6 +18,8 @@ package org.apache.sling.cli.impl.release;
 
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.sling.cli.impl.Command;
@@ -26,6 +28,8 @@ import org.apache.sling.cli.impl.mail.EmailThread;
 import org.apache.sling.cli.impl.mail.VoteThreadFinder;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
 import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
+import org.apache.sling.cli.impl.people.Member;
+import org.apache.sling.cli.impl.people.MembersFinder;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -37,7 +41,10 @@ import org.slf4j.LoggerFactory;
     Command.PROPERTY_NAME_SUMMARY+"=Counts votes cast for a release and generates the result email"
 })
 public class TallyVotesCommand implements Command {
-    
+
+    @Reference
+    private MembersFinder membersFinder;
+
     // TODO - move to file
     private static final String EMAIL_TEMPLATE =
             "To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
@@ -45,12 +52,17 @@ public class TallyVotesCommand implements Command {
             "\n" + 
             "Hi,\n" + 
             "\n" + 
-            "The vote has passed with the following result :\n" + 
+            "The vote has passed with the following result:\n" +
             "\n" + 
             "+1 (binding): ##BINDING_VOTERS##\n" + 
-            "\n" + 
+            "+1 (non-binding): ##NON_BINDING_VOTERS##\n" +
+            "\n" +
             "I will copy this release to the Sling dist directory and\n" + 
-            "promote the artifacts to the central Maven repository.\n";
+            "promote the artifacts to the central Maven repository.\n" +
+            "\n" +
+            "Regards,\n" +
+            "##USER_NAME##\n" +
+            "\n";
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
     @Reference
@@ -67,16 +79,37 @@ public class TallyVotesCommand implements Command {
             Release release = Release.fromString(repository.getDescription());
             EmailThread voteThread = voteThreadFinder.findVoteThread(release.getFullName());
 
-            // TODO - validate which voters are binding and list them separately in the email
-            String bindingVoters = voteThread.getEmails().stream()
-                .filter( e -> isPositiveVote(e) )
-                .map ( e -> e.getFrom().replaceAll("<.*>", "").trim() )
-                .collect(Collectors.joining(", "));
-            
+            Set<String> bindingVoters = new HashSet<>();
+            Set<String> nonBindingVoters = new HashSet<>();
+            for (Email e : voteThread.getEmails()) {
+                if (isPositiveVote(e)) {
+                    String sender = e.getFrom().replaceAll("<.*>", "").trim();
+                    for (Member m : membersFinder.findMembers()) {
+                        if (sender.equals(m.getName())) {
+                            if (m.isPMCMember()) {
+                                bindingVoters.add(sender);
+                            } else {
+                                nonBindingVoters.add(sender);
+                            }
+                        }
+                    }
+                }
+            }
+            String currentUserId = System.getProperty("asf.username");
+            if (currentUserId == null) {
+                currentUserId = System.getenv("ASF_USERNAME");
+            }
+            Member currentUser = membersFinder.getMemberById(currentUserId);
             String email = EMAIL_TEMPLATE
                 .replace("##RELEASE_NAME##", release.getFullName())
-                .replace("##BINDING_VOTERS##", bindingVoters);
-            
+                .replace("##BINDING_VOTERS##", String.join(", ", bindingVoters))
+                .replace("##USER_NAME##", currentUser == null ? "" : currentUser.getName());
+            if (nonBindingVoters.isEmpty()) {
+                email = email.replace("##NON_BINDING_VOTERS##", "none");
+            } else {
+                email = email.replace("##NON_BINDING_VOTERS##", String.join(", ", nonBindingVoters));
+            }
+
             logger.info(email);
             
         } catch (IOException e) {


[sling-org-apache-sling-committer-cli] 22/44: Updated name

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 74669b3bff12e3824f84e91951d03a534b356cff
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Apr 16 18:45:44 2019 +0300

    Updated name
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 735bc46..a0458e6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
     <artifactId>sling-cli</artifactId>
     <version>1.0-SNAPSHOT</version>
 
-    <description>Sling CLI tool for development usage</description>
+    <description>Apache Sling Committer CLI</description>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>


[sling-org-apache-sling-committer-cli] 11/44: re-add the space in between the subject's tags

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 6b9e300a84a0279814dbe665d09dff993992576a
Author: Radu Cotescu <17...@users.noreply.github.com>
AuthorDate: Wed Mar 20 15:06:15 2019 +0100

    re-add the space in between the subject's tags
---
 src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index cbda427..86742bb 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -41,7 +41,7 @@ public class TallyVotesCommand implements Command {
     // TODO - move to file
     private static final String EMAIL_TEMPLATE =
             "To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
-            "Subject: [RESULT][VOTE] Release ##RELEASE_NAME##\n" + 
+            "Subject: [RESULT] [VOTE] Release ##RELEASE_NAME##\n" + 
             "\n" + 
             "Hi,\n" + 
             "\n" + 


[sling-org-apache-sling-committer-cli] 06/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 7675184c579148b7d12a11b9f256c53f558f4204
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Mar 19 16:21:26 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Fix local site update diff generation.
---
 .../java/org/apache/sling/cli/impl/release/ReleaseVersion.java     | 7 ++++++-
 .../org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java  | 6 ++----
 .../java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java | 1 +
 3 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java b/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
index b629e19..0f0ef96 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/ReleaseVersion.java
@@ -27,12 +27,14 @@ public final class ReleaseVersion {
         rel.name = rel.fullName
             .replace("Apache Sling ", ""); // Apache Sling prefix
         rel.version = rel.fullName.substring(rel.fullName.lastIndexOf(' ') + 1);
+        rel.component = rel.name.substring(0, rel.name.lastIndexOf(' '));
         
         return rel;
     }
     
     private String fullName;
     private String name;
+    private String component;
     private String version;
 
     private ReleaseVersion() {
@@ -50,5 +52,8 @@ public final class ReleaseVersion {
     public String getVersion() {
         return version;
     }
-    
+
+    public String getComponent() {
+        return component;
+    }
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
index 10e836a..613afe0 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
@@ -21,8 +21,6 @@ import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.Locale;
 
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.jbake.JBakeContentUpdater;
@@ -66,8 +64,8 @@ public class UpdateLocalSiteCommand implements Command {
         
                 Path templatePath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "templates", "downloads.tpl");
                 Path releasesPath = Paths.get(GIT_CHECKOUT, "src", "main", "jbake", "content", "releases.md");
-                updater.updateDownloads(templatePath, releaseVersion.getName(), releaseVersion.getVersion());
-                updater.updateReleases(releasesPath, releaseVersion.getName(), releaseVersion.getVersion(), LocalDateTime.now());
+                updater.updateDownloads(templatePath, releaseVersion.getComponent(), releaseVersion.getVersion());
+                updater.updateReleases(releasesPath, releaseVersion.getComponent(), releaseVersion.getVersion(), LocalDateTime.now());
         
                 git.diff()
                     .setOutputStream(System.out)
diff --git a/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java b/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
index 90ed3e5..fc63a5f 100644
--- a/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/release/ReleaseVersionTest.java
@@ -30,5 +30,6 @@ public class ReleaseVersionTest {
         assertEquals("Resource Merger 1.3.10", rel.getName());
         assertEquals("Apache Sling Resource Merger 1.3.10", rel.getFullName());
         assertEquals("1.3.10", rel.getVersion());
+        assertEquals("Resource Merger", rel.getComponent());
     }
 }


[sling-org-apache-sling-committer-cli] 32/44: SLING-8337 - Create sub-command to manage the Jira update when promoting a release

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 9ea5eddcaf4bd9f1d0806a00470af11fe4757553
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Apr 22 15:12:20 2019 +0300

    SLING-8337 - Create sub-command to manage the Jira update when promoting a release
    
    WIP on creating Jira issues
---
 docker-env.sample                                  |  3 ++
 .../{VersionFinder.java => VersionClient.java}     | 52 +++++++++++++++++++---
 .../cli/impl/release/PrepareVoteEmailCommand.java  |  4 +-
 .../sling/cli/impl/release/UpdateJiraCommand.java  |  8 ++--
 .../sling/cli/impl/jira/VersionFinderTest.java     |  4 +-
 5 files changed, 59 insertions(+), 12 deletions(-)

diff --git a/docker-env.sample b/docker-env.sample
index 7a2a892..12e39be 100644
--- a/docker-env.sample
+++ b/docker-env.sample
@@ -12,3 +12,6 @@
 # ----------------------------------------------------------------------------------------
 ASF_USERNAME=changeme
 ASF_PASSWORD=changeme
+
+JIRA_USERNAME=changeme
+JIRA_PASSWORD=changeme
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
similarity index 77%
rename from src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
rename to src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
index 2c2be18..6a4a770 100644
--- a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionClient.java
@@ -16,9 +16,11 @@
  */
 package org.apache.sling.cli.impl.jira;
 
+import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.StringWriter;
 import java.lang.reflect.Type;
 import java.util.List;
 import java.util.Optional;
@@ -27,6 +29,8 @@ import java.util.function.Predicate;
 
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 import org.apache.sling.cli.impl.release.Release;
@@ -34,12 +38,16 @@ import org.osgi.service.component.annotations.Component;
 
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonWriter;
 
 /**
  * Access the ASF <em>Jira</em> instance and looks up project version data.
  */
-@Component(service = VersionFinder.class)
-public class VersionFinder {
+@Component(service = VersionClient.class)
+public class VersionClient {
+    
+    private static final String JIRA_URL_PREFIX = "https://issues.apache.org/jira/rest/api/2/";
+    private static final String CONTENT_TYPE_JSON = "application/json";
 
     /**
      * Finds a Jira version which matches the specified release
@@ -102,6 +110,40 @@ public class VersionFinder {
         
         return version;
     }
+    
+    public void create(String versionName) throws IOException {
+        StringWriter w = new StringWriter();
+        try ( JsonWriter jw = new Gson().newJsonWriter(w) ) {
+            jw.beginObject();
+            jw.name("name").value(versionName);
+            jw.name("project").value("SLING");
+            jw.endObject();
+        }
+        
+        HttpPost post = new HttpPost(JIRA_URL_PREFIX + "version");
+        post.addHeader("Content-Type", CONTENT_TYPE_JSON);
+        post.addHeader("Accept", CONTENT_TYPE_JSON);
+        post.setEntity(new StringEntity(w.toString()));
+
+        try (CloseableHttpClient client = HttpClients.createDefault() ) {
+            try (CloseableHttpResponse response = client.execute(post)) {
+                try (InputStream content = response.getEntity().getContent();
+                        InputStreamReader reader = new InputStreamReader(content)) {
+                    
+                    if (response.getStatusLine().getStatusCode() != 201) {
+                        // TODO - try and parse JSON error message, fall back to status code
+                        try ( BufferedReader bufferedReader = new BufferedReader(reader)) {
+                            String line;
+                            while  ( (line = bufferedReader.readLine()) != null )
+                                System.out.println(line);
+                        }
+                        
+                        throw new IOException("Status line : " + response.getStatusLine());
+                    }
+                }
+            }
+        }
+    }
 
     private Optional<Version> findVersion(Predicate<Version> matcher, CloseableHttpClient client) throws IOException {
         
@@ -112,7 +154,7 @@ public class VersionFinder {
             return versions.stream()
                     .filter( v -> v.getName().length() > 1) // avoid old '3' release
                     .filter(matcher)
-                    .sorted(VersionFinder::compare)
+                    .sorted(VersionClient::compare)
                     .findFirst();
         });
     }
@@ -144,8 +186,8 @@ public class VersionFinder {
     }
     
     private HttpGet newGet(String suffix) {
-        HttpGet get = new HttpGet("https://issues.apache.org/jira/rest/api/2/" + suffix);
-        get.addHeader("Accept", "application/json");
+        HttpGet get = new HttpGet(JIRA_URL_PREFIX + suffix);
+        get.addHeader("Accept", CONTENT_TYPE_JSON);
         return get;
     }
     
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
index 8656362..dc20db0 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -22,7 +22,7 @@ import java.util.stream.Collectors;
 
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.jira.Version;
-import org.apache.sling.cli.impl.jira.VersionFinder;
+import org.apache.sling.cli.impl.jira.VersionClient;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
 import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
 import org.apache.sling.cli.impl.people.MembersFinder;
@@ -80,7 +80,7 @@ public class PrepareVoteEmailCommand implements Command {
     private StagingRepositoryFinder repoFinder;
     
     @Reference
-    private VersionFinder versionFinder;
+    private VersionClient versionFinder;
 
     @Override
     public void execute(String target) {
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java
index 20d2acc..a636ee8 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateJiraCommand.java
@@ -20,7 +20,7 @@ import java.io.IOException;
 
 import org.apache.sling.cli.impl.Command;
 import org.apache.sling.cli.impl.jira.Version;
-import org.apache.sling.cli.impl.jira.VersionFinder;
+import org.apache.sling.cli.impl.jira.VersionClient;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
 import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
 import org.osgi.service.component.annotations.Component;
@@ -39,7 +39,7 @@ public class UpdateJiraCommand implements Command {
     private StagingRepositoryFinder repoFinder;
     
     @Reference
-    private VersionFinder versionFinder;
+    private VersionClient versionFinder;
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -54,7 +54,9 @@ public class UpdateJiraCommand implements Command {
                 logger.info("Found successor version {}", successorVersion);
                 if ( successorVersion == null ) {
                     Release next = release.next();
-                    logger.info("Would create version {}", next);
+                    logger.info("Would create version {}", next.getName());
+                    versionFinder.create(next.getName());
+                    logger.info("Created version {}", next.getName());
                 }
                     
             }
diff --git a/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java b/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
index 04a97bd..6f11f6c 100644
--- a/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
+++ b/src/test/java/org/apache/sling/cli/impl/jira/VersionFinderTest.java
@@ -32,7 +32,7 @@ import org.junit.Test;
 
 public class VersionFinderTest {
 
-    private VersionFinder finder = new StubVersionFinder();
+    private VersionClient finder = new StubVersionFinder();
     
     @Test
     public void findMatchingVersion() {
@@ -67,7 +67,7 @@ public class VersionFinderTest {
         assertThat("successor", successor, nullValue());
     }
     
-    private static final class StubVersionFinder extends VersionFinder {
+    private static final class StubVersionFinder extends VersionClient {
         @Override
         protected <T> T doWithJiraVersions(CloseableHttpClient client, Function<InputStreamReader, T> parserCallback)
                 throws IOException {


[sling-org-apache-sling-committer-cli] 14/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit 7a4c927f8b9396ece2ca91b09b5454f0c3af430d
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Wed Mar 27 15:42:54 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    * improved email templates to contain the full name of the current user
---
 .../org/apache/sling/cli/impl/people/MembersFinder.java    | 14 ++++++++++++++
 .../sling/cli/impl/release/PrepareVoteEmailCommand.java    | 13 +++++++++++--
 .../apache/sling/cli/impl/release/TallyVotesCommand.java   |  7 +------
 3 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
index 10cba19..a27bf6c 100644
--- a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
+++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java
@@ -107,4 +107,18 @@ public class MembersFinder {
         return null;
     }
 
+    public Member getCurrentMember() {
+        final String currentUserId;
+        if (System.getProperty("asf.username") != null) {
+            currentUserId = System.getProperty("asf.username");
+        } else {
+            currentUserId = System.getenv("ASF_USERNAME");
+        }
+        if (currentUserId == null) {
+            throw new IllegalStateException(String.format("Expected to find the current user defined either through the %s system " +
+                    "property or through the %s environment variable.", "asf.username", "ASF_USERNAME"));
+        }
+         return getMemberById(currentUserId);
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
index 9819107..21b72f8 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -23,6 +23,7 @@ import org.apache.sling.cli.impl.jira.Version;
 import org.apache.sling.cli.impl.jira.VersionFinder;
 import org.apache.sling.cli.impl.nexus.StagingRepository;
 import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
+import org.apache.sling.cli.impl.people.MembersFinder;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
 import org.slf4j.Logger;
@@ -34,6 +35,9 @@ import org.slf4j.LoggerFactory;
     Command.PROPERTY_NAME_SUMMARY + "=Prepares an email vote for the specified release." })
 public class PrepareVoteEmailCommand implements Command {
 
+    @Reference
+    private MembersFinder membersFinder;
+
     // TODO - replace with file template
     private static final String EMAIL_TEMPLATE ="To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
             "Subject: [VOTE] Release ##RELEASE_NAME##\n" + 
@@ -58,7 +62,11 @@ public class PrepareVoteEmailCommand implements Command {
             "  [ ]  0 Don't care\n" + 
             "  [ ] -1 Don't release, because ...\n" + 
             "\n" + 
-            "This majority vote is open for at least 72 hours.\n";
+            "This majority vote is open for at least 72 hours.\n" +
+            "\n" +
+            "Regards,\n" +
+            "##USER_NAME##\n" +
+            "\n";
 
     private final Logger logger = LoggerFactory.getLogger(getClass());
     
@@ -80,7 +88,8 @@ public class PrepareVoteEmailCommand implements Command {
                     .replace("##RELEASE_NAME##", release.getFullName())
                     .replace("##RELEASE_ID##", String.valueOf(repoId))
                     .replace("##VERSION_ID##", String.valueOf(version.getId()))
-                    .replace("##FIXED_ISSUES_COUNT##", String.valueOf(version.getIssuesFixedCount()));
+                    .replace("##FIXED_ISSUES_COUNT##", String.valueOf(version.getIssuesFixedCount()))
+                    .replace("##USER_NAME##", membersFinder.getCurrentMember().getName());
                     
             logger.info(emailContents);
 
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
index 74cdd23..90244eb 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -95,15 +95,10 @@ public class TallyVotesCommand implements Command {
                     }
                 }
             }
-            String currentUserId = System.getProperty("asf.username");
-            if (currentUserId == null) {
-                currentUserId = System.getenv("ASF_USERNAME");
-            }
-            Member currentUser = membersFinder.getMemberById(currentUserId);
             String email = EMAIL_TEMPLATE
                 .replace("##RELEASE_NAME##", release.getFullName())
                 .replace("##BINDING_VOTERS##", String.join(", ", bindingVoters))
-                .replace("##USER_NAME##", currentUser == null ? "" : currentUser.getName());
+                .replace("##USER_NAME##", membersFinder.getCurrentMember().getName());
             if (nonBindingVoters.isEmpty()) {
                 email = email.replace("##NON_BINDING_VOTERS##", "none");
             } else {


[sling-org-apache-sling-committer-cli] 05/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit ad74b228a8633bfb2b58f24056cd825146e8be33
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Tue Mar 19 16:13:04 2019 +0100

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Add command for generating local changes to the website
---
 pom.xml                                            |   13 +-
 src/main/features/app.json                         |   12 +
 .../sling/cli/impl/jbake/JBakeContentUpdater.java  |  140 ++
 .../cli/impl/release/UpdateLocalSiteCommand.java   |    4 -
 .../cli/impl/jbake/JBakeContentUpdaterTest.java    |  197 +++
 src/test/resources/downloads.tpl                   |  474 +++++
 src/test/resources/releases.md                     | 1812 ++++++++++++++++++++
 7 files changed, 2647 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index 047121d..1f717d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -164,10 +164,21 @@
             <version>2.8.5</version>
             <scope>provided</scope>
         </dependency>
-        
+        <dependency>
+          <groupId>org.eclipse.jgit</groupId>
+          <artifactId>org.eclipse.jgit</artifactId>
+          <version>5.2.1.201812262042-r</version>
+          <scope>provided</scope>
+        </dependency>
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <version>1.3</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/features/app.json b/src/main/features/app.json
index f07827d..4d310c4 100644
--- a/src/main/features/app.json
+++ b/src/main/features/app.json
@@ -56,6 +56,18 @@
 		{
 			"id": "com.google.code.gson:gson:2.8.5",
 			"start-level": "3"
+		},
+		{
+			"id": "org.eclipse.jgit:org.eclipse.jgit:5.2.1.201812262042-r",
+			"start-level": "3"
+		},
+		{
+			"id": "com.googlecode.javaewah:JavaEWAH:1.1.6",
+			"start-level": "3"
+		},
+		{
+			"id": "org.apache.servicemix.bundles:org.apache.servicemix.bundles.jsch:0.1.55_1",
+			"start-level": "3"
 		}
 	],
 	"configurations": {
diff --git a/src/main/java/org/apache/sling/cli/impl/jbake/JBakeContentUpdater.java b/src/main/java/org/apache/sling/cli/impl/jbake/JBakeContentUpdater.java
new file mode 100644
index 0000000..52bd1a4
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/jbake/JBakeContentUpdater.java
@@ -0,0 +1,140 @@
+/*
+ * 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.cli.impl.jbake;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+public class JBakeContentUpdater {
+    
+    private static final Pattern DOWNLOAD_LINE_PATTERN = Pattern.compile("^.*\"([a-zA-Z\\s\\-]+)\\|([a-zA-Z\\.\\-]+)\\|([0-9\\.\\-]+).*$");
+
+    public int updateDownloads(Path downloadsTemplatePath, String newReleaseName, String newReleaseVersion) throws IOException {
+        
+        int[] changeCount = new int[1];
+        
+        List<String> updatedLines = Files.readAllLines(downloadsTemplatePath, StandardCharsets.UTF_8).stream()
+                .map(line -> {
+                    Matcher matcher = DOWNLOAD_LINE_PATTERN.matcher(line);
+                    if ( !matcher.find() )
+                        return line;
+                    
+                    if ( ! matcher.group(1).equals(newReleaseName) )
+                        return line;
+                    
+                    changeCount[0]++;
+                    
+                    StringBuilder buffer = new StringBuilder();
+                    buffer.append(line.substring(0, matcher.start(3)));
+                    buffer.append(newReleaseVersion);
+                    buffer.append(line.substring(matcher.end(3)));
+                    
+                    return buffer.toString();
+                }).collect(Collectors.toList());
+
+        Files.write(downloadsTemplatePath, updatedLines);
+        
+        return changeCount[0];
+    }
+
+    public void updateReleases(Path releasesPath, String releaseName, String releaseVersion, LocalDateTime releaseTime) throws IOException {
+        
+        List<String> releasesLines = Files.readAllLines(releasesPath, StandardCharsets.UTF_8);
+        String dateHeader = "## " + releaseTime.format(DateTimeFormatter.ofPattern("MMMM uuuu", Locale.ENGLISH));
+        
+        int releaseLineIdx = -1;
+        int dateLineIdx = -1;
+        for ( int i = 0 ; i < releasesLines.size(); i++ ) {
+            String releasesLine = releasesLines.get(i);
+            if ( releasesLine.startsWith("This is a list of all our releases") ) {
+                releaseLineIdx = i;
+            }
+            if ( releasesLine.equals(dateHeader) ) {
+                dateLineIdx = i;
+            }
+        }
+        
+        if ( dateLineIdx == -1 ) {
+            // need to add month marker
+            releasesLines.add(releaseLineIdx + 1, "");
+            releasesLines.add(releaseLineIdx + 2, dateHeader);
+            releasesLines.add(releaseLineIdx + 3, "");
+            dateLineIdx = releaseLineIdx + 2;
+        }
+        
+        String date = formattedDay(releaseTime);
+        
+        // inspect all lines in the current month ( until empty line found )
+        // to see if the release date already exists
+        boolean changed = false;
+        for ( int i = dateLineIdx +2 ; i < releasesLines.size(); i++ ) {
+            String potentialLine = releasesLines.get(i);
+            if ( potentialLine.trim().isEmpty() )
+                break;
+            
+            if ( potentialLine.endsWith("(" +date+")") ) {
+                if ( potentialLine.contains(releaseName + " " + releaseVersion ) ) {
+                    changed = true;
+                    break;
+                }
+                
+                int insertionIdx = potentialLine.indexOf('(') - 1;
+                StringBuilder buffer = new StringBuilder();
+                buffer
+                    .append(potentialLine.substring(0, insertionIdx))
+                    .append(", ")
+                    .append(releaseName)
+                    .append(' ')
+                    .append(releaseVersion)
+                    .append(' ')
+                    .append(potentialLine.substring(insertionIdx + 1));
+                
+                releasesLines.set(i, buffer.toString());
+                changed = true;
+                break;
+            }
+        }
+        
+        if ( !changed )
+            releasesLines.add(dateLineIdx + 2, "* " + releaseName + " " + releaseVersion +" (" + date + ")");
+
+        Files.write(releasesPath, releasesLines);
+    }
+    
+    private String formattedDay(LocalDateTime releaseTime) {
+        String date = releaseTime.format(DateTimeFormatter.ofPattern("d", Locale.ENGLISH));
+        switch (date) {
+        case "1":
+            return "1st";
+        case "2":
+            return "2nd";
+        case "3":
+            return "3rd";
+        default:
+            return date + "th";
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
index 483613e..10e836a 100644
--- a/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
+++ b/src/main/java/org/apache/sling/cli/impl/release/UpdateLocalSiteCommand.java
@@ -44,10 +44,6 @@ import org.slf4j.LoggerFactory;
 })
 public class UpdateLocalSiteCommand implements Command {
     
-    public static void main(String[] args) {
-        System.out.println(LocalDateTime.now().format(DateTimeFormatter.ofPattern("MMMM uuuu", Locale.ENGLISH)));
-    }
-
     private static final String GIT_CHECKOUT = "/tmp/sling-site";
 
     @Reference
diff --git a/src/test/java/org/apache/sling/cli/impl/jbake/JBakeContentUpdaterTest.java b/src/test/java/org/apache/sling/cli/impl/jbake/JBakeContentUpdaterTest.java
new file mode 100644
index 0000000..5bbbc53
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/jbake/JBakeContentUpdaterTest.java
@@ -0,0 +1,197 @@
+/*
+ * 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.cli.impl.jbake;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.contains;
+import static org.junit.Assert.assertThat;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.diff.DiffEntry.Side;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class JBakeContentUpdaterTest {
+
+    @Rule
+    public TemporaryFolder tmp = new TemporaryFolder();
+    
+    private JBakeContentUpdater updater;
+    
+    @Before
+    public void setUp() throws IOException {
+        
+        updater = new JBakeContentUpdater();
+        // copy the file away so we don't modify what is in source control
+        Files.copy(getClass().getResourceAsStream("/downloads.tpl"), Paths.get(new File(tmp.getRoot(), "downloads.tpl").toURI()));
+        Files.copy(getClass().getResourceAsStream("/releases.md"), Paths.get(new File(tmp.getRoot(), "releases.md").toURI()));
+    }
+    
+    @Test
+    public void updateDownloadsTemplate_newReleaseOfExistingModule() throws IOException {
+        
+        updateDownloadsTemplate0("API", "2.20.2");
+    }
+
+    private void updateDownloadsTemplate0(String newReleaseName, String newReleaseVersion) throws IOException {
+        Path templatePath = Paths.get(new File(tmp.getRoot(), "downloads.tpl").toURI());
+        
+        int changeCount = updater.updateDownloads(templatePath, newReleaseName, newReleaseVersion);
+        assertThat("Unexpected count of changes", changeCount, equalTo(1));
+
+        String apiLine = Files.readAllLines(templatePath, StandardCharsets.UTF_8).stream()
+            .filter( l -> l.trim().startsWith("\"" + newReleaseName + "|"))
+            .findFirst()
+            .get();
+        
+        assertThat("Did not find modified version in the release line", apiLine, containsString(newReleaseVersion));
+    }
+
+    @Test
+    public void updateDownloadsTemplate_newReleaseOfExistingMavenPlugin() throws IOException {
+        
+        updateDownloadsTemplate0("Slingstart Maven Plugin", "1.9.0");
+    }
+
+    @Test
+    public void updateDownloadsTemplate_newReleaseOfIDETooling() throws IOException {
+
+        updateDownloadsTemplate0("Sling IDE Tooling for Eclipse", "1.4.0");
+    }
+    
+    @Test
+    public void updateReleases_releaseInExistingMonth() throws IOException, GitAPIException {
+        updateReleases0(LocalDateTime.of(2019, 2, 27, 22, 00), 
+            Arrays.asList( 
+                " " ,  
+                " ## February 2019", 
+                " " ,
+                "+* API 2.20.2 (27th)",
+                " * DataSource Provider 1.0.4, Resource Collection API 1.0.2, JCR ResourceResolver 3.0.18 (26th)",  
+                " * Scripting JSP Tag Library 2.4.0, Scripting JSP Tag Library (Compat) 1.0.0 (18th)",
+                " * Pipes 3.1.0 (15th)"
+            )
+        );
+        
+    }
+
+    private void updateReleases0(LocalDateTime releaseDate, List<String> expectedLines, String... releaseNameAndInfo) throws IOException, GitAPIException {
+        
+        if ( releaseNameAndInfo.length > 2 )
+            throw new IllegalArgumentException("Unexpected releaseNameAndInfo: " + Arrays.toString(releaseNameAndInfo));
+        
+        String releaseName = releaseNameAndInfo.length > 0 ? releaseNameAndInfo[0] : "API";
+        String releaseVersion = releaseNameAndInfo.length > 1 ? releaseNameAndInfo[1] : "2.20.2";
+        
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        
+        try ( Git git = Git.init().setDirectory(tmp.getRoot()).call() ) {
+            git.add()
+                .addFilepattern("downloads.tpl")
+                .addFilepattern("releases.md")
+                .call();
+            
+            git.commit()
+                .setMessage("Initial commit")
+                .call();
+            
+            Path releasesPath = Paths.get(new File(tmp.getRoot(), "releases.md").toURI());
+            updater.updateReleases(releasesPath, releaseName, releaseVersion, releaseDate);
+            
+            List<DiffEntry> changes = git.diff().setOutputStream(out).call();
+            
+            // ensure that the diff we're getting only refers to the releases file
+            // alternatively, when no changes are expected validate that
+            
+            if ( expectedLines.isEmpty() ) {
+                assertThat("changes.size", changes.size(), equalTo(0));
+                return;
+            }
+
+            assertThat("changes.size", changes.size(), equalTo(1));
+            assertThat("changes[0].type", changes.get(0).getChangeType(), equalTo(ChangeType.MODIFY));
+            assertThat("changes[0].path", changes.get(0).getPath(Side.NEW), equalTo("releases.md"));
+            
+            // now hack away on it safely
+            List<String> ignoredPrefixes = Arrays.asList("diff", "index", "---", "+++", "@@");
+            List<String> diffLines = Arrays.stream(new String(out.toByteArray(), StandardCharsets.UTF_8)
+                .split("\\n"))
+                .filter( l -> !ignoredPrefixes.stream().filter( p -> l.startsWith(p)).findAny().isPresent() )
+                .collect(Collectors.toList());
+            
+            assertThat(diffLines, contains(expectedLines.toArray(new String[0])));
+        }
+    }
+
+    @Test
+    public void updateReleases_releaseAlreadyExists() throws IOException, GitAPIException {
+        updateReleases0(LocalDateTime.of(2019, 2, 18, 22, 00), Collections.emptyList(), "Scripting JSP Tag Library", "2.4.0");
+    }
+    
+    @Test
+    public void updateReleases_releaseInNewMonth() throws IOException, GitAPIException {
+        updateReleases0(LocalDateTime.of(2019, 3, 15, 22, 00), 
+            Arrays.asList( 
+               " ~~~~~~",
+               " This is a list of all our releases, available from our [downloads](/downloads.cgi) page.",
+               " ",
+               "+## March 2019",
+               "+",
+               "+* API 2.20.2 (15th)",
+               "+",
+               " ## February 2019",
+               " ",
+               " * DataSource Provider 1.0.4, Resource Collection API 1.0.2, JCR ResourceResolver 3.0.18 (26th)"
+            )
+        );
+    }
+
+    @Test
+    public void updateReleases_releaseExistingMonthAndDay() throws IOException, GitAPIException {
+        updateReleases0(LocalDateTime.of(2019, 2, 26, 22, 00),
+            Arrays.asList(
+                " ", 
+                " ## February 2019", 
+                " ", 
+                "-* DataSource Provider 1.0.4, Resource Collection API 1.0.2, JCR ResourceResolver 3.0.18 (26th)",
+                "+* DataSource Provider 1.0.4, Resource Collection API 1.0.2, JCR ResourceResolver 3.0.18, API 2.20.2 (26th)", 
+                " * Scripting JSP Tag Library 2.4.0, Scripting JSP Tag Library (Compat) 1.0.0 (18th)", 
+                " * Pipes 3.1.0 (15th)", 
+                " * Testing OSGi Mock 2.4.6 (14th)"
+            )
+        );
+    }
+}
diff --git a/src/test/resources/downloads.tpl b/src/test/resources/downloads.tpl
new file mode 100644
index 0000000..5a0d578
--- /dev/null
+++ b/src/test/resources/downloads.tpl
@@ -0,0 +1,474 @@
+/*
+ * 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.
+ */
+
+// ------------------------------------------------------------------------------------------------
+// Sling downloads page
+// http://www.apache.org/dev/release-download-pages.html explains how the apache.org mirrored
+// downloads page work. Basically, we provide a downloads.html page with a few placeholders
+// and a form to select the download mirrog, and a downloads.cgi page which wraps the apache.org
+// download logic CGI.
+// ------------------------------------------------------------------------------------------------
+
+// ------------------------------------------------------------------------------------------------
+// Downloads template data
+// The page template itself is found below.
+// To convert from the old svn downloads.list ust
+//    while read l; do echo "  \"$l\","; done < content/downloads.list
+// ------------------------------------------------------------------------------------------------
+def launchpadVersion="11"
+
+def slingIDETooling=[
+  "Sling IDE Tooling for Eclipse|eclipse|1.2.2|A p2 update site which can be installed in Eclipse.|sling-ide-tooling"
+]
+
+def slingApplication=[
+  "Sling Starter Standalone|A self-runnable Sling jar|org.apache.sling.starter|.jar|${launchpadVersion}|Y",
+  "Sling Starter WAR|A ready-to run Sling webapp as a war file|org.apache.sling.starter|-webapp.war|${launchpadVersion}|Y",
+  "Sling Source Release|The released Sling source code|org.apache.sling.starter|-source-release.zip|${launchpadVersion}|Y",
+  "Sling CMS App|A reference CMS App built on Apache Sling|org.apache.sling.cms.builder|.jar|0.11.2|org.apache.sling.app.cms",
+]
+
+def mavenPlugins=[
+  "JSPC Maven Plugin|jspc-maven-plugin|2.1.0|Y",
+  "Maven Launchpad Plugin|maven-launchpad-plugin|2.3.4|Y",
+  "Sling Maven Plugin|sling-maven-plugin|2.4.0|Y",
+  "Slingstart Maven Plugin|slingstart-maven-plugin|1.8.2|Y",
+  "HTL Maven Plugin|htl-maven-plugin|1.2.2-1.4.0|Y",
+]
+
+def bundles=[
+  "Adapter|org.apache.sling.adapter|2.1.10|Y|jar",
+  "Adapter Annotations|adapter-annotations|1.0.0|Y|jar",
+  "API|org.apache.sling.api|2.20.0|Y|jar",
+  "Auth Core|org.apache.sling.auth.core|1.4.2|Y|jar",
+  "Auth Form|org.apache.sling.auth.form|1.0.12|Y|jar",
+  "Authentication XING API|org.apache.sling.auth.xing.api|0.0.2|Y|jar",
+  "Authentication XING Login|org.apache.sling.auth.xing.login|0.0.2|Y|jar",
+  "Authentication XING OAuth|org.apache.sling.auth.xing.oauth|0.0.2|Y|jar",
+  "Bundle Resource Provider|org.apache.sling.bundleresource.impl|2.3.2|Y|jar",
+  "Capabilities|org.apache.sling.capabilities|0.1.2|Y|jar",
+  "Capabilities JCR|org.apache.sling.capabilities.jcr|0.1.2|Y|jar",
+  "Clam|org.apache.sling.clam|1.0.2|Y|jar",   
+  "Classloader Leak Detector|org.apache.sling.extensions.classloader-leak-detector|1.0.0|Y|jar",
+  "CMS App API|org.apache.sling.cms.api|0.11.2|org.apache.sling.app.cms|jar",
+  "CMS App Core|org.apache.sling.cms.core|0.11.2|org.apache.sling.app.cms|jar",
+  "CMS App Reference|org.apache.sling.cms.reference|0.11.2|org.apache.sling.app.cms|jar",
+  "CMS App UI|org.apache.sling.cms.ui|0.11.2|org.apache.sling.app.cms|jar",
+  "Commons Classloader|org.apache.sling.commons.classloader|1.4.4|Y|jar",
+  "Commons Clam|org.apache.sling.commons.clam|1.0.2|Y|jar",
+  "Commons Compiler|org.apache.sling.commons.compiler|2.3.6|Y|jar",
+  "Commons FileSystem ClassLoader|org.apache.sling.commons.fsclassloader|1.0.8|Y|jar",
+  "Commons HTML|org.apache.sling.commons.html|1.1.0|Y|jar",
+  "Commons Johnzon|org.apache.sling.commons.johnzon|1.1.0|Y|jar",
+  "Commons Log|org.apache.sling.commons.log|5.1.10|Y|jar",
+  "Commons Log WebConsole Plugin|org.apache.sling.commons.log.webconsole|1.0.0|Y|jar",
+  "Commons Log Service|org.apache.sling.commons.logservice|1.0.6|Y|jar",
+  "Commons Metrics|org.apache.sling.commons.metrics|1.2.6|Y|jar",
+  "Commons RRD4J metrics reporter|org.apache.sling.commons.metrics-rrd4j|1.0.2|Y|jar",
+  "Commons Mime Type Service|org.apache.sling.commons.mime|2.2.0|Y|jar",
+  "Commons OSGi|org.apache.sling.commons.osgi|2.4.0|Y|jar",
+  "Commons Scheduler|org.apache.sling.commons.scheduler|2.7.2|Y|jar",
+  "Commons Testing|org.apache.sling.commons.testing|2.1.2|Y|jar",
+  "Commons Threads|org.apache.sling.commons.threads|3.2.18|Y|jar",
+  "Content Detection Support|org.apache.sling.commons.contentdetection|1.0.2|Y|jar",
+  "Context-Aware Configuration API|org.apache.sling.caconfig.api|1.1.2|Y|jar",
+  "Context-Aware Configuration bnd Plugin|org.apache.sling.caconfig.bnd-plugin|1.0.2|Y|jar",
+  "Context-Aware Configuration Impl|org.apache.sling.caconfig.impl|1.4.14|Y|jar",
+  "Context-Aware Configuration Mock Plugin|org.apache.sling.testing.caconfig-mock-plugin|1.3.2|Y|jar",
+  "Context-Aware Configuration SPI|org.apache.sling.caconfig.spi|1.3.4|Y|jar",
+  "Crankstart API|org.apache.sling.crankstart.api|1.0.0|N|jar",
+  "Crankstart API Fragment|org.apache.sling.crankstart.api.fragment|1.0.2|N|jar",
+  "Crankstart Core|org.apache.sling.crankstart.core|1.0.0|N|jar",
+  "Crankstart Launcher|org.apache.sling.crankstart.launcher|1.0.0|Y|jar",
+  "Crankstart Launcher Sling Extensions|org.apache.sling.crankstart.sling.extensions|1.0.0|Y|jar",
+  "Crankstart Launcher Test Services|org.apache.sling.crankstart.test.services|1.0.0|Y|jar",
+  "DataSource Provider|org.apache.sling.datasource|1.0.4|Y|jar",
+  "Discovery API|org.apache.sling.discovery.api|1.0.4|Y|jar",
+  "Discovery Impl|org.apache.sling.discovery.impl|1.2.12|Y|jar",
+  "Discovery Commons|org.apache.sling.discovery.commons|1.0.20|Y|jar",
+  "Discovery Base|org.apache.sling.discovery.base|2.0.8|Y|jar",
+  "Discovery Oak|org.apache.sling.discovery.oak|1.2.28|Y|jar",
+  "Discovery Standalone|org.apache.sling.discovery.standalone|1.0.2|Y|jar",
+  "Discovery Support|org.apache.sling.discovery.support|1.0.4|Y|jar",
+  "Distributed Event Admin|org.apache.sling.event.dea|1.1.4|Y|jar",
+  "Distribution API|org.apache.sling.distribution.api|0.3.0|Y|jar",
+  "Distribution Core|org.apache.sling.distribution.core|0.4.0|Y|jar",
+  "Distribution Integration Tests|org.apache.sling.distribution.it|0.1.2|Y|jar",
+  "Distribution Sample|org.apache.sling.distribution.sample|0.1.6|Y|jar",
+  "Dynamic Include|org.apache.sling.dynamic-include|3.1.2|Y|jar",
+  "Engine|org.apache.sling.engine|2.6.18|Y|jar",
+  "Event|org.apache.sling.event|4.2.12|Y|jar",
+  "Event API|org.apache.sling.event.api|1.0.0|Y|jar",
+  "Feature Model|org.apache.sling.feature|1.0.0|Y|jar",
+  "Feature Model Analyser|org.apache.sling.feature.analyser|0.8.0|Y|jar",
+  "Feature Model IO|org.apache.sling.feature.io|1.0.0|Y|jar",
+  "Feature Model Converter|org.apache.sling.feature.modelconverter|0.8.0|Y|jar",
+  "Feature Flags|org.apache.sling.featureflags|1.2.2|Y|jar",
+  "File Optimization|org.apache.sling.fileoptim|0.9.2|org.apache.sling.file.optimization|jar",
+  "File System Resource Provider|org.apache.sling.fsresource|2.1.14|Y|jar",
+  "I18n|org.apache.sling.i18n|2.5.12|Y|jar",
+  "HApi|org.apache.sling.hapi|1.1.0|Y|jar",
+  "Health Check Annotations|org.apache.sling.hc.annotations|1.0.6|Y|jar",
+  "Health Check Core|org.apache.sling.hc.core|1.2.10|Y|jar",
+  "Health Check API|org.apache.sling.hc.api|1.0.2|Y|jar",
+  "Health Check Integration Tests|org.apache.sling.hc.it|1.0.4|Y|jar",
+  "Health Check JUnit Bridge|org.apache.sling.hc.junit.bridge|1.0.2|Y|jar",
+  "Health Check Samples|org.apache.sling.hc.samples|1.0.6|Y|jar",
+  "Health Check Support|org.apache.sling.hc.support|1.0.4|Y|jar",
+  "Health Check Webconsole|org.apache.sling.hc.webconsole|1.1.2|Y|jar",
+  "Installer Core|org.apache.sling.installer.core|3.9.0|Y|jar",
+  "Installer Console|org.apache.sling.installer.console|1.0.2|Y|jar",
+  "Installer Configuration Support|org.apache.sling.installer.factory.configuration|1.2.0|Y|jar",
+  "Installer Health Checks|org.apache.sling.installer.hc|2.0.0|Y|jar",
+  "Installer Subystems Support|org.apache.sling.installer.factory.subsystems|1.0.0|Y|jar",
+  "Installer File Provider|org.apache.sling.installer.provider.file|1.1.0|Y|jar",
+  "Installer JCR Provider|org.apache.sling.installer.provider.jcr|3.1.26|Y|jar",
+  "Installer Vault Package Install Hook|org.apache.sling.installer.provider.installhook|1.0.4|Y|jar",
+  "javax activation|org.apache.sling.javax.activation|0.1.0|Y|jar",
+  "JCR API|org.apache.sling.jcr.api|2.4.0|Y|jar",
+  "JCR API Wrapper|org.apache.sling.jcr.jcr-wrapper|2.0.0|Y|jar",
+  "JCR Base|org.apache.sling.jcr.base|3.0.6|Y|jar",
+  "JCR ClassLoader|org.apache.sling.jcr.classloader|3.2.4|Y|jar",
+  "JCR Content Loader|org.apache.sling.jcr.contentloader|2.3.0|Y|jar",
+  "JCR Content Parser|org.apache.sling.jcr.contentparser|1.2.6|Y|jar",
+  "JCR DavEx|org.apache.sling.jcr.davex|1.3.10|Y|jar",
+  "JCR Jackrabbit AccessManager|org.apache.sling.jcr.jackrabbit.accessmanager|3.0.4|Y|jar",
+  "JCR Jackrabbit UserManager|org.apache.sling.jcr.jackrabbit.usermanager|2.2.8|Y|jar",
+  "JCR Oak Server|org.apache.sling.jcr.oak.server|1.2.2|Y|jar",
+  "JCR Registration|org.apache.sling.jcr.registration|1.0.6|Y|jar",
+  "JCR Repoinit|org.apache.sling.jcr.repoinit|1.1.8|Y|jar",
+  "JCR Resource|org.apache.sling.jcr.resource|3.0.18|Y|jar",
+  "JCR Resource Security|org.apache.sling.jcr.resourcesecurity|1.0.2|Y|jar",
+  "JCR Web Console Plugin|org.apache.sling.jcr.webconsole|1.0.2|Y|jar",
+  "JMX Resource Provider|org.apache.sling.jmx.provider|1.0.2|Y|jar",
+  "JCR WebDAV|org.apache.sling.jcr.webdav|2.3.8|Y|jar",
+  "JUnit Core|org.apache.sling.junit.core|1.0.26|Y|jar",
+  "JUnit Remote Tests Runners|org.apache.sling.junit.remote|1.0.12|Y|jar",
+  "JUnit Scriptable Tests Provider|org.apache.sling.junit.scriptable|1.0.12|Y|jar",
+  "JUnit Tests Teleporter|org.apache.sling.junit.teleporter|1.0.18|Y|jar",
+  "JUnit Health Checks|org.apache.sling.junit.healthcheck|1.0.6|Y|jar",
+  "Launchpad API|org.apache.sling.launchpad.api|1.2.0|Y|jar",
+  "Launchpad Base|org.apache.sling.launchpad.base|5.6.10-2.6.26|Y|jar",
+  "Launchpad Base - Application Launcher|org.apache.sling.launchpad.base|5.6.10-2.6.26|Y|war",
+  "Launchpad Base - Web Launcher|org.apache.sling.launchpad.base|5.6.10-2.6.26|Y|war",
+  "Launchpad Installer|org.apache.sling.launchpad.installer|1.2.2|Y|jar",
+  "Launchpad Integration Tests|org.apache.sling.launchpad.integration-tests|1.0.8|Y|jar",
+  "Launchpad Test Fragment Bundle|org.apache.sling.launchpad.test-fragment|2.0.16|Y|jar",
+  "Launchpad Test Bundles|org.apache.sling.launchpad.test-bundles|0.0.6|Y|jar",
+  "Launchpad Testing|org.apache.sling.launchpad.testing|11|Y|jar",
+  "Launchpad Testing WAR|org.apache.sling.launchpad.testing-war|11|Y|jar",
+  "Launchpad Testing Services|org.apache.sling.launchpad.test-services|2.0.16|Y|jar",
+  "Launchpad Testing Services WAR|org.apache.sling.launchpad.test-services-war|2.0.16|Y|war",
+  "Log Tracer|org.apache.sling.tracer|1.0.6|Y|jar",
+  "Models API|org.apache.sling.models.api|1.3.8|Y|jar",
+  "Models bnd Plugin|org.apache.sling.bnd.models|1.0.0|Y|jar",
+  "Models Implementation|org.apache.sling.models.impl|1.4.10|Y|jar",
+  "Models Jackson Exporter|org.apache.sling.models.jacksonexporter|1.0.8|Y|jar",
+  "NoSQL Generic Resource Provider|org.apache.sling.nosql.generic|1.1.0|Y|jar",
+  "NoSQL Couchbase Client|org.apache.sling.nosql.couchbase-client|1.0.2|Y|jar",
+  "NoSQL Couchbase Resource Provider|org.apache.sling.nosql.couchbase-resourceprovider|1.1.0|Y|jar",
+  "NoSQL MongoDB Resource Provider|org.apache.sling.nosql.mongodb-resourceprovider|1.1.0|Y|jar",
+  "Oak Restrictions|org.apache.sling.oak.restrictions|1.0.2|Y|jar",
+  "Pax Exam Utilities|org.apache.sling.paxexam.util|1.0.4|Y|jar",
+  "Performance Test Utilities|org.apache.sling.performance.base|1.0.2|org.apache.sling.performance|jar",
+  "Pipes|org.apache.sling.pipes|3.1.0|Y|jar",
+  "Provisioning Model|org.apache.sling.provisioning.model|1.8.4|Y|jar",
+  "Repoinit Parser|org.apache.sling.repoinit.parser|1.2.2|Y|jar",
+  "Resource Access Security|org.apache.sling.resourceaccesssecurity|1.0.0|Y|jar",
+  "Resource Builder|org.apache.sling.resourcebuilder|1.0.4|Y|jar",
+  "Resource Collection|org.apache.sling.resourcecollection|1.0.2|Y|jar",
+  "Resource Filter|org.apache.sling.resource.filter|1.0.0|Y|jar",
+  "Resource Inventory|org.apache.sling.resource.inventory|1.0.8|Y|jar",
+  "Resource Merger|org.apache.sling.resourcemerger|1.3.8|Y|jar",
+  "Resource Presence|org.apache.sling.resource.presence|0.0.2|Y|jar",
+  "Resource Resolver|org.apache.sling.resourceresolver|1.6.6|Y|jar",
+  "Rewriter|org.apache.sling.rewriter|1.2.2|Y|jar",
+  "Failing Server-Side Tests|org.apache.sling.testing.samples.failingtests|1.0.6|N|jar",
+  "Sample Integration Tests|org.apache.sling.testing.samples.integrationtests|1.0.6|N|jar",
+  "Sample Server-Side Tests|org.apache.sling.testing.samples.sampletests|1.0.6|N|jar",
+  "Scripting API|org.apache.sling.scripting.api|2.2.0|Y|jar",
+  "Scripting Console|org.apache.sling.scripting.console|1.0.0|Y|jar",
+  "Scripting Core|org.apache.sling.scripting.core|2.0.56|Y|jar",
+  "Scripting EL API Wrapper|org.apache.sling.scripting.el-api|1.0.0|Y|jar",
+  "Scripting Java|org.apache.sling.scripting.java|2.1.2|Y|jar",
+  "Scripting JavaScript|org.apache.sling.scripting.javascript|3.0.4|Y|jar",
+  "Scripting JSP|org.apache.sling.scripting.jsp|2.3.4|Y|jar",
+  "Scripting JSP API Wrapper|org.apache.sling.scripting.jsp-api|1.0.0|Y|jar",
+  "Scripting JSP Taglib|org.apache.sling.scripting.jsp.taglib|2.4.0|Y|jar",
+  "Scripting Groovy|org.apache.sling.scripting.freemarker|1.0.0|Y|jar",
+  "Scripting Groovy|org.apache.sling.scripting.groovy|1.0.4|Y|jar",
+  "Scripting HTL Runtime|org.apache.sling.scripting.sightly.runtime|1.1.0-1.4.0|Y|jar",
+  "Scripting HTL Compiler|org.apache.sling.scripting.sightly.compiler|1.1.2-1.4.0|Y|jar",
+  "Scripting HTL Java Compiler|org.apache.sling.scripting.sightly.compiler.java|1.1.2-1.4.0|Y|jar",
+  "Scripting HTL Engine|org.apache.sling.scripting.sightly|1.1.2-1.4.0|Y|jar",
+  "Scripting HTL JavaScript Use Provider|org.apache.sling.scripting.sightly.js.provider|1.0.28|Y|jar",
+  "Scripting HTL Sling Models Use Provider|org.apache.sling.scripting.sightly.models.provider|1.0.8|Y|jar",
+  "Scripting HTL REPL|org.apache.sling.scripting.sightly.repl|1.0.6|Y|jar",
+  "Scripting Thymeleaf|org.apache.sling.scripting.thymeleaf|2.0.0|Y|jar",
+  "Security|org.apache.sling.security|1.1.16|Y|jar",
+  "Service User Mapper|org.apache.sling.serviceusermapper|1.4.2|Y|jar",
+  "Service User WebConsole|org.apache.sling.serviceuser.webconsole|1.0.0|Y|jar",
+  "Servlet Annotations|org.apache.sling.servlets.annotations|1.2.4|Y|jar",
+  "Servlet Helpers|org.apache.sling.servlet-helpers|1.1.8|Y|jar",
+  "Servlets Get|org.apache.sling.servlets.get|2.1.40|Y|jar",
+  "Servlets Post|org.apache.sling.servlets.post|2.3.28|Y|jar",
+  "Servlets Resolver|org.apache.sling.servlets.resolver|2.5.2|Y|jar",
+  "Settings|org.apache.sling.settings|1.3.10|Y|jar",
+  "Slf4j MDC Filter|org.apache.sling.extensions.slf4j.mdc|1.0.0|Y|jar",
+  "Sling Query|org.apache.sling.query|4.0.2|Y|jar",
+  "Starter Content|org.apache.sling.starter.content|1.0.2|Y|jar",
+  "Starter Startup|org.apache.sling.starter.startup|1.0.6|Y|jar",
+  "Superimposing Resource Provider|org.apache.sling.superimposing|0.2.0|Y|jar",
+  "System Bundle Extension: Activation API|org.apache.sling.fragment.activation|1.0.2|Y|jar",
+  "System Bundle Extension: WS APIs|org.apache.sling.fragment.ws|1.0.2|Y|jar",
+  "System Bundle Extension: XML APIs|org.apache.sling.fragment.xml|1.0.2|Y|jar",
+  "Tenant|org.apache.sling.tenant|1.1.4|Y|jar",
+  "Testing Clients|org.apache.sling.testing.clients|1.2.0|Y|jar",
+  "Testing Email|org.apache.sling.testing.email|1.0.0|Y|jar",
+  "Testing Hamcrest|org.apache.sling.testing.hamcrest|1.0.2|Y|jar",
+  "Testing JCR Mock|org.apache.sling.testing.jcr-mock|1.4.2|Y|jar",
+  "Testing Logging Mock|org.apache.sling.testing.logging-mock|2.0.0|Y|jar",
+  "Testing OSGi Mock Core|org.apache.sling.testing.osgi-mock.core|2.4.6|org.apache.sling.testing.osgi-mock|jar",
+  "Testing OSGi Mock JUnit 4|org.apache.sling.testing.osgi-mock.junit4|2.4.6|org.apache.sling.testing.osgi-mock|jar",
+  "Testing OSGi Mock JUnit 5|org.apache.sling.testing.osgi-mock.junit5|2.4.6|org.apache.sling.testing.osgi-mock|jar",
+  "Testing PaxExam|org.apache.sling.testing.paxexam|2.0.0|Y|jar",
+  "Testing Rules|org.apache.sling.testing.rules|1.0.8|Y|jar",
+  "Testing Resource Resolver Mock|org.apache.sling.testing.resourceresolver-mock|1.1.22|Y|jar",
+  "Testing Server Setup Tools|org.apache.sling.testing.serversetup|1.0.1|Y|jar",
+  "Testing Sling Mock Core|org.apache.sling.testing.sling-mock.core|2.3.4|org.apache.sling.testing.sling-mock|jar",
+  "Testing Sling Mock JUnit 4|org.apache.sling.testing.sling-mock.junit4|2.3.4|org.apache.sling.testing.sling-mock|jar",
+  "Testing Sling Mock JUnit 5|org.apache.sling.testing.sling-mock.junit5|2.3.4|org.apache.sling.testing.sling-mock|jar",
+  "Testing Sling Mock Oak|org.apache.sling.testing.sling-mock-oak|2.1.2|Y|jar",
+  "Tooling Support Install|org.apache.sling.tooling.support.install|1.0.4|Y|jar",
+  "Tooling Support Source|org.apache.sling.tooling.support.source|1.0.4|Y|jar",
+  "URL Rewriter|org.apache.sling.urlrewriter|0.0.2|Y|jar",
+  "Validation API|org.apache.sling.validation.api|1.0.0|Y|jar",
+  "Validation Core|org.apache.sling.validation.core|1.0.4|Y|jar",
+  "Web Console Branding|org.apache.sling.extensions.webconsolebranding|1.0.2|Y|jar",
+  "Web Console Security Provider|org.apache.sling.extensions.webconsolesecurityprovider|1.2.0|Y|jar",
+  "XSS Protection|org.apache.sling.xss|2.1.0|Y|jar",
+  "XSS Protection Compat|org.apache.sling.xss.compat|1.1.0|N|jar"
+]
+                                                                      
+def deprecated=[
+  "Auth OpenID|Not Maintained|org.apache.sling.auth.openid|1.0.4",
+  "Auth Selector|Not Maintained|org.apache.sling.auth.selector|1.0.6",
+  "Background Servlets Engine|Not Maintained|org.apache.sling.bgservlets|1.0.8",
+  "Background Servlets Integration Test|Not Maintained|org.apache.sling.bgservlets.testing|1.0.0",
+  "Commons JSON|Replaced with Commons Johnzon|org.apache.sling.commons.json|2.0.20",
+  "Explorer|Replaced with Composum|org.apache.sling.extensions.explorer|1.0.4",
+  "GWT Integration|Not Maintained|org.apache.sling.extensions.gwt.servlet|3.0.0",
+  "JCR Compiler|Replaced with FS ClassLoader|org.apache.sling.jcr.compiler|2.1.0",
+  "JCR Jackrabbit Server|Replaced with Apache Jackrabbit Oak|org.apache.sling.jcr.jackrabbit.server|2.3.0",
+  "JCR Prefs|Replaced with CA Configs|org.apache.sling.jcr.prefs|1.0.0",
+  "Karaf repoinit|Removed|org.apache.sling.karaf-repoinit|0.2.0",
+  "Launchpad Content|Replaced with Starter Content|org.apache.sling.launchpad.content|2.0.12",
+  "Path-based RTP sample|Not Maintained|org.apache.sling.samples.path-based.rtp|2.0.4",
+  "Scripting JSP Taglib Compat|Superseded by the XSS API bundle|org.apache.sling.scripting.jsp.taglib.compat|1.0.0",
+  "Scripting JST|Not Maintained|org.apache.sling.scripting.jst|2.0.6",
+  "Servlets Compat|Not Maintained|org.apache.sling.servlets.compat|1.0.2",
+  "Testing Sling Mock Jackrabbit|Not Maintained|org.apache.sling.testing.sling-mock-jackrabbit|1.0.0",
+  "Testing Tools|SLING-5703|org.apache.sling.testing.tools|1.0.16",
+  "Thread Dumper|Replaced with Apache Felix Thread Dumper|org.apache.sling.extensions.threaddump|0.2.2"
+]
+
+// ------------------------------------------------------------------------------------------------
+// Utilities
+// ------------------------------------------------------------------------------------------------
+def downloadLink(label, artifact, version, suffix) {
+	def sep = version ? "-" : ""
+	def path = "sling/${artifact}${sep}${version}${suffix}"
+	def digestsBase = "https://www.apache.org/dist/${path}"
+
+	a(href:"[preferred]${path}", label)
+	yield " ("
+	a(href:"${digestsBase}.asc", "asc")
+	yield ", "
+	a(href:"${digestsBase}.sha1", "sha1")
+	yield ")"
+	newLine()
+}
+
+def githubLink(artifact,ghflag) {
+	if(ghflag == 'Y') {
+		artifact = artifact.replaceAll('\\.','-')
+    def url = "https://github.com/apache/sling-${artifact}"
+    // remove duplicate sling- prefix
+    url = url.replaceAll('sling-sling-','sling-')
+		a(href:url, "GitHub")
+		newLine()
+	} else if (ghflag != 'N') {
+		artifact = ghflag.replaceAll('\\.','-')
+    // remove duplicate sling- prefix
+    def url = "https://github.com/apache/sling-${artifact}"
+    url = url.replaceAll('sling-sling-','sling-')
+		a(href:url, "GitHub")
+		newLine()
+	} else {
+		yield "N/A"
+	}
+}
+
+def tableHead(String [] headers) {
+	thead() {
+		tr() {
+			headers.each { header ->
+				th(header)
+			}
+		}
+	}
+
+}
+
+ // ------------------------------------------------------------------------------------------------
+// Downloads page layout
+// ------------------------------------------------------------------------------------------------
+layout 'layout/main.tpl', true,
+        projects: projects,
+        tags : contents {
+            include template: 'tags-brick.tpl'
+        },
+        lastModified: contents {
+            include template : 'lastmodified-brick.tpl'
+        },
+        bodyContents: contents {
+
+            div(class:"row"){
+                div(class:"small-12 columns"){
+                    section(class:"wrap"){
+                        yieldUnescaped content.body
+
+						h2("Sling Application")
+						table(class:"table") {
+							tableHead("Artifact", "Version", "GitHub", "Provides", "Package")
+							tbody() {
+								slingApplication.each { line ->
+									tr() {
+										def data = line.split("\\|")
+										td(data[0])
+										td(data[4])
+										td(){
+											githubLink(data[2], data[5])
+										}
+										td(data[1])
+										def artifact = "${data[2]}-${data[4]}${data[3]}"
+										td(){
+											downloadLink(artifact, artifact, "", "")
+										}
+									}
+								}
+							}
+						}
+
+						h2("Sling IDE Tooling")
+						table(class:"table") {
+							tableHead("Artifact", "Version", "Provides", "Update Site")
+							tbody() {
+								slingIDETooling.each { line ->
+									tr() {
+										def data = line.split("\\|")
+										td(data[0])
+										td(data[2])
+										td(data[3])
+										def artifact = "${data[1]}/${data[2]}"
+										td(){
+											downloadLink("Update site", artifact, "", "")
+										}
+									}
+								}
+							}
+						}
+
+						h2("Sling Components")
+						table(class:"table") {
+							tableHead("Artifact", "Version", "GitHub", "Binary", "Source")
+							tbody() {
+								bundles.each { line ->
+									tr() {
+										def data = line.split("\\|")
+										td(data[0])
+										td(data[2])
+										def artifact = data[1]
+										def version = data[2]
+										def ghflag = data[3]
+										def extension = data[4]
+										td(){
+											githubLink(artifact,ghflag)
+										}
+										td(){
+											downloadLink("Bundle", artifact, version, "." + extension)
+										}
+										td(){
+											downloadLink("Source ZIP", artifact, version, "-source-release.zip")
+										}
+									}
+								}
+							}
+						}
+
+						h2("Maven Plugins")
+						table(class:"table") {
+							tableHead("Artifact", "Version", "GitHub", "Binary", "Source")
+							tbody() {
+								mavenPlugins.each { line ->
+									tr() {
+										def data = line.split("\\|")
+										td(data[0])
+										td(data[2])
+										def artifact = data[1]
+										def version = data[2]
+										def ghflag = data[3]
+										td(){
+											githubLink(artifact, ghflag)
+										}
+										td(){
+											downloadLink("Maven Plugin", artifact, version, ".jar")
+										}
+										td(){
+											downloadLink("Source ZIP", artifact, version, "-source-release.zip")
+										}
+									}
+								}
+							}
+						}
+    
+						h2("Deprecated")
+						table(class:"table") {
+							tableHead("Artifact", "Replacement", "Version", "Binary", "Source")
+							tbody() {
+								deprecated.each { line ->
+									tr() {
+										def data = line.split("\\|")
+										td(data[0])
+										td(data[1])
+										td(data[3])
+										def artifact = data[2]
+										def version = data[3]
+										td(){
+											downloadLink("Bundle", artifact, version, ".jar")
+										}
+										td(){
+											downloadLink("Source ZIP", artifact, version, "-source-release.zip")
+										}
+									}
+								}
+							}
+						}
+                    }
+                }
+            }
+        }
diff --git a/src/test/resources/releases.md b/src/test/resources/releases.md
new file mode 100644
index 0000000..aec47ec
--- /dev/null
+++ b/src/test/resources/releases.md
@@ -0,0 +1,1812 @@
+<!-- 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. -->
+
+title=Releases
+type=page
+status=published
+tags=news
+tableOfContents=false
+~~~~~~
+This is a list of all our releases, available from our [downloads](/downloads.cgi) page.
+
+## February 2019
+
+* DataSource Provider 1.0.4, Resource Collection API 1.0.2, JCR ResourceResolver 3.0.18 (26th)
+* Scripting JSP Tag Library 2.4.0, Scripting JSP Tag Library (Compat) 1.0.0 (18th)
+* Pipes 3.1.0 (15th)
+* Testing OSGi Mock 2.4.6 (14th)
+* Feature Model 1.0.0 and Feature Model IO 1.0.0 (1st)
+
+## January 2019
+
+* XSS Protection API 2.1.0 (29th)
+* Scripting HTL Runtime 1.1.0-1.4.0, Scripting HTL Compiler 1.1.2-1.4.0, Scripting HTL Java Compiler 1.1.2-1.4.0, Scripting HTL Engine 1.1.2-1.4.0, HTL Maven Plugin 1.2.4-1.4.0 (28th)
+* XSS Protection API 2.0.14 (24th)
+* Commons HTML 1.1.0 (22nd)
+* Tenant 1.1.4 (16th)
+* Servlets Post 2.3.28, Sling Maven Plugin 2.4.0 (14th)
+* Engine 2.6.18 (11th)
+
+## December 2018
+
+* JCR Jackrabbit Access Manager 3.0.4, JCR ContentLoader 2.3.0 (20th)
+* API 2.20.0, Servlets Resolver 2.5.2, Servlets Annotations 1.2.4 (18th)
+* Capabilities 0.1.2, Capabilities JCR 0.1.2 (17th)
+* JCR Base 3.0.6 (16th)
+* JCR Oak Server 1.2.2 (16th)
+* Servlet GET 2.1.40 (11th)
+* Testing Sling Mock Oak 2.1.2 (11th)
+* Distribution Core 0.4.0 (10th)
+* Commons ClassLoader 1.4.4, JCR ClassLoader 3.2.4 (8th)
+* Scripting Core 2.0.56 (4th)
+
+## November 2018
+
+* App CMS 0.11.2 (28th)
+* Testing JCR Mock 1.4.2, OSGi Mock 2.4.4 (19th)
+* Servlets Get 2.1.38 and Servlets Resolver 2.4.24 (13th)
+* Installer Vault Package Install Hook 1.0.4 (12th)
+* Distributed Event Admin 1.1.4, App CMS 0.11.0 (9th)
+* Models API 1.3.8 (8th)
+* Capabilities 0.1.0, Capabilities JCR 0.1.0, Scripting HTL Runtime 1.0.0-1.4.0, Scripting HTL Compiler 1.1.0-1.4.0, Scripting HTL Java Compiler 1.1.0-1.4.0, Scripting HTL Engine 1.1.0-1.4.0, HTL JS Use Provider 1.0.28, HTL Models Use Provider 1.0.8, Scripting HTL Testing Content 1.0.14-1.4.0, Scripting HTL Testing 1.0.14-1.4.0, HTL Maven Plugin 1.2.2-1.4.0 (5th)
+
+## October 2018
+
+* Sling 11, Slingstart Archetype 1.0.8, JUnit Tests Teleporter 1.0.18 (23rd)
+* Sling Oak Restrictions 1.0.2 (21nd)
+* Form Based Authentication Handler 1.0.12, Starter Content 1.0.2 (19th)
+* Installer Vault Package Install Hook 1.0.2 (15th)
+* Scripting HTL Engine 1.0.56-1.4.0, Scripting HTL Testing Content 1.0.12-1.4.0, Scripting HTL Testing 1.0.12-1.4.0, HTL Maven Plugin 1.2.0-1.4.0
+* Clam 1.0.0, Commons Clam 1.0.0 (1st)
+
+## September 2018
+
+* Security 1.1.16 (25th)
+* Distribution Core 0.3.4 (21st)
+* Jackrabbit UserManager Support 2.2.8, Jackrabbit JSR-283 Access Control Manager Support 3.0.2 (17th)
+* Pipes 3.0.2 (17th)
+* File Optimization 0.9.2, Clam 1.0.0, Commons Clam 1.0.0, Commons Metrics 1.2.6, Commons Mime 2.2.0, Scripting FreeMarker 1.0.0, Scripting Groovy 1.0.4, Scripting Thymeleaf 2.0.0, Service User Mapper 1.4.2, Settings 1.3.10 (12th)
+* Servlets Annotations 1.1.0 (11th)
+* Commons Log 5.1.10 (10th)
+* Resource Filter 1.0.0 (8th)
+* Context-Aware Configuration API 1.1.2 (5th)
+* Context-Aware Configuration SPI 1.3.4 (5th)
+* Context-Aware Configuration Impl 1.4.14 (5th)
+* Sling Tenant 1.1.2 (5th)
+* Testing Sling Mock 2.3.4 (3rd)
+* Testing OSGi Mock 2.4.2 (3rd)
+
+## August 2018
+
+* Servlets Get 2.1.34, Discovery Oak 1.2.28 (31st)
+* Testing JCR Mock 1.4.0, API 2.18.4, ResourceResolver 1.6.6 (27th)
+* Testing Sling Mock 2.3.2 (27th)
+* Installer Core 3.9.0 (27th)
+* Installer Factory Configuration 1.2.0 (27th)
+* Bundle Resource 2.3.2 (24th)
+* Resource Builder 1.0.4 (21th)
+* Testing JCR Mock 1.3.6 (21th)
+* ResourceResolver Mock 1.1.22 (21th)
+* OSGi Mock 2.4.0 (21th)
+* Sling Mock 2.3.0 (21th)
+* Sling Mock Oak 2.1.0 (21th)
+* Context-Aware Configuration Mock Plugin 1.3.2 (21th)
+* Maven Sling Plugin 2.3.8 (17th)
+* JCR Repository Registration 1.0.6 (14th)
+* Apache Sling JCR Resource Resolver 3.0.16 (14th)
+* Dynamic Include 3.1.2 (14th)
+* Engine 2.16.14 (13th)
+* Parent POM 34 (9th)
+* Scripting HTL Java Compiler 1.0.26-1.4.0 (7th)
+* HTL Maven Plugin 1.1.8-1.4.0 (7th)
+* XSS Protection API 2.0.12 (7th)
+* Servlet Helpers 1.1.8 (6th)
+* Testing JCR Mock 1.3.4 (6th)
+* Testing OSGi Mock 2.3.10 (6th)
+* Testing Sling Mock 2.2.20 (6th)
+* Resource Resolver 1.6.4 (3rd)
+* JCR Resource 3.0.14 (3rd)
+* File Optimization 0.9.0 (1st)
+* App CMS 0.9.0 (1st)
+
+## July 2018
+
+* API 2.18.2 (30th)
+* Sling Content Loader 2.2.6 (6th)
+* XSS Protection API 2.0.8 (3rd)
+
+## June 2018
+
+* Scripting HTL Compiler 1.0.22-1.4.0 (22nd)
+* Scripting HTL Java Compiler 1.0.24-1.4.0 (22nd) 
+* Scripting HTL Engine 1.0.54-1.4.0 (22nd)
+* Scripting HTL Testing Content 1.0.10-1.4.0 (22nd)
+* Scripting HTL Testing 1.0.10-1.4.0 (22nd) 
+* HTL Maven Plugin 1.1.6-1.4.0 (22nd)
+* Scripting HTL REPL 1.0.6 (22nd)
+* Servlet Annotations 1.0.0 (19th)
+* Commons Threads 3.2.18 (19th)
+* SlingStart Maven Plugin 1.8.2 (16th)
+* Feature Model 0.1.2 (2nd)
+
+## May 2018
+
+* Commons Log 5.1.8 (24th) 
+* JSP Taglib 2.3.0 (22nd) 
+* Servlets Post 2.3.26 (22nd)
+* Form Based Authentication 1.0.10 (22nd)
+* Starter Content 1.0.0 (22nd)
+* Starter Startup 1.0.6 (22nd)
+* Servlets Get 2.1.32 (21st)
+* Servlet Helpers 1.1.6 (12th)
+* Commons HTML 1.0.2 (12th)
+* IDE tooling for Eclipse 1.2.2 (9th)
+* Context-Aware Configuration Impl 1.4.12 (7th)
+* Maven Sling Plugin 2.3.6 (7th)
+* Sling Query 4.0.2 (5th)
+* JCR Content Parser 1.2.6 (5th)
+* Feature Model 0.1.0 (1st)
+
+## April 2018
+
+* Commons Log 5.1.6 (24th)
+* File System Resource Provider 2.1.14 (23rd)
+* Installer Heath Checks 2.0.0 (11th)
+
+## March 2018
+
+* Commons Log 5.1.4 (26th)
+* Testing OSGi Mock 2.3.8 (23th)
+* Resource Resolver 1.6.0 (22nd)
+* JCR Resource 3.0.10 (22nd)
+* API 2.18.0 (19th)
+* Security 1.1.12 (19th)
+* XSS Protection API 2.0.6
+* Scripting HTL Engine 1.0.52-1.3.1 (16th)
+* Scripting HTL Testing 1.0.8-1.3.1 (16th)
+* Testing PaxExam 2.0.0 (7th)
+* JCR Oak Server 1.2.0 (7th)
+* Models Impl 1.4.8 (5th)
+* Testing Sling Mock 2.2.18 (1st)
+* Servlet Helpers 1.1.4 (1st)
+
+## February 2018
+
+* Installer Core 3.8.12 (19th)
+* Sling Pipes 2.0.2 (7th)
+* Discovery Base 2.0.8 (6th)
+* Discovery Support 1.0.4 (6th)
+* File System Resource Provider 2.1.12 (5th)
+* Starter 10 (3rd)
+* Launchpad Testing 10 (3rd)
+* Launchpad Testing WAR 10 (3rd)
+* Launchpad Integration Tests 1.0.6 (3rd)
+* Launchpad Test Bundles 0.0.4 (3rd)
+* Launchpad Testing Fragment Bundle 2.0.14 (3rd)
+* Launchpad Testing Services 2.0.14 (3rd)
+* Launchpad Testing Services WAR 2.0.14 (3rd)
+* Archetype Parent 5 (3rd)
+* Bundle Archetype 1.0.6 (3rd)
+* JCRInstall Bundle Archetype 1.0.6 (3rd)
+* Initial Content Archetype 1.0.6 (3rd)
+* Slingstart Archetype 1.0.6 (3rd)
+* Testing PaxExam 1.0.0 (2nd)
+* Scripting HTL Compiler 1.0.20-1.3.1 (1st)
+* Scripting HTL Java Compiler 1.0.22-1.3.1 (1st)
+* Scripting HTL Engine 1.0.48-1.3.1 (1st)
+* Scripting HTL Testing Content 1.0.8-1.3.1 (1st)
+* Scripting HTL Testing 1.0.6-1.3.1 (1st)
+* HTL Maven Plugin 1.1.4-1.3.1 (1st)
+
+## January 2018
+
+* Thread Support 3.2.16 (31st)
+* I18N Support 2.5.12 (29th)
+* Testing Sling Mock 1.9.12 (29th)
+* Testing Sling Mock 2.2.16 (29th)
+* Resource Merger 1.3.8 (20th)
+* Parent 33 (20th)
+* Context-Aware Configuration Impl 1.4.10 (19th)
+* Commons Compiler 2.3.6 (19th)
+* ServiceUser Mapper 1.4.0 (18th)
+* ServiceUser WebConsole 1.0.0 (18th)
+* SlingStart Maven Plugin 1.7.16 (15th)
+* File System Resource Provider 2.1.10 (15th)
+* Resource Merger 1.3.6 (15th)
+* XSS Protection API 2.0.4 (8th)
+
+## December 2017
+
+* Validation Core 1.0.4 (21st)
+* Test Services 1.0.4 (21st)
+* Slingstart Maven Plugin 1.7.14 (21st)
+* Scripting Core 2.0.54 (19th)
+* Scripting JavaScript 3.0.4 (19th)
+* Scripting HTL JS Use Provider 1.0.26 (19th)
+* Scripting HTL Compiler 1.0.16 (19th)
+* Scripting HTL Engine 1.0.46 (19th)
+* Auth Core 1.4.0 (18th)
+* Servlets Resolver 2.4.22 (13th)
+* Scripting HTL Java Compiler 1.0.18 (8th)
+* HTL Maven Plugin 1.1.2 (8th)
+* Commons Metrics 1.2.4 (5th)
+* Scripting JSP 2.3.4 (4th)
+* Commons Log 5.1.0 (1st)
+* Metrics RRD4J 1.0.2 (1st)
+* Engine 2.6.10 (1st)
+* Feature Flags 1.2.2 (1st)
+* i18n 2.5.10 (1st)
+* Security 1.1.10 (1st)
+
+## November 2017
+
+* Event Support 4.2.10 (30th)
+* JCR Registration 1.0.4 (30th)
+* Scripting HTL Java Compiler 1.0.16 (27th)
+* Scripting HTL Engine 1.0.44 (27th)
+* HTL Maven Plugin 1.1.0 (27th)
+* Starter Startup 1.0.4 (27th)
+* Security 1.1.8 (20th)
+* Context-Aware Configuration Impl 1.4.8 (13th)
+* Sling API 2.16.4 (6th)
+* Sling JCR ResourceResolver 3.0.6 (6th)
+* Sling Default GET Servlets 2.1.28 (6th)
+
+## October 2017
+
+* Scripting Core 2.0.50 (23rd)
+* Maven Sling Plugin 2.3.4 (17th)
+* Servlet Helpers 1.0.2 (17th)
+* Servlet Helpers 1.1.2 (17th)
+* JCR Mock 1.3.2 (17th)
+* ResourceResolver Mock 1.1.20 (17th)
+* OSGi Mock 1.9.8 (17th)
+* OSGi Mock 2.3.4 (17th)
+* Sling Mock 1.9.10 (17th)
+* Sling Mock 2.2.14 (17th)
+* Discovery Oak 1.2.22 (17th)
+* Starter Startup 1.0.2 (10th)
+* Context-Aware Configuration Impl 1.4.6 (9th)
+* Query 4.0.0 (4th)
+* Thread Support 3.2.10 (2nd)
+
+## September 2017
+
+* Scripting Core 2.0.48 (29th)
+* Event Support 4.2.8 (29th)
+* SlingStart Maven Plugin 1.7.10 (29th)
+* JUnit Tests Teleporter 1.0.16 (29th)
+* Testing Utilities 2.1.2 (29th)
+* Pipes 1.1.0 (24th)
+* Maven Sling Plugin 2.3.2 (23th)
+* Repoinit JCR version 1.1.6 (22th)
+* Repoinit Parser version 1.2.0 (22th)
+* Context-Aware Configuration Impl 1.4.4 (22th)
+* Scripting HTL Compiler 1.0.14 (20th)
+* Scripting HTL Java Compiler 1.0.14 (20th)
+* Scripting HTL Engine 1.0.42 (20th)
+* Commons Scheduler 2.7.2 (19th)
+* Commons Compiler 2.3.4 (17th)
+* Commons Compiler 2.3.2 (9th)
+* RRD4J metrics reporter 1.0.0 (8th)
+* Scripting JavaScript 3.0.2 (4th)
+* Scripting HTL JS Use Provider 1.0.24 (4th)
+* Scripting HTL Engine 1.0.40 (4th)
+* Parent POM 32 (4th)
+* Java Version Maven Plugin 1.0.0 (4th)
+* Scripting HTL Compiler 1.0.12 (1st)
+* Scripting HTL Java Compiler 1.0.12 (1st)
+* Scripting HTL Engine 1.0.38 (1st)
+
+## August 2017
+
+* Launchpad Base 5.6.8-2.6.24 (31st)
+* Provisioning Model 1.8.4 (28th)
+* Slingstart Maven Plugin 1.7.8 (28th)
+* Servlets Resolver 2.4.14 (28th)
+* Commons Scheduler 2.7.0 (25th)
+* Scripting JSP 2.3.2 (24th)
+* Rewriter 1.2.2 (17th)
+* Launchpad Base 5.6.6-2.6.22 (17th)
+* Event 4.2.6 (16th)
+* Default POST Servlets 2.3.22 (14th)
+* Parent 31 (8th)
+* Security 1.1.6 (7th)
+* Scripting HTL Engine 1.0.38 (7th)
+* Scripting HTL Compiler 1.0.10 (7th)
+* HTL Maven Plugin 1.0.8 (7th)
+* Launchpad Base 5.6.6-2.6.20 (3rd)
+
+## July 2017 
+
+* Resource Resolver 1.5.30 (27th)
+* Service User Mapper 1.3.4 (21th)
+* Resource Resolver 1.5.28 (21th)
+* JCR Base 3.0.4 (21th)
+* JCR Resource 3.0.4 (21th)
+* File System Resource Provider 2.1.8 (18th)
+* File System Resource Provider 1.4.8 (18th)
+* Commons Johnzon 1.1.0 (17th)
+* Discovery Base 2.0.4 (10th)
+* Discovery Oak 1.2.20 (10th)
+* Resource Resolver 1.5.26 (10th)
+
+## June 2017 
+
+* Auth Core 1.4.0 (29th)
+* Event 4.2.4 (26th)
+* JCR Content Parser 1.2.4 (26th)
+* File System Resource Provider 2.1.6 (26th)
+* File System Resource Provider 1.4.6 (26th)
+* Security 1.1.4 (21st)
+* Testing Clients 1.1.4 (20th)
+* Testing Email 1.0.0 (20th)
+* Resource Merger 1.3.4 (20th)
+* Slingstart Archetype (15th)
+* JSPC Maven Plugin 2.1.0 (15th)
+* Slingstart Archetype 1.0.2 (15th)
+* Sling 9
+* Launchpad Testing 9
+* Launchpad Testing WAR version 9 (12th)
+* Launchpad Testing Fragment Bundle 2.0.12 (12th)
+* Launchpad Testing Services 2.0.12 (12th)
+* Launchpad Test Bundles 0.0.2 (12th)
+* Launchpad Testing Services WAR 2.0.12 (12th)
+* Integration Tests 1.0.4 (12th)
+* JUnit Core 1.0.26 (6th)
+* Testing Clients 1.1.0 (6th)
+* JUnit Remote Test Runners 1.0.12 (6th)
+* Tooling Support Install 1.0.4 (6th)
+* Tooling Support Source 1.0.4 (6th)
+* Resource Inventory 1.0.8 (6th)
+* Content Distribution Core 0.2.8 (6th)
+* Testing Sling Mock 2.2.12 (6th)
+* Log Tracer 1.0.4 (6th)
+* Commons Metrics 1.2.2 (6th)
+* JCR Content Parser 1.2.2 (2nd)
+* JCR ContentLoader 2.2.4 (2nd)
+* File System Resource Provider 2.1.4 (2nd)
+* File System Resource Provider 1.4.4 (2nd)
+* Maven Sling Plugin 2.3.0 (2nd)
+* SLF4J Implementation (Logback) 5.0.2 (1st)
+
+## May 2017
+
+* JCR Resource 3.0.2 (31st)
+* JCR Content Parser 1.2.0 (29th)
+* File System Resource Provider 2.1.2 (29th)
+* File System Resource Provider 1.4.2 (29th)
+* Maven Sling Plugin 2.2.2 (29th)
+* Context-Aware Configuration SPI 1.3.2 (29th)
+* Context-Aware Configuration Impl 1.4.2 (29th)
+* Context-Aware Configuration Mock Plugin 1.3.0 (29th)
+* Launchpad Content 2.0.12 (29th)
+* Servlet Post 2.3.20 (28th)
+* JCR Contentloader 2.2.2 (28th)
+* Launchpad Base 5.6.4-2.6.18 (28th)
+* Service User Mapper 1.3.2 (22nd)
+* JCR Base Bundle 3.0.2 (22nd)
+* Web Console Branding 1.0.2 (22nd)
+* Engine Implementation 2.6.8 (22nd)
+* Servlets Get 2.1.26 (19th)
+* Servlets Post 2.3.18 (19th)
+* Commons Scheduler 2.6.2 (18th)
+* Installer Core 3.8.10 (18th)
+* Models Impl 1.4.2 (15th)
+* Testing OSGi Mock 2.3.2 (15th)
+* OSGi Mock 1.9.6 (15th)
+* Sling Mock 2.2.10 (15th)
+* Sling Mock 1.9.8 (15th)
+* Auth Core 1.3.24 (15th)
+* JUnit Scriptable Tests Provider 1.0.12 (12th)
+* XSS Protection Compat Bundle 1.1.0 (11th)
+* Scripting JavaScript 3.0.0 (11th)
+* Junit Core 1.0.24 (11th)
+* Junit Teleporter 1.0.14 (11th)
+* Testing Tools 1.0.16 (11th)
+* Adapter 2.1.10 (11th)
+* Servlets Get 2.1.24 (11th)
+* Servlets Post 2.3.16 (11th)
+* JCR Jackrabbit Access Manager 3.0.0 (11th)
+* Healthcheck API 1.0.0 (11th)
+* Healthcheck Core 1.2.8 (11th)
+* Discovery Commons 1.0.20 (11th)
+* Discovery Base 2.0.0 (11th)
+* Discovery Impl 1.2.12 (11th)
+* Discovery Oak 1.2.18 (11th)
+* Scripting HTL Java Compiler 1.0.10 (9th)
+* Scripting HTL Engine 1.0.34 (9th)
+* Scripting HTL JS Use Provider 1.0.22 (9th)
+* Servlets Resolver 2.4.12 (9th)
+* Installer Core 3.8.8 (8th)
+* JCR ContentLoader 2.2.0 (8th)
+* Pax Exam Utilities 1.0.4 (8th)
+* File System Resource Provider 2.1.0 (8th)
+* File System Resource Provider 1.4.0 (8th)
+* JCR Content Parser 1.1.0 (8th)
+* XSS Protection Bundle 2.0.0 (5th)
+* XSS Protection Compat Bundle 1.0.0 (5th)
+* Sling Resource Resolver 1.5.24 (5th)
+* Context-Aware Configuration Impl 1.4.0 (5th)
+* Context-Aware Configuration Mock Plugin 1.2.0 (5th)
+* Testing JCR Mock 1.3.0 (5th)
+* OSGi Mock 2.3.0 (5th)
+* Sling Mock 2.2.8 (5th)
+* Models API 1.3.4 (1st)
+* Models Implementation 1.4.0 (1st)
+* JCR Installer Provider 3.1.26 (1st)
+* JCR Resource 3.0.0 (1st)
+* Sling Slingstart Maven Plugin 1.7.4 (1st)
+* Service User Mapper 1.3.0 (1st)
+* Scripting API 2.2.0
+* Tooling Support Source 1.0.2 (1th)
+
+## April 2017
+
+* Commons Johnzon 1.0.0 (29th)
+* Event API 1.0.0 (19th)
+* Validation API (12th)
+* Validation Core 1.0.0 (12th)
+* Testing ResourceResolver Mock 1.1.18 (4th)
+* JCR Jackrabbit User Manager 2.2.6 (3rd)
+
+## March 2017
+
+* Resource Resolver 1.5.22 (30th)
+* Service User Mapper 1.2.6 (30th)
+* Testing Sling Mock 2.2.6 (30th)
+* Sling Mock 1.9.6 (30th)
+* Sling Mock Oak 1.0.2 (30th)
+* Context-Aware Configuration Implementation 1.3.2 (30th)
+* File System Resource Provider 2.0.0 (30th)
+* File System Resource Provider 1.3.0 (30th)
+* CAConfig SPI 1.3.0 (24th)
+* CAConfig Impl 1.3.0 (24th)
+* CAConfig Mock Plugin 1.1.0 (24th)
+* Maven Sling Plugin 2.2.0 (24th)
+* JCR Content Parser 1.0.0 (23th)
+* Testing OSGi Mock 2.2.4 (23th)
+* Testing OSGi Mock 1.9.4 (23th)
+* Commons JSON 2.0.20 (20th)
+* Karaf repoinit 0.2.0 (20th)
+* Scripting JSP API Wrapper 1.0.0 (20th)
+* Scripting JSP EL Wrapper 1.0.0 (20th)
+* Scripting JSP 2.3.0 (20th)
+* Testing PaxExam 0.0.4 (20th)
+* JCR Oak Server 1.1.4 (20th)
+* Scripting Thymeleaf 1.1.0 (20th)
+* Resource Presence 0.0.2 (20th)
+* Resource Resolver 1.5.20 (13th)
+* JCR Repoinit 1.1.4 (13th)
+* i18n 2.5.8 (8th)
+* JCR Installer 3.1.24 (8th)
+* XSS 1.0.18 (8th)
+* HTL JavaScript Use Provider 1.0.20 (8th)
+* Scripting Core 2.0.46 (8th)
+* Health Check Core 1.2.6 (8th)
+* Event 4.2.2 (8th)
+* Distributed Event Admin 1.1.2 (8th)
+* Content Distribution Core 0.2.6 (2nd)
+* Installer Health Checks 1.0.0 (2nd)
+* Parent 30 (Mar 6th) (2nd)
+* Scripting HTL Compiler 1.0.8 (2nd)
+* Scripting HTL Engine 1.0.32 (2nd)
+* Commons Classloader 1.4.0 (1st)
+* Commons File System Classloader 1.0.6 (1st)
+
+## February 2017
+
+* Resource Resolver 1.5.14 (28th)
+* JUnit Tests Teleporter 1.0.12 (28th)
+* Slingstart Maven Plugin 1.7.2 (28th)
+* Installer Core 3.8.6 (20th)
+* Content Distribution Core 0.2.4 (20th)
+* Content Distribution Core 0.2.0 (14th)
+* Servlets GET 2.1.22 (12th)
+* Resource Merger 1.3.2 (8th)
+* DavEx Access to repositories 1.3.8 (8th)
+* Simple WebDAV Access to repositories 2.3.8 (8th)
+* Launchpad Base 2.6.16 (5th)
+* Servlets GET 2.1.20 (3rd)
+* Commons ClassLoader 1.3.8 (3rd)
+
+## January 2017 
+
+* Resource Inventory 1.0.6 (31st)
+* Installer Core 3.8.2 (31st)
+* JSP 2.2.6 (30th)
+* Auth Core 1.3.24 (30th)
+* File System Resource Provider 1.2.2 (23th)
+* Maven Sling Plugin 2.1.10 (23th)
+* Resource Resolver 1.5.12 (23th)
+* Tenant 1.1.0 (17th)
+* Scripting HTL Compiler 1.0.6 (17th)
+* Scripting HTL Java Compiler 1.0.8 (17th)
+* Scripting HTL Engine 1.0.30 (17th)
+* HTL Maven Plugin 1.0.6 (17th)
+* JSP 2.2.4 (16th)
+* Resource Resolver 1.5.10 (13th)
+* JUnit Core 1.0.23 (11th)
+* JUnit Tests Teleporter 1.0.10 (11th)
+* Testing Clients 1.0.1 (11th)
+* Server Setup Tools 1.0.1 (11th)
+* Testing Rules 1.0.1 (11th)
+* Scripting HTL Compiler 1.0.4 (9th)
+* Scripting HTL Java Compiler 1.0.6 (9th)
+* Scripting HTL Engine 1.0.28 (9th)
+* Scripting HTL Models Use Provider 1.0.6 (9th)
+* Scripting HTL JS Use Provider 1.0.18 (9th)
+* HTL Maven Plugin 1.0.4 (9th)
+* Testing Hamcrest 1.0.2 (7th)
+
+## December
+
+* Testing ResourceResolver Mock 1.1.16 (27th)
+* Servlets Resolver 2.4.10 (24th)
+* File System Classloader 1.0.4 (24th)
+* Installer Console 1.0.2 (24th)
+* Resource Resolver 1.5.8 (23rd)
+* JCR Resource 2.9.2 (23rd)
+* Slingshot Sample 0.8.0 (23rd)
+* Sling Mock 2.2.4 (22th)
+* Sling Mock 1.9.4 (22th)
+* JUnit Core 1.0.22 (22th)
+* Models API 1.3.2 (22th)
+* Models Impl 1.3.8 (22th)
+* Service User Mapper 1.2.4 (22th)
+* JCR Base 3.0.0 (20th)
+* Dynamic Include 3.0.0 (20th)
+* i18n 2.5.6 (19th)
+* JCR RepoInit module 1.1.2 (19th)
+* API 2.16.2 (18th)
+* Slingstart Maven Plugin 1.7.0 (18th)
+* JCR Resource 2.9.0 (16th)
+* Scripting JSP 2.2.2 (16th)
+* Scripting Java 2.1.2 (16th)
+* Provisioning Model 1.8.0 (16th)
+* Testing JCR Mock 1.2.0 (16th)
+* OSGi Mock 2.2.2 (16th)
+* OSGi Mock 1.9.2 (16th)
+* Sling Mock 2.2.2 (16th)
+* Sling Mock 1.9.2 (16th)
+* JUnit Core 1.0.20 (16th)
+* Context-Aware Configuration API 1.1.0 (16th)
+* Context-Aware Configuration SPI 1.2.0 (16th)
+* Context-Aware Configuration Impl 1.2.0 (16th)
+* Context-Aware Configuration bnd Plugin 1.0.2 (16th)
+* Context-Aware Configuration Mock Plugin 1.0.0 (16th)
+* Scripting API 2.1.12 (15th)
+* Scripting Core 2.0.44 (15th)
+* JCR Installer 3.1.22 (13th)
+* Commons Metrics 1.2.0 (13th)
+* OSGi Mock 2.2.0 (8th)
+* OSGi Mock 1.9.0 (8th)
+* Sling Mock 2.2.0 (8th)
+* Sling Mock 1.9.0 (8th)
+* API 2.16.0 (6th)
+* Resource Resolver 1.5.6 (6th)
+* Servlets Resolver 2.4.8 (6th)
+* JCR Resource 2.8.4 (6th)
+
+## November 2016
+
+* Models bnd Plugin 1.0.0 (24th)
+* Commons ClassLoader 1.3.6 (24th)
+* JCR Resource 2.8.2 (22th)
+* Security 1.1.2 (22th)
+* Models Implementation 1.3.4 (22th)
+* Models Jackson Exporter 1.0.4 (22th)
+* Resource Resolver 1.5.4 (21st)
+* Discovery Base 1.1.6 (21st)
+* Discovery Commons 1.0.18 (21st)
+* Discovery Impl 1.2.10 (21st)
+* Discovery Oak 1.2.16 (21st)
+* Auth Core 1.3.22 (15th)
+* JCR Base 2.4.2 (14th)
+* Provisioning Model 1.7.0 (13th)
+* Slingstart Maven Plugin 1.6.0 (13th)
+* JCR Repoinit 1.1.0 (12th)
+* Repoinit Parser 1.1.0 (12th)
+* Slingstart Maven Plugin 1.5.0 (11th)
+* Installer Core 3.8.0 (10th)
+* Context-Aware Configuration SPI 1.1.0 (10th)
+* Context-Aware Configuration Impl 1.1.0 (10th)
+* Resource Resolver 1.5.2 (8th)
+* Provisioning Model 1.6.0 (8th)
+* HTL Maven Plugin 1.0.2 (8th)
+* Commons Mime 2.1.10 (7th)
+* Web Console Security Provider 1.2.0 (6th)
+* Models API 1.3.0 (3th)
+* Models Implementation 1.3.0 (3th)
+* Models Jackson Exporter 1.0.0 (3th)
+
+## October 2016
+
+* Launchpad Base 2.6.14 (31st)
+* Provisioning Model 1.5.0 (31st)
+* XSS Protection API 1.0.16 (31st)
+* Commons Scheduler 2.5.2 (27th)
+* API 2.15.0 (25st)
+* Resource Resolver 1.5.0 (25st)
+* Installer Core 3.7.0 (25st)
+* Scripting HTL Engine 1.0.26 (24th)
+* Commons Log 5.0.0 (24th)
+* Log WebConsole 1.0.0 (24th)
+* DataSource Provider 1.0.2 (24th)
+* Log Tracer 1.0.2 (24th)
+* Servlets Resolver 2.4.6 (22nd)
+* Scripting JSP 2.2.0 (20th)
+* Engine 2.6.6 (20th)
+* Scripting Java 2.1.0 (20th)
+* Scripting HTL JS Use Provider 1.0.16 (20th)
+* Context-Aware Configuration API 1.0.0
+* Context-Aware Configuration SPI 1.0.0 (18th)
+* Context-Aware Configuration Impl 1.0.0 (18th)
+* Context-Aware Configuration bnd Plugin 1.0.0 (18th)
+* Resource Builder 1.0.2 (18th)
+* Testing Hamcrest 1.0.0 (18th)
+* Scripting Core 2.0.40 (17th)
+* Scripting HTL Java Compiler 1.0.4 (17th)
+* Scripting HTL Engine 1.0.24 (17th)
+* Launchpad Base 2.6.12 (16th)
+* Event 3.4.0 (15th)
+* Distributed Eventing 1.1.0 (15th)
+* Rewriter 1.2.0 (15th)
+* Pipes 0.0.10 (14th)
+* Scripting HTL Compiler 1.0.2 (13th)
+* Scripting HTL Java Compiler 1.0.2 (13th)
+* Scripting HTL Engine 1.0.22 (13th)
+* Scripting HTL JS Use Provider 1.0.14 (13th)
+* Scripting HTL Models Use Provider 1.0.4 (13th)
+* Testing Sling Mock 2.1.2 (10th)
+* i18n 2.5.4 (8th)
+* Oak Restrictions 1.0.0 (3rd)
+
+## September 2016
+
+* Discovery Commons 1.0.16 (29th)
+* Discovery Oak 1.2.14 (29th)
+* Testing Clients 1.0.0 (16th)
+* Server Setup Tools 1.0.0 (16th)
+* Testing Rules 1.0.0 (16th)
+* Auth Core 1.3.20 (19th)
+* JUnit Core 1.0.18 (19th)
+* JUnit Tests Teleporter 1.0.8 (19th)
+* Testing Logging Mock 2.0.0 (19th)
+* JCR Mock 1.1.16 (19th)
+* OSGi Mock 1.8.0 (19th)
+* OSGi Mock 2.1.0 (19th)
+* Sling Mock 1.8.0 (19th)
+* Sling Mock 2.1.0 (19th)
+* Sling Mock Oak 2.0.2 (19th)
+* Resource Builder 1.0.0 (19th)
+* Servlet Helpers 1.1.0 (19th)
+* Scripting HTL Compiler 1.0.0 (8th)
+* Scripting HTL Java Compiler 1.0.0 (8th)
+* Scripting HTL Engine 1.0.20 (8th)
+* Scripting HTL JS Use Provider 1.0.12 (8th)
+* Scripting HTL Models Use Provider 1.0.2 (8th)
+* Scripting HTL REPL 1.0.4 (8th)
+* HTL Maven Plugin 1.0.0 (8th)
+* Discovery Oak 1.2.10 (5th)
+
+## August 2016
+
+* RepoInit Parser 1.0.4 (29th)
+* RepoInit JCR module 1.0.2 (29th)
+* XSS Protection API 1.0.14 (29th)
+* Auth Core 1.3.18 (29th)
+* Testing Tools 1.0.14 (29th)
+* Engine 2.6.2 (26th)
+* Commons Testing 2.1.0 (26th)
+* API 2.14.2 (26th)
+* Resource Resolver 1.4.18 (26th)
+* Servlets Get 2.1.18 (25th)
+* Engine 2.6.0 (22nd)
+* Feature Flags 1.2.0 (22nd)
+* Background Servlets 1.0.8 (19th)
+* XSS Protection API 1.0.12 (19th)
+* I18n 2.5.2 (18th)
+* Hypermedia API client-side tools 1.0.0 (18th)
+* Testing PaxExam 0.0.2 (17th)
+* JCR Oak Server 1.1.0 (17th)
+* Security 1.1.0 (15th)
+* i18n 2.5.0 (8th)
+* i18n 2.4.10 (8th)
+* Engine 2.5.0 (5th)
+* i18n 2.4.8 (5th)
+* Feature Flags 1.1.0 (5th)
+* Event Support 4.1.0 (1st)
+
+## July 2016
+
+* Resource Resolver 1.4.16 (25th)
+* Launchpad Testing Services 2.0.10 (25th)
+* Launchpad Testing Services WAR 2.0.10 (25th)
+* Launchpad Integration Tests 1.0.2 (25th)
+* API 2.14.0 (25th)
+* Commons Scheduler 2.5.0 (24th)
+* Resource Resolver 1.4.14 (21st)
+* Discovery Base 1.1.4 (17th)
+* Discovery Impl 1.2.8 (17th)
+* Discovery Oak 1.2.8 (17th)
+* Testing Sling Mock 1.7.0 (15th)
+* Sling Mock 2.0.0 (15th)
+* Sling Mock Oak 2.0.0 (15th)
+* Commons Testing 2.0.26 (13th)
+* Scripting Core 2.0.38 (13th)
+* Servlets Resolver 2.4.4 (13th)
+* Repoinit Parser 1.0.2 (11th)
+* Repoinit JCR 1.0.0 (11th)
+* API 2.12.0 (9th)
+* Scripting Thymeleaf 1.0.0 (4th)
+
+## June 2016
+
+* Auth Core 1.3.16 (29th)
+* Adapter Manager 2.1.8 (28th)
+* Repository API Bundle 2.4.0 (27th)
+* JCR Base Bundle 2.4.0 (27th)
+* Rewriter 1.1.4 (26th)
+* JCR Resource 2.8.0 (22nd)
+* Scripting JavaScript 2.0.30 (22nd)
+* Testing JCR Mock 1.1.14 (13th)
+* OSGi Mock 2.0.4 (13th)
+* ResourceResolver Mock 1.1.14 (13th)
+* Provisioning Model 1.4.4
+* Slingstart Maven Plugin 1.4.4 (10th)
+* Servlets Post 2.3.12 (1st)
+
+## May 2016
+
+* JSON Library 2.0.16 (27th)
+
+## April 2016
+
+* Discovery API 1.0.4 (29th)
+* Log Tracer version 1.0.0 (25th)
+* Resource Resolver 1.4.12 (25th)
+* Scripting JSP-Taglib version 2.2.6 (21st)
+* Auth Core 1.3.14 (12th)
+* Servlets Post (10th)
+* Resource Resolver (7th)
+* Event 4.0.2 (4th)
+
+## March 2016
+
+* Discovery Commons 1.0.12 (29th)
+* Scripting Sightly Engine 1.0.18 (18th)
+* Apache Maven Sling Plugin 2.1.8 (16th)
+* IDE Tooling for Eclipse 1.1.0 (14th)
+* Resource Resolver 1.4.8 (11th)
+* JCR Resource 2.7.4 (11th)
+* Installer Core 3.6.8 (11th)
+* Health Check Core 1.2.4 ( 8th)
+* Health Checks Annotations 1.0.4 ( 8th)
+* JCR Davex 1.3.2 ( 8th)
+* JCR Webdav 2.3.4 ( 8th)
+* Scripting Sightly Engine 1.0.16 (5th)
+* Tooling Support Source 1.0.0 (3rd)
+* Background Servlets Engine 1.0.6 (2nd)
+* Background Servlets Integration Test 1.0.0 (2nd)
+
+## February 2016
+
+* NoSQL Generic Resource Provider 1.1.0 (27th)
+* Couchbase Client 1.0.2 (27th)
+* Couchbase Resource Provider 1.1.0 (27th)
+* MongoDB Resource Provider 1.1.0 (27th)
+* Resource Resolver 1.4.4 (26th)
+* Scripting Sightly Engine 1.0.14 (26th)
+* JCR Base 2.3.2 (23rd)
+* Internationalization Support (I18N) 2.4.6 (22nd)
+* Resource Resolver 1.4.2 (19th)
+* JCR Resource 2.7.2 (19th)
+* Servlets Resolver 2.4.2 (19th)
+* JCR Installer 3.1.18 (15th)
+* Resource Merger 1.3.0 (14th)
+* Scripting Core 2.0.36 (12th)
+* Slingstart Maven Plugin 1.4.2 (11th)
+* API 2.11.0 (8th)
+* Resource Resolver 1.4.0 (8th)
+* JCR Resource 2.7.0 (8th)
+* Servlets Resolver 2.4.0 (8th)
+* Testing OSGi Mock 1.7.2 (8th)
+* OSGi Mock 2.0.2 (8th)
+* JCR Mock 1.1.12 (8th)
+* Sling Mock 1.6.2 (8th)
+* ResourceResolver Mock 1.1.12 (8th)
+* Servlet Helpers 1.0.0 (8th)
+* Discovery Oak 1.2.6 (8th)
+* HApi 1.0.0 (5th) (2nd)
+* XSS Protection API 1.0.8 (2nd)
+* Scripting Sightly Engine 1.0.12 (2nd)
+* Discovery Commons 1.0.10 (1st)
+* Discovery Oak 1.2.4 (1st)
+* Testing Tools 1.0.12 (1st)
+
+## January 2016
+
+* Thread Support 3.2.6 (25th)
+* Models Impl 1.2.6 (23st)
+* Testing Utilities 2.0.24 (21st)
+* Commons Metrics 1.0.0 (15th)
+* Scripting Sightly Engine 1.0.10 (12th)
+* Scripting Groovy 1.0.2 (12th)
+* Slingstart Maven Plugin 1.4.0 (8th)
+* Commons OSGi 2.4.0 (8th)
+* Engine Implementation 2.4.6 (8th)
+* Scripting JavaScript 2.0.28 (8th)
+* JUnit Tests Teleporter 1.0.6 (3rd)
+* JUnit Core 1.0.16 (3rd)
+* Settings 1.3.8 (3rd)
+* Launchpad Base 2.6.10 (3rd)
+* Scripting JSP 2.1.8 (3rd)
+* Commons Threads 3.2.4 (3rd)
+* Discovery Standalone 1.0.2 (3rd)
+* Parent POM 26 (3rd
+
+## December 2015
+
+* Provisioning Model 1.4.2 (28th)
+* Commons Scheduler 2.4.14 (21st)
+* Servlets GET 2.1.14 (21st)
+* Scripting Java 2.0.14 (20th)
+* Models Impl 1.2.4 (14th)
+* Sling Testing OSGi Mock 2.0.0 (14th)
+* Commons Scheduler 2.4.12 (14th)
+* Launchpad Base 2.6.8 (11th)
+* Event 4.0.0 (1st)
+
+## November 2015
+
+* Discovery Commons 1.0.6 (30th)
+* Discovery Base 1.1.2 (30th)
+* Discovery Oak 1.2.0 (30th)
+* iscovery Impl 1.2.6 (30th)
+* Thread Support 3.2.2 ( 29th)
+* Background Servlets Engine 1.0.2 (23rd)
+* JUnit Core 1.0.14 (23rd)
+* JUnit Tests Teleporter 1.0.4 (23rd)
+* Security 1.0.18 (20th)
+* Discovery Commons 1.0.4 (16th)
+* Discovery Base 1.1.0 (16th)
+* Discovery Oak 1.1.0 (16th)
+* Discovery Impl 1.2.2 (16th)
+* Commons Testing 2.0.22 (12th)
+* Commons JSON 2.0.16 (12th)
+* Maven Sling Plugin 2.1.6 (12th)
+* Testing OSGi Mock 1.7.0 (9th)
+* Discovery Commons 1.0.2 (5th)
+* Discovery Base 1.0.2 (5th)
+* Discovery Oak 1.0.2 (5th)
+* Discovery Impl 1.2.0 (5th)
+* IDE Tooling 1.0.10 (9th)
+* Discovery Commons 1.0.0 (2nd)
+* Discovery Base 1.0.0 (2nd)
+* Discovery Oak 1.0.0 (2nd)
+
+## October 2015
+
+* Rewriter 1.1.2 (27th)
+* Launchpad Base 2.6.6 (26th)
+* Event 3.7.6 (26th)
+* Provisioning Model 1.4.0 (26th)
+* Maven Launchpad Plugin 2.3.4 (26th)
+* Archetype Parent version 4 (19th)
+* Bundle Archetype version 1.0.4 (19th)
+* JCRInstall Bundle Archetype 1.0.4 (19th)
+* Initial Content Archetype 1.0.4 (19th)
+* Servlet Archetype 1.0.4 (19th)
+* Slingstart Archetype 1.0.0 (19th)
+* Auth Core 1.3.12 (18th)
+* Sling 8 (16th)
+* Maven Plugin for Supporting Bundle Development 2.1.2 (15th)
+* Auth Forms 1.0.8 (13)
+* Scripting Sightly Engine 1.0.6 (12th)
+* Scripting Sightly Models Use Provider 1.0.0 (12th)
+* Scripting Sightly REPL 1.0.2 (12th)
+* Scripting JavaScript 2.0.26 (12th)
+* XSS Protection API 1.0.6 (12th)
+* Oak Repository Server 1.0.0 (12th)
+* Adapter Manager Implementation 2.1.6 (12th)
+* Jackrabbit UserManager Support 2.2.4 (12th)
+* Simple WebDAV Access to repositories 2.3.2 (12th)
+* OSGi LogService Implementation 1.0.6 (12th)
+* Engine Implementation 2.4.4 (12th)
+* JSON Library 2.0.12 (12th)
+* Testing OSGi Mock 1.6.0 (9th)
+* Sling Mock 1.6.0 (9th)
+* Sling Mock Jackrabbit 1.0.0 (9th)
+* Sling Mock Oak 1.0.0 (9th)
+* Eclipse IDE 1.0.8 (8th)
+* Servlets Resolver 2.3.8 (5th)
+* Parent POM 25 (5th)
+
+## September 2015
+
+* Discovery Impl 1.1.8 (30th)
+* Distributed Event Admin 1.0.4 (30th)
+* JUnit Core 1.0.12 (28th)
+* JUnit Tests Teleporter 1.0.2 (28th)
+* NoSQL Generic Resource Provider 1.0.0 (21th)
+* NoSQL Couchbase Client 1.0.0 (21th)
+* NoSQL Couchbase Resource Provider 1.0.0 (21th)
+* NoSQL MongoDB Resource Provider 1.0.0 (21th)
+* Testing ResourceResolver Mock 1.1.10 (15th)
+* Rewriter 1.1.0 (15th)
+* Models API 1.2.2 (15th)
+* Models Impl 1.2.2 (15th)
+* Testing Sling Mock 1.5.0 (10th)
+* JCR Mock 1.1.10 (10th)
+* Scripting Sightly JS Use Provider 1.0.10 (7th)
+* Scripting Sightly Engine 1.0.4 (7th)
+* Scripting JavaScript 2.0.24 (7th)
+* Slingstart Maven Plugin 1.3.6 (7th)
+* Launchpad Base 5.2.0-2.6.4 (6th)
+* Security 1.0.16 (4th)
+
+## August 2015
+
+* JCR Resource 2.5.6 (31st)
+* Slingstart Maven Plugin 1.3.4 (31st)
+* Security 1.0.14 (31st)
+* Security 1.0.12 (25th)
+* Testing OSGi Mock 1.5.0 (24th)
+* ResourceResolver Mock 1.1.8 (24th)
+* Web Console Security Provider 1.1.6 (21st)
+* Slingstart Maven Plugin 1.3.2 (20th)
+* JCR API 2.3.0 (17th)
+* JCR Base 2.3.0 (17th)
+* JCR Jackrabbit Server 2.3.0 (17th)
+* JCR Davex 1.3.0 (17th)
+* JCR Webdav 2.3.0 (17th)
+* Commons Scheduler 2.4.10 (14th)
+* i18n 2.4.4 (13th)
+* Scripting Core 2.0.34 (13th)
+* Scripting JavaScript 2.0.22 (13th)
+* Scripting Sightly JS Use Provider 1.0.8 (13th)
+* Commons Log 4.0.6 (11th)
+* Slingstart Maven Plugin 1.3.0 (7th)
+* Provisioning Model 1.3.0 (7th)
+* XSS Protection Bundle 1.0.4 (3rd)
+
+## July 2015
+
+* Scripting Core 2.0.32 (26th)
+* Event 3.7.4 (24th)
+* Distributed Event Admin 1.0.2 (24th)
+* Scripting API 2.1.8 (21st)
+* Scripting Core 2.0.30 (21st)
+* Scripting JavaScript 2.0.20 (21st)
+* Scripting Sightly JS Use Provider 1.0.6 (21st)
+* Resource Merger 1.2.10 (21st)
+* JCR Resource 2.5.4 (17th)
+* Servlets GET 2.1.12 (17th)
+* Event 3.7.2 (13th)
+* Authentication Service 1.3.10 (13th)
+* Scripting Thymeleaf 0.0.6 (10th)
+* Content Detection Support 1.0.2 (9th)
+* Parent POM 24 (6th)
+* Authentication Service 1.3.8 (6th)
+* Scripting JavaScript Support 2.0.18 (2nd)
+* Feature Flags 1.0.2 (1st)
+
+## June 2015
+
+* Event 3.7.0 (30th)
+* Models API 1.2.0 (27th)
+* Models Impl 1.2.0 (27th)
+* Testing Sling Mock 1.4.0 (27th)
+* OSGi Mock 1.4.0 (27th)
+* JCR Mock 1.1.8 (27th)
+* JCR Resource Resolver 2.5.2 (26th)
+* Parent 23 (25th)
+* Log Tracer 1.0.2 (22th)
+* Commons FileSystem ClassLoader 1.0.2 (19th)
+* Default POST Servlets 2.3.8 (15th)
+* Provisioning Model 1.2.0
+* Installer Core 3.6.6 (13th) (13th)
+* Commons Scheduler 2.4.8 (13th)
+* Slingstart Maven Plugin 1.2.0 (13th)
+* Background Servlets Engine 1.0.0 (2nd)
+
+## May 2015
+
+* Testing Sling Mock 1.3.0 (26th)
+* OSGi Mock 1.3.0 (26th)
+* JCR Mock 1.1.6 (26th)
+* ResourceResolver Mock 1.1.6 (26th)
+* Logging Mock 1.0.0 (26th)
+* Commons OSGi 2.3.0 (26th)
+* Installer Core 3.6.4 (2nd)
+
+## April 2015
+
+* Health Check Core 1.2.2 (30th)
+* Launchpad Base 5.0.0-2.6.0 (30th)
+* Discovery Impl 1.1.2 (28th)
+* Testing Tools 1.0.10 (24th)
+* i18n 2.4.2 (23rd)
+* Slingstart Maven Plugin 1.1.0 (23rd)
+* Scripting JavaScript Support 2.0.16 (20th)
+* Engine Implementation 2.4.2 (20th)
+* File Installer 1.1.0 (20th)
+* Resource Inventory 1.0.4 (20th)
+* Scripting Sightly JS Provider 1.0.4 (16th)
+* Scripting Sightly Testing Content 1.0.4 (16th)
+* Scripting Sightly Testing 1.0.4 (16th)
+* JCR Installer 3.1.16 (13th)
+* Commons Testing 2.0.18 (13th)
+* Security 1.0.10 (April 8th)
+* Scripting Sightly 1.0.2 (7th)
+* Scripting Sightly Testing Content 1.0.2 (7th)
+* Scripting Sightly Testing 1.0.2 (7th)
+* Event 3.6.0 (7th)
+* Commons Log 4.0.2 (7th)
+* Commons Log Service 1.0.4 (7th)
+* Performance Test Utilities 1.0.2 (2nd)
+
+## March 2015
+
+* Scripting Sightly 1.0.0 (30th)
+* Scripting Sightly JavaScript Use Provider 1.0.0 (30th)
+* Scripting Sightly REPL 1.0.0 (30th)
+* Scripting Sightly Testing Content 1.0.0 (30th)
+* XSS Protection Bundle 1.0.2 (30th)
+* Resource Resolver 1.2.4 (24th)
+* XSS Protection Bundle 1.0.0 (20th)
+* JCR Installer 3.1.14 (16th)
+* Resource Resolver 1.2.2 (16th)
+* Service User Mapper 1.2.0 (16th)
+* Launchpad Base 4.6.1-2.5.8 (12th)
+* Event 3.5.4 (12th)
+* Settings 1.3.6 (12th)
+* Resource Merger 1.2.8 (12th)
+* Slingstart Maven Plugin 1.0.4 (12th)
+* Provisioning Model 1.1.0 (12th)
+* Query 3.0.0 (11th)
+* JCR Resource 2.5.0 (5th)
+* Resource Resolver 1.2.0 (5th)
+* Commons Scheduler 2.4.6 (4th)
+* IDE Tooling 1.0.6 (2nd)
+* Service User Mapper 1.1.0 (2nd)
+* Health Check Core 1.2.0 (2nd)
+* Health Check Web Console 1.1.2 (2nd)
+
+## February 2015
+
+* URL Rewriter 0.0.2 (27th)
+* Security 1.0.8 (26th)
+* Testing Sling Mock 1.2.0 (26th)
+* OSGi Mock 1.2.0 (26th)
+* JCR Mock 1.1.4 (26th)
+* ResourceResolver Mock 1.1.4 (26th)
+* API 2.9.0 (February 24th)
+* Tooling Support Install 1.0.2 (23rd)
+* Engine Implementation 2.4.0 (19th)
+* Auth Core 1.3.6 (16th)
+* Resource Resolver 1.1.14 (16th)
+* Eventing 3.5.2 (16th)
+* Resource Merger 1.2.6 (16th)
+* Installer Factory Configuration 1.1.2 (16th)
+* Resource Resolver 1.1.12 (2nd)
+
+## January 2015
+
+* Testing JCR Mock 1.1.2 (28th)
+* ResourceResolver Mock 1.1.2 (28th)
+* Sling Mock 1.1.2 (28th)
+* Sling Mock Jackrabbit 0.1.2 (28th)
+* JCR Resource 2.4.4 (26th)
+* Launchpad Base 4.6.0-2.5.6 (26th)
+* JCR Resource 2.4.2 (23rd)
+* Resource Resolver 1.1.10  (19th)
+* DataSource 1.0.0 (19th)
+* Launchpad Base 4.6.0-2.5.4 (19th)
+* Commons JSON 2.0.10 (17th)
+* Installer Core 3.6.2 (16th)
+* i18n 2.3.2 (16th)
+* JCR Resource Security 1.0.2 (13th)
+* Installer Core 3.6.0 (12th)
+* Installer Factory Configuration 1.1.0 (12th)
+* Launchpad Installer 1.2.2 (12th)
+* Eventing 3.5.0 (10th)
+* JCR Resource Security 1.0.0 (9th)
+* Installer Factory Subsystems 1.0.0 (9th)
+
+## December 2014
+
+* Release JUnit Core 1.0.10 (15th)
+* JUnit Scriptable Tests Provider 1.0.10 (15th)
+* JUnit Remote Tests Runners 1.0.10 (15th)
+* Testing Sling Mock 1.1.0 (15th)
+* OSGi Mock 1.1.0 (15th)
+* JCR Mock 1.1.0 (15th)
+* ResourceResolver Mock 1.1.0 (15th)
+* Adapter Manager 2.1.4 (15th)
+* Auth Core 1.3.4 (2nd)
+
+## November 2014
+
+* Resource Merger 1.2.0 (29th)
+* Resource Resolver 1.1.8 (27th)
+* Servlets Resolver 2.3.6 (18th)
+* Engine 2.3.10 (18th)
+* Servlets Resolver 2.3.4 (14th)
+* Eventing 3.4.4 (7th)
+* Scripting JSP 2.1.6 (6th)
+* Slingstart Maven Plugin 1.0.2 (5th)
+* Event 3.4.2 (1st)
+
+## October 2014
+
+* Engine 2.3.8 (27th)
+* Provisioning Model 1.0.0 (27th)
+* Slingstart Maven Plugin 1.0.0 (27th)
+* JCR Resource Resolver 2.3.12 (26th)
+* Eventing 3.4.0 (24th)
+* Distributed Event Admin 1.0.0 (24th)
+* JCR Resource Resolver 2.3.10 (22nd)
+* Auth Core 1.3.2 (21st)
+* Resource Resolver Mock 1.0.0 (21st)
+* JCR Mock 1.0.0 (21st)
+* OSGi Mock 1.0.0 (21st)
+* Sling Mock 1.0.0 (21st)
+* Sling Mock Jackrabbit 0.1.0 (21st)
+* IDE Tooling 1.0.4 (18th)
+* Settings 1.3.4 (10th)
+* Discovery API 1.0.2 (10th)
+* Discovery Impl 1.0.12 (10th)
+* Resource Resolver 1.1.6 (4th)
+* Superimposing Resource Provider 0.2.0 (3rd)
+* Sling 7 (3rd)
+
+## September 2014
+
+* Scripting Java 2.0.12 (30th)
+* Resource Resolver Mock 0.3.0 (29th)
+* Resource Resolver 1.1.4 (26th)
+* JSP Taglib 2.2.4 (25th)
+* DavEx Access to repositories 1.2.2 (22nd)
+* Adapter Manager Implementation 2.1.2 (22nd)
+* Scripting Core implementation 2.0.28 (22nd)
+* JCR ContentLoader 2.1.0 (21st)
+* Web Console Security Provider 1.1.4 (21st)
+* Resource Access Security 1.0.0 (20th)
+* Auth Core 1.3.0 (20th)
+* Engine 2.3.6 (19th)
+* JCR ClassLoader 3.2.2 (19th)
+* JCR Jackrabbit Access Manager 2.1.2 (19th)
+* JCR Jackrabbit Server 2.2.0 (19th)
+* JCR Jackrabbit User Manager 2.2.2 (19th)
+* JCR Registration 1.0.2 (19th)
+* JCR Web Console 1.0.2 (19th)
+* Scripting Thymeleaf 0.0.4 (17th)
+* Resource Resolver 1.1.2 (16th)
+* Maven Launchpad Plugin 2.3.2 (15th)
+* Filesystem Resource Provider 1.1.4 (12th)
+* Launchpad Content 2.0.8 (12th)
+* JSP Tag Library 2.2.2 (12th)
+* Scripting Groovy Support 1.0.0 (12th)
+* Auth Core 1.2.0 (8th)
+* Models API 1.1.0 (5th)
+* Models Impl 1.1.0 (5th)
+* Health Check Annotations 1.0.2 (5th)
+* Health Check Core 1.1.2 (5th)
+* Health Check JUnit Bridge 1.0.2 (5th)
+* Health Check Samples 1.0.6 (5th)
+* Default GET Servlets 2.1.10 (1st)
+* Explorer 1.0.4 (1st)
+
+## August 2014
+
+* API 2.8.0 (31st)
+* JCR Resource 2.3.8 (31st)
+* i18n 2.2.10 (31st)
+* Installer Core 3.5.4 (31st)
+* JCR Installer 3.1.8 (31st)
+* File Installer 1.0.4 (31st)
+* JSON Library 2.0.8 (28th)
+* Default POST Servlets 2.3.6 (28th)
+* Eventing 3.3.14 (25th)
+* Commons Mime 2.1.8 (25th)
+* Commons Scheduler 2.4.4 (25th)
+* Commons Mime 2.1.6 (19th)
+* Commons OSGi 2.2.2 (19th)
+* Tenant 1.0.2 (18th)
+* Query 2.0.0 (11th)
+* Auth Core 1.1.8 (11th)
+* Auth Selector 1.0.6 (11th)
+* Form Based Authentication 1.0.6 (11th)
+* OpenID Authentication 1.0.4 (11th)
+* Eventing 3.3.12 (8st)
+* Parent 20 (1st)
+
+## July 2014
+
+* Discovery Impl 1.0.10 (29th)
+* Engine 2.3.4 (26th)
+* Launchpad Base 4.4.1-2.5.2 (26th)
+* Testing Tools 1.0.8 (22nd)
+* Compat Servlets 1.0.2 (14th)
+* Service User Mapper 1.0.4 (14th)
+* Settings 1.3.2 (26th)
+* Scripting JSP 2.1.4 (26th)
+* Scripting Java 2.0.10 (13th)
+* Authentication XING API 0.0.2 (11th)
+* Authentication XING Login 0.0.2 (11th)
+* Authentication XING OAuth 0.0.2 (11th)
+* Scripting Thymeleaf 0.0.2 (11th)
+* Models API 1.0.2 (2nd)
+* Models Impl 1.0.6 (2nd)
+* Installer Core 3.5.2 (2nd)
+* Installer Configuration Factory 1.0.14 (2nd)
+* Eclipse IDE 1.0.0 (1st)
+
+## June 2014
+
+* Scripting JavaScript Support 2.0.14 (23rd)
+* SLF4J MDC Filter 1.0.0 (9th)
+* Classloader Leak Detector 1.0.0 (9th)
+* Bundle JCR Install Archetype 1.0.2 (4th)
+* Tooling Support Install 1.0.0 (4th)
+* Bundle Archetype 1.0.2 (4th)
+* Servlet Archetype 1.0.2 (4th)
+* Service User Mapper 1.0.2 (2nd)
+
+## May 2014
+
+* JCR ContentLoader 2.1.8 (23th)
+* Commons Compiler 2.2.0 (20th)
+* Scripting JSP 2.1.0 (20th)
+* Archetype Parent 1 (14th)
+* Models Impl 1.0.4 (7th)
+
+## April 2014
+
+* Discovery Impl 1.0.8 (30th)
+* Resource Inventory 1.0.2 (30th)
+* Eventing 3.3.10 (30th)
+* Commons ClassLoader 1.3.2 (17th)
+* Resource Resolver 1.1.0 (4th)
+* Featureflags 1.0.0 (4th)
+* Resource-Based Discovery Service (discovery.impl) 1.0.6 (4th)
+
+## March 2014
+
+* Servlets Resolver 2.3.2 (31st)
+* Servlets Get 2.1.8 (31st)
+* Installer Configuration Factory 1.0.12 (31st)
+* Parent POM 19 (31st)
+* API 2.7.0 (24th)
+* JCR Resource 2.3.6 (24th)
+* JMX Resource Provider 1.0.2 (24th)
+* Resource Merger 1.1.2 (24th)
+* Model Implementation 1.0.2 (18th)
+* JCR Resource 2.3.4 (17th)
+* Engine 2.3.2 (16th)
+* i18n 2.2.8 (8th)
+* JCR Resource 2.3.2 (8th)
+* Discovery Impl 1.0.4 (8th)
+* JCR Webdav 2.2.2 (8th)
+* Resource Collection 1.0.0 (8th)
+* Resource Inventory 1.0.0 (8th)
+* JMX Provider 1.0.0 (8th)
+* Resource Merger 1.1.0 (8th)
+* Commons Log 4.0.0 (7th)
+* Security 1.0.0 (7th)
+* JCR Registration 1.0.0 (7th)
+* Bundle Resource 2.2.20 (7th)
+* JCR Base 2.2.2 (7th)
+* Eventing 3.3.6 (7th)
+* Scripting API 2.1.6 (7th)
+* Scripting Core 2.0.26 (7th)
+* Servlets Get 2.1.6 (7th)
+* Servlets Post 2.3.4 (7th)
+* Maven Launchpad Plugin 2.3.0 (6th)
+* API 2.6.0 (3rd)
+* Engine 2.3.0 (3rd)
+
+## February 2014
+
+* Servlets Resolver 2.3.0 (24th)
+* Resource Merger 1.0.0 (24th)
+* JCR API 2.2.0 (17th)
+* JCR Base 2.2.0 (17th)
+* JCR Resource 2.3.0 (17th)
+* Service User Mapper 1.0.0 (6th)
+* Resource Resolver Mock 0.2.0 (6th)
+
+## January 2014
+
+* Health Check Core 1.1.0 (31th)
+* Health Check Webconsole 1.1.0 (31th)
+* Auth Core 1.1.6 (31th)
+* Pax Exam Utilities 1.0.2 (28th)
+* API 2.5.0 (24th)
+* Eventing 3.3.4 (24th)
+* Installer Core 3.5.0 (19th)
+* Eventing 3.3.2 (19th)
+
+## December 2013
+
+* Web Console Security Provider 1.1.2 (17th)
+* Maven JSPC Plugin 2.0.8 (14th)
+* Resource-Based Discovery Service 1.0.2 (3rd)
+
+## November 2013
+
+* Testing Utilities 2.0.16 (November 27th)
+
+## October 2013
+
+* Web Console Security Provider 1.1.0 (28th)
+* Event 3.3.0 (24th)
+* Commons Scheduler 2.4.2 (24th)
+* Commons Threads 3.2.0 (24th)
+* Health Check Core 1.0.6 (24th)
+* Health Check JMX 1.0.6 (24th)
+* JMX Resource Provider 0.6.0 (24th)
+* Engine 2.2.10 (October 12th)
+* Auth Core 1.1.4 (7th)
+* Commons Scheduler 2.4.0 (7th)
+* Resource Inventory 0.5.0 (7th)
+* JMX Resource Provider 0.5.0 (7th)
+
+## September 2013
+
+* org.apache.sling.hc.core-1.0.4 (30th)
+* org.apache.sling.hc.it-1.0.4 (30th)
+* org.apache.sling.hc.jmx-1.0.4 (30th)
+* org.apache.sling.hc.samples-1.0.4 (30th)
+* org.apache.sling.hc.support-1.0.4 (30th)
+* org.apache.sling.hc.webconsole-1.0.4 (30th)
+* org.apache.sling.junit.healthcheck-1.0.6 (30th)
+* Commons Log 3.0.2 (12th)
+
+## August 2013
+
+* Discovery Impl 1.0.0 (12th)
+* Discovery Standalone 1.0.0 (12th)
+* Discovery Support 1.0.0 (12th)
+* Settings 1.3.0 (12th)
+* Event 3.2.0 (12th)
+* JCR Jackrabbit Server 2.1.2 (8th)
+* Scripting JSP Taglib 2.2.0 (8th)
+
+## July 2013
+
+* JCR DavEx 1.2.0 (31st)
+* JCR Webdav 2.2.0 (31st)
+* Servlets Post 2.3.2 (18th)
+* I18n 2.2.6 (18th)
+* Commons FileSystem ClassLoader 1.0.0 (18th)
+* JCR ClassLoader 3.2.0 (18th)
+* Parent POM 17 (18th)
+
+## May 2013
+
+* Form Based Authentication Handler 1.0.4 (27th)
+* Scripting JSP 2.0.28 (16th)
+* Servlets Post 2.3.0 (10th)
+* JCR Resource 2.2.8 (10th)
+* API 2.4.2 (3rd)
+* Parent POM 16 (3rd)
+
+## April 2013
+
+* Tenant 1.0.0 (26th)
+* Security 1.0.4 (26th)
+* javax.activation 0.1.0 (26th)
+* API 2.4.0 (18th)
+* Bundle Resource Provider 2.1.2 (18th)
+* File System Resource Provider 1.1.2 (18th)
+* JCR Resource 2.2.6 (18th)
+* Resource Resolver 1.0.6 (18th)
+* Servlets Resolver 2.2.4 (18th)
+* Engine 2.2.8 (18th)
+* Auth Core 1.1.2 (18th)
+
+## March 2013
+
+* Launchpad Base 2.5.0 (4th)
+* Script Console 1.0.0 (4th)
+
+## February 2013
+
+* JCR Classloader 3.1.2 (18th)
+* Commons Testing 2.0.14 (18th)
+* JUnit Core 1.0.8 (18th)
+* JUnit Remote 1.0.8 (18th)
+* JUnit Scriptable 1.0.8 (18th)
+* Testing Tools 1.0.6 (18th)
+* Installer Core 3.4.6 (18th)
+* Installer Configuration Factory 1.0.10 (18th)
+* JCR Instaler 3.1.6 (18th)
+* Parent POM 15 (18th)
+* Fragment Extension XML 1.0.2 (18th)
+* Fragment Extension WS 1.0.2 (18th)
+* Fragment Extension Activation 1.0.2  (18th)
+* Resource Resolver 1.0.4 (14th )
+* JCR Resource 2.2.4 (14th)
+
+## December 2012
+
+* Installer Core 3.4.4 (20th)
+* JCR Resource 2.2.2 (20th)
+* Resource Resolver 1.0.2 (20th)
+* Security 1.0.2 (20th)
+* Parent POM 14 (20th)
+* Servlet Resolver 2.2.2 (10th)
+
+## November 2012
+
+* Settings 1.2.2 (30th)
+* Auth Core 1.1.0 (30th)
+* Commons Logservice 1.0.2 (30th)
+* Installer Core 3.4.2 (30th)
+* Scripting JSP 2.0.26 (30th)
+* Commons Compiler 2.1.0 (30th)
+* JCR Compiler 2.1.0 (30th)
+* I18n 2.2.4 (30th)
+* JCR Classloader 3.1.10 (30th)
+* JCR Webdav 2.1.2 (30th)
+* JCR Davex 1.1.0 (30th)
+* Maven Launchpad Plugin 2.2.0 (19th)
+* Commons OSGi 2.2.0 (19th)
+* Launchpad Installer 1.2.0 (19th)
+* Rewriter 1.0.4 (19th)
+* Settings 1.2.0 (19th)
+* API 2.3.0 (15th)
+* Bundle Resource Provider 2.1.0 (15th)
+* File System Resource Provider 1.1.0 (15th)
+* JCR Resource 2.2.0 (15th)
+* Resource Resolver 1.0.0 (15th)
+* Servlets Get 2.1.4 (15th)
+* Servlets Post 2.2.0 (15th)
+* Servlets Resolver 2.2.0 (15th)
+* Adapter 2.1.0 (15th)
+* Commons Testing 2.0.12 (15th)
+
+## October 2012
+
+* JSP Taglib 2.1.8 (29th)
+* Installer Core 3.4.0 (29th)
+* Installer API 1.0.0 (29th)
+* Installer Console 1.0.0 (29th)
+* JCR Wrapper 2.0.0 (29th)
+
+## August 2012
+
+* Installer Core 3.3.8 (19th)
+* Launchpad Installer 1.1.4 (19th)
+* Maven Launchpad Plugin 2.1.2 (19th)
+* Scripting JST 2.0.6 (17th)
+
+## July 2012
+
+* Adapter 2.0.16 (9th)
+* JCR ContentLoader 2.1.6 (9th)
+* Parent POM 13 (9th)
+
+## June 2012
+* Commons Compiler 2.0.6 (28th)
+* Adapter 2.0.14 (28th)
+* JCR ClassLoader 3.1.8 (28th)
+* JCR Compiler 2.0.4 (28th)
+* Scripting Core 2.0.24 (28th)
+* Scripting Java 2.0.4 (28th)
+* Scripting JSP 2.0.24 (28th)
+* POST Servlets 2.1.2 (28th)
+
+## May 2012
+
+* Installer Factory Configuration 1.0.8 (26th)
+* Engine 2.2.6 (26th)
+* i18n 2.2.2 (26th)
+* Scripting Core 2.0.22 (26th)
+* Commons ClassLoader 1.3.0 (18th)
+* Commons Compiler 2.0.4 (18th)
+* Eventing 3.1.4 (18th)
+* Installer Core 3.3.6 (18th)
+* JCR Installer 3.1.4 (18th)
+* JCR ClassLoader 3.1.6 (18th)
+* JCR Resource 2.1.0 (18th)
+* Launchpad Installer 1.1.2 (18th)
+* Scripting Java 2.0.4 (18th)
+* Scripting JSP 2.0.22 (18th)
+
+## February 2012
+
+* Commons Testing 2.0.10 (7th)
+* Commons Scheduler 2.3.4 (7th)
+* Commons Log 3.0.0 (7th)
+* Commons Log Service 1.0.0 (7th)
+* Adapter 2.0.12 (7th)
+* Installer Core 3.3.4 (7th)
+* Launchpad API 1.1.0 (7th)
+* Launchpad Installer 1.1.0 (7th)
+* Maven JSPC Plugin 2.0.6 (7th)
+
+## January 2012
+
+* API 2.2.4 (30th)
+* Adapter 2.0.10 (30th)
+* Scripting JSP Taglib 2.1.6 (30th)
+* Rewriter 1.0.2 (30th)
+* JCR ContentLoader 2.1.4 (30th)
+* JCR Base 2.1.2 (30th)
+* Servlet Resolver 2.1.2 (30th)
+* Security 1.0.0 (30th)
+* Scripting API 2.1.4 (16th)
+* Scripting Core 2.0.20 (16th)
+* i18n 2.2.0 (16th)
+* Installer Core 3.3.2 (16th)
+* Scripting Java 2.0.2 (16th)
+* Scripting JSP 2.0.20 (16th)
+* Adapter Annotations 1.0.0 (14th)
+* Maven Sling Plugin 2.1.0 (14th)
+* Settings 1.1.0 (6th)
+* Commons ClassLoader 1.2.4 (6th)
+* Commons Scheduler 2.3.2 (6th)
+* Installer Core 3.3.0 (6th)
+* Installer Configuration Factory 1.0.4 (6th)
+* Launchpad Installer 1.0.6 (6th)
+* JCR Installer 3.1.2 (6th)
+* Thread Dumper 0.2.2 (6th)
+
+## November 2011
+
+* Jackrabbit User Manager 2.2.0 (15th)
+
+## October 2011
+
+* Maven Sling Plugin 2.0.6 (21st)
+
+## September 2011
+
+* Maven Launchpad Plugin 2.1.0 (9th)
+
+## August 2011
+
+* Resource Bundle 1.0.0 (16th)
+* Parent POM 12 (16th)
+* API 2.2.2 (16th)
+* Commons Scheduler 2.3.0 (16th)
+* Commons OSGi 2.1.0 (16th)
+* Scripting Core 2.0.18 (16th)
+* Installer Core 3.2.2 (16th)
+* Installer Configuration Factory 1.0.2 (16th)
+* Launchpad Installer 1.0.4 (16th)
+* File Installer 1.0.2 (16th)
+* Scripting JSP Support 2.0.18 (15th)
+* Parent POM 11 (8th)
+
+## July 2011
+
+* Internationalization 2.1.2 (15th)
+* Event 3.1.0 (13th)
+* OSGi Installer 3.2.0 (13th)
+* JCR Installer 3.1.0 (13th)
+* Installer Configuration Factory 1.0.0 (13th)
+* Launchpad Installer 1.0.2 (13th)
+
+## June 2011
+
+* Engine 2.2.4 (22nd)
+
+## May 2011
+
+* Launchpad Standalone Archetype 1.0.0 (13th)
+* Launchpad Webapp Archetype 1.0.0 (13th)
+* Scripting JSP Support 2.0.16(3rd)
+* JSP Taglib 2.1.2 (3rd)
+
+## April 2011
+
+* Test Tools 1.0.2 (26th)
+* JUnit Core 1.0.6 (26th)
+* JUnit Remote Tests Runners 1.0.6 (26th)
+* JUnit Scriptable Tests Provider 1.0.6 (26th)
+* Sample Integration Tests 1.0.6 (26th)
+* Sample Server-Side Tests 1.0.6 (26th)
+* Failing Server-Side Tests 1.0.6 (26th)
+* I18N 2.1.0 (12th)
+
+## March 2011
+
+* Sling 6 (28th)
+* Launchpad Content 2.0.6 (28th)
+* Launchpad Integration Tests 1.0.0 (04th)
+* Launchpad Testing Services 2.0.8 (04th)
+* Launchpad Testing Services WAR 2.0.8 (04th)
+
+## February 2011
+
+* Javascript 2.0.12 (26th)
+* Explorer 1.0.2 (24th)
+* JCR Resource 2.0.10 (24th)
+* Engine 2.2.2 (24th)
+* Installer IT Testing 3.1.2 (24th)
+* Launchpad API 1.0.0 (20th)
+* Launchpad Installer 1.0.0 (20th)
+* Launchpad Base 2.3.0 (20th)
+* Maven Launchpad Plugin 2.0.10 (20th)
+* Commons Testing 2.0.8 (20th)
+* Servlets Get 2.1.2 (18th)
+* Installer Core 3.1.2 (4th)
+* JCR Installer 3.0.4 (4th)
+* Event 3.0.2 (4th)
+
+## January 2011
+
+* Scripting Core 2.0.16 (29th)
+* JCR Resource 2.0.8 (28th)
+* Engine 2.2.0 (28th)
+* Bundle Resource Provider 2.0.6 (28th)
+* File Resource Provider 1.0.2 (28th)
+* Auth Core 1.0.6 (28th)
+* Auth Selector 1.0.4 (28th)
+* Commons Compiler 2.0.2 (21st)
+* JCR Compiler 2.0.2 (21st)
+* Commons Log 2.1.2 (21st)
+* Event 3.0.0 (21st)
+* Scripting JSP 2.0.14 (21st)
+* Installer Core 3.1.0 (21st)
+* JCR Installer 3.0.2 (21st)
+
+## December 2010
+
+* Maven Launchpad Plugin 2.0.8 (20th)
+* Commons Compiler 2.0.0 (20th)
+* i18n 2.0.4 (20th)
+* Commons Json 2.0.6 (20th)
+* Commons Log 2.1.0 (20th)
+* Scripting Java 2.0.0 (20th)
+* Scripting JST 2.0.4 (20th)
+* Scripting API 2.1.2 (20th)
+* Scripting JSP 2.0.12 (20th)
+* Scripting Javascript 2.0.10 (20th)
+* JCR Compiler 2.0.0 (20th)
+* Auth Core 1.0.4 (20th)
+* Auth Selector 1.0.2 (20th)
+* Auth Form 1.0.2 (20th)
+* Auth OpenId 1.0.2 (20th)
+* JCR ContentLoader 2.1.2 (20th)
+* API 2.20 (13th)
+* Adapter 2.0.8 (13th)
+* Commons ClassLoader 1.2.2 (13th)
+* JCR ClassLoader 3.1.4 (13th)
+* Parent POM 10 (13th)
+
+## November 2010
+
+* JCR Web Console Plugin 1.0.0 (16th)
+* JCR Access Manager 2.1.0
+* JCR User Manager 2.1.0 (8th) (8th)
+* JCR WebDAV support 2.1.0 (8th)
+* JCR DavEX support 1.0.0 (8th)
+* Explorer 1.0.0 (1st)
+
+## October 2010
+
+* Scripting Core 2.0.14 (25th)
+* Commons Threads 3.1.0 (15th)
+* Event 2.4.2 (15th)
+* I18N 2.0.2 (15th)
+* Rewriter 1.0.0 (15th)
+* Settings 1.0.2 (15th)
+
+## September 2010
+
+* Installer Core 3.0.0 (24th)
+* Installer File Provider 1.0.0 (24th)
+* Installer JCR Provider 3.0.0 (24th)
+* Commons Testing 2.0.6 (20th)
+* JCR API 2.1.0 (10th)
+* JCR Base 2.1.0 (10th)
+* JCR Content Loader 2.1.0 (10th)
+* Jackrabbit Server 2.1.0 (10th)
+* Commons Threads 3.0.2 (06th)
+* Event 2.4.0 (06th)
+
+## August 2010
+
+* Commons ClassLoader 1.2.0 (30th)
+* JCR ClassLoader 3.1.2 (30th)
+* Web Console Branding 1.0.0 (25th)
+* Web Console Security Provider 1.0.0 (25th)
+* API 2.1.0 (21st)
+
+## July 2010
+
+* GWT Integration 3.0.0 (30th)
+
+## April 2010
+
+* Commons OSGi 2.0.6 (27th)
+* Launchpad Base 2.2.0 (27th)
+* Maven Launchpad Plugin 2.0.6 (27th)
+
+## March 2010
+
+* Event 2.3.0 (1st)
+* Scripting Core 2.1.0 (1st)
+* Apache Commons MIME 2.1.4 (1st)
+* FileResource Provider 1.0.0 (1st)
+
+## February 2010
+
+* Sample Path Based Resource Type Provider 2.0.4 (22nd)
+* Event 2.2.0 (19th)
+* Scripting API 2.1.0 (19th)
+* Thread Dumper 0.2.0 (19th)
+* JCR WebDav 2.0.8 (17th)
+* JCR ContentLoader 2.0.6 (17th)
+* JCR UserManager 2.0.4 (17th)
+* JCR Server 2.0.6 (17th)
+* JCR AccessManager 2.0.4 (17th)
+* JCR Base 2.0.6 (17th)
+* Commons ClassLoader 1.1.4 (8th)
+* JCR ClassLoader 3.1.0 (8th)
+
+## January 2010
+
+* JCR API 2.0.6 (29th)
+
+## December 2009
+
+* Commons ClassLoader 1.1.2 (21st)
+* Commons Scheduler 2.2.0 (21st)
+* Commons Threads 3.0.0 (21st)
+* Event 2.1.0 (21st)
+* Servlets Get 2.0.8 (21st)
+* Commons Mime 2.1.2 (15th)
+* Commons HTML 1.0.0 (2nd)
+* Commons Compiler 1.0.0 (2nd)
+* JCR Compiler 1.0.0 (2nd)
+* JCR Prefs 1.0.0 (2nd)
+* Scripting Java 1.0.0 (2nd)
+
+## November 2009
+
+* Parent POM 8 (28th)
+* Launchpad Base 2.1.0 (28th)
+* Commons ClassLoader 1.1.0 (28th)
+* JCR ClassLoader 3.0.0 (28th)
+* Scripting Core 2.0.8 (28th)
+* Scripting JSP 2.0.8 (28th)
+* Scripting JSP Taglib 2.0.6 (28th)
+* Scripting JavaScript 2.0.6 (28th)
+
+## October 2009
+
+* Engine 2.0.6 (13th)
+* Adapter 2.0.4 (13th)
+* JCR Resource 2.0.6 (13th)
+* Commons ClassLoader 1.0.0 (13th)
+* Event 2.0.6 (13th)
+* JCR ClassLoader 2.0.6 (13th)
+* Scripting Core 2.0.6 (13th)
+* Servlets Resolver 2.0.8 (13th)
+* API 2.0.8 (2nd)
+* Commons HTML 0.9.0 (2nd)
+* Commons ClassLoader 0.9.0 (2nd)
+* Commons Scheduler 2.1.0 (2nd)
+* Servlets Get 2.0.6 (2nd)
+
+## August 2009
+
+* API 2.0.6 (17th)
+* JCR API 2.0.4 (17th)
+* Commons LogService 2.0.6 (5th)
+


[sling-org-apache-sling-committer-cli] 01/44: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to branch feature/SLING-8337
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git

commit abbd20f172a25e5e51ab75fa06c15fc1f8f9231f
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Fri Mar 8 13:56:17 2019 +0200

    SLING-8311 - Investigate creating a Sling CLI tool for development task automation
    
    Prototype of sling cli tool.
---
 .gitignore                                         |   1 +
 Dockerfile                                         |  30 ++++
 README.md                                          |  23 +++
 bnd.bnd                                            |   0
 docker-env.sample                                  |  15 ++
 pom.xml                                            | 173 +++++++++++++++++++++
 src/main/features/app.json                         |  67 ++++++++
 .../java/org/apache/sling/cli/impl/Command.java    |  26 ++++
 .../apache/sling/cli/impl/CommandProcessor.java    | 143 +++++++++++++++++
 .../apache/sling/cli/impl/ExecutionTrigger.java    |  37 +++++
 .../org/apache/sling/cli/impl/jira/Version.java    |  47 ++++++
 .../apache/sling/cli/impl/jira/VersionFinder.java  |  98 ++++++++++++
 .../sling/cli/impl/nexus/StagingRepositories.java  |  33 ++++
 .../sling/cli/impl/nexus/StagingRepository.java    |  65 ++++++++
 .../cli/impl/nexus/StagingRepositoryFinder.java    |  86 ++++++++++
 .../cli/impl/release/PrepareVoteEmailCommand.java  |  98 ++++++++++++
 .../sling/cli/impl/release/TallyVotesCommand.java  |  39 +++++
 src/main/resources/conf/logback-default.xml        |  23 +++
 src/main/resources/scripts/launcher.sh             |  29 ++++
 .../impl/release/PrepareVoteEmailCommandTest.java  |  31 ++++
 20 files changed, 1064 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a49f72d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+docker-env
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..1338fcb
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,30 @@
+# ----------------------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------------------
+
+FROM openjdk:8-jre-alpine
+MAINTAINER dev@sling.apache.org
+# escaping required to properly handle arguments with spaces
+ENTRYPOINT ["/usr/share/sling-cli/bin/launcher.sh"]
+
+# Add feature launcher
+ADD target/lib /usr/share/sling-cli/launcher
+# Add launcher script
+ADD target/classes/scripts /usr/share/sling-cli/bin
+# workaround for MRESOURCES-236
+RUN chmod a+x /usr/share/sling-cli/bin/*
+# Add config files
+ADD target/classes/conf /usr/share/sling-cli/conf
+# Add all bundles
+ADD target/artifacts /usr/share/sling-cli/artifacts
+# Add the service itself
+ARG FEATURE_FILE
+ADD ${FEATURE_FILE} /usr/share/sling-cli/sling-cli.feature
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..eaa6d43
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# Apache Sling Engine CLI tool
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
+
+This module provides a command-line tool which automates various Sling development tasks. The tool is packaged
+as a docker image.
+
+## Configuration
+
+To make various credentials and configurations available to the docker image it is recommended to use a docker env file.
+A sample file is stored at `docker-env.sample`. Copy this file to `docker-env` and fill in your own information.
+
+## Launching
+
+The image is built using `mvn package`. Afterwards it may be run with
+
+    docker run --env-file=./docker-env apache/sling-cli
+    
+This invocation produces a list of available subcommands.
+
+Currently the only implemented command is generating the release vote email, for instance
+
+    docker run --env-file=./docker-env apache/sling-cli release prepare-email $STAGING_REPOSITORY_ID
\ No newline at end of file
diff --git a/bnd.bnd b/bnd.bnd
new file mode 100644
index 0000000..e69de29
diff --git a/docker-env.sample b/docker-env.sample
new file mode 100644
index 0000000..15454cf
--- /dev/null
+++ b/docker-env.sample
@@ -0,0 +1,15 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------------------
+ASF_USERNAME=changeme
+ASF_PASSWORD=changeme
+RELEASE_ID=42
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..047121d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,173 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>34</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>sling-cli</artifactId>
+    <version>1.0-SNAPSHOT</version>
+
+    <description>Sling CLI tool for development usage</description>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addClasspath>true</addClasspath>
+                            <classpathPrefix>lib/</classpathPrefix>
+                            <mainClass>org.apache.sling.cli.impl.Main</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <overWriteReleases>false</overWriteReleases>
+                            <includeScope>runtime</includeScope>
+                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
+                            <stripVersion>true</stripVersion>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>slingfeature-maven-plugin</artifactId>
+                <version>0.8.0</version>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <id>feature-dependencies</id>
+                        <goals>
+                            <goal>repository</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>extra-dependencies</id>
+                        <goals>
+                            <goal>repository</goal>
+                        </goals>
+                        <configuration>
+                            <repositories>
+                                <repository>
+                                    <embedArtifacts>
+                                        <embedArtifact>
+                                            <groupId>org.apache.felix</groupId>
+                                            <artifactId>org.apache.felix.framework</artifactId>
+                                            <version>6.0.2</version>
+                                        </embedArtifact>
+                                        <embedArtifact>
+                                            <groupId>org.apache.sling</groupId>
+                                            <artifactId>org.apache.sling.launchpad.api</artifactId>
+                                            <version>1.2.0</version>
+                                        </embedArtifact>
+                                    </embedArtifacts>
+                                </repository>
+                            </repositories>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.spotify</groupId>
+                <artifactId>dockerfile-maven-plugin</artifactId>
+                <version>1.4.10</version>
+                <executions>
+                    <execution>
+                        <id>default</id>
+                        <goals>
+                            <goal>build</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skipDockerInfo>true</skipDockerInfo> <!-- does not contain legal files -->
+                    <repository>apache/sling-cli</repository>
+                    <buildArgs>
+                        <FEATURE_FILE>target/artifacts/org/apache/sling/${project.artifactId}/${project.version}/${project.artifactId}-${project.version}-app.slingfeature</FEATURE_FILE>
+                    </buildArgs>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.metatype.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.feature.launcher</artifactId>
+            <version>0.8.0</version>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient-osgi</artifactId>
+            <version>4.5.7</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.8.5</version>
+            <scope>provided</scope>
+        </dependency>
+        
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/features/app.json b/src/main/features/app.json
new file mode 100644
index 0000000..f07827d
--- /dev/null
+++ b/src/main/features/app.json
@@ -0,0 +1,67 @@
+{
+	"id": "${project.groupId}:${project.artifactId}:slingfeature:app:${project.version}",
+	"variables": {
+    	"asf.username":"change-me",
+    	"asf.password": "change-me"
+    },
+	"bundles": [
+		{
+			"id": "${project.groupId}:${project.artifactId}:${project.version}",
+			"start-level": "5"
+		},
+		{
+			"id": "org.apache.felix:org.apache.felix.scr:2.1.12",
+			"start-level": "1"
+		},
+		{
+			"id": "org.apache.felix:org.apache.felix.configadmin:1.9.10",
+			"start-level": "1"
+		},
+		{
+			"id": "org.apache.felix:org.apache.felix.log:1.2.0",
+			"start-level": "1"
+		},
+		{
+			"id": "ch.qos.logback:logback-classic:1.2.3",
+			"start-level": "1"
+		},
+		{
+			"id": "ch.qos.logback:logback-core:1.2.3",
+			"start-level": "1"
+		},
+		{
+			"id": "org.slf4j:jul-to-slf4j:1.7.25",
+			"start-level": "1"
+		},
+		{
+			"id": "org.slf4j:jcl-over-slf4j:1.7.25",
+			"start-level": "1"
+		},
+		{
+			"id": "org.slf4j:slf4j-api:1.7.25",
+			"start-level": "1"
+		},
+		{
+			"id": "org.apache.felix:org.apache.felix.logback:1.0.2",
+			"start-level": "1"
+		},
+		{
+			"id": "org.apache.httpcomponents:httpcore-osgi:4.4.11",
+			"start-level": "3"
+		},
+		{
+			"id": "org.apache.httpcomponents:httpclient-osgi:4.5.7",
+			"start-level": "3"
+		},
+		{
+			"id": "com.google.code.gson:gson:2.8.5",
+			"start-level": "3"
+		}
+	],
+	"configurations": {
+		"org.apache.sling.cli.impl.nexus.StagingRepositoryFinder": {
+			"username": "${asf.username}",
+			"password": "${asf.password}"
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/cli/impl/Command.java b/src/main/java/org/apache/sling/cli/impl/Command.java
new file mode 100644
index 0000000..4caeea7
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/Command.java
@@ -0,0 +1,26 @@
+/*
+ * 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.cli.impl;
+
+public interface Command {
+    
+    String PROPERTY_NAME_COMMAND = "command";
+    String PROPERTY_NAME_SUBCOMMAND = "subcommand";
+    String PROPERTY_NAME_SUMMARY = "summary";
+
+    void execute(String target);
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java b/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
new file mode 100644
index 0000000..57e5430
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/CommandProcessor.java
@@ -0,0 +1,143 @@
+/*
+ * 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.cli.impl;
+
+import static org.osgi.service.component.annotations.ReferenceCardinality.MULTIPLE;
+import static org.osgi.service.component.annotations.ReferencePolicy.DYNAMIC;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.launch.Framework;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = CommandProcessor.class)
+public class CommandProcessor {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    private BundleContext ctx;
+
+    private Map<CommandKey, CommandWithProps> commands = new ConcurrentHashMap<>();
+
+    protected void activate(BundleContext ctx) {
+        this.ctx = ctx;
+    }
+
+    @Reference(service = Command.class, cardinality = MULTIPLE, policy = DYNAMIC)
+    protected void bindCommand(Command cmd, Map<String, ?> props) {
+        commands.put(CommandKey.of(props), CommandWithProps.of(cmd, props));
+    }
+
+    protected void unbindCommand(Map<String, ?> props) {
+        commands.remove(CommandKey.of(props));
+    }
+
+    public void runCommand() {
+        // TODO - remove duplication from CLI parsing code
+        CommandKey key = CommandKey.of(ctx.getProperty("exec.args"));
+        String target = parseTarget(ctx.getProperty("exec.args"));
+        commands.getOrDefault(key, new CommandWithProps(ignored -> {
+            logger.info("Usage: sling command sub-command [target]");
+            logger.info("");
+            logger.info("Available commands:");
+            commands.forEach((k, c) -> logger.info("{} {}: {}", k.command, k.subCommand, c.summary));
+        }, "")).cmd.execute(target);
+        try {
+            ctx.getBundle(Constants.SYSTEM_BUNDLE_LOCATION).adapt(Framework.class).stop();
+        } catch (BundleException e) {
+            logger.warn("Failed running command", e);
+        }
+    }
+
+    private String parseTarget(String cliSpec) {
+        if (cliSpec == null || cliSpec.isEmpty())
+            return null;
+
+        String[] args = cliSpec.split(" ");
+        if (args.length < 3)
+            return null;
+        
+        return args[2];
+    }
+    
+
+    static class CommandKey {
+
+        private static final CommandKey EMPTY = new CommandKey("", "");
+
+        private final String command;
+        private final String subCommand;
+
+        static CommandKey of(String cliSpec) {
+            if (cliSpec == null || cliSpec.isEmpty())
+                return EMPTY;
+
+            String[] args = cliSpec.split(" ");
+            if (args.length < 2)
+                return EMPTY;
+
+            return new CommandKey(args[0], args[1]);
+        }
+
+        static CommandKey of(Map<String, ?> serviceProps) {
+            return new CommandKey((String) serviceProps.get(Command.PROPERTY_NAME_COMMAND), (String) serviceProps.get(Command.PROPERTY_NAME_SUBCOMMAND));
+        }
+
+        CommandKey(String command, String subCommand) {
+            this.command = command;
+            this.subCommand = subCommand;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(command, subCommand);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            CommandKey other = (CommandKey) obj;
+            return Objects.equals(command, other.command) && Objects.equals(subCommand, other.subCommand);
+        }
+    }
+    
+    static class CommandWithProps {
+        private final Command cmd;
+        private final String summary;
+
+        static CommandWithProps of(Command cmd, Map<String, ?> props) {
+            return new CommandWithProps(cmd, (String) props.get(Command.PROPERTY_NAME_SUMMARY));
+        }
+        
+        CommandWithProps(Command cmd, String summary) {
+            this.cmd = cmd;
+            this.summary = summary;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/ExecutionTrigger.java b/src/main/java/org/apache/sling/cli/impl/ExecutionTrigger.java
new file mode 100644
index 0000000..23fa1b8
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/ExecutionTrigger.java
@@ -0,0 +1,37 @@
+/*
+ * 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.cli.impl;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component
+public class ExecutionTrigger {
+
+    @Reference
+    private CommandProcessor processor;
+
+    protected void activate(BundleContext ctx) {
+        ctx.addFrameworkListener(evt -> {
+            if (evt.getType() == FrameworkEvent.STARTED)
+                new Thread(() -> processor.runCommand(), getClass().getSimpleName() + "Thread").start();
+        });
+        // never removed but not important - it's one-shot anyway
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/Version.java b/src/main/java/org/apache/sling/cli/impl/jira/Version.java
new file mode 100644
index 0000000..7cce8d5
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/jira/Version.java
@@ -0,0 +1,47 @@
+/*
+ * 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.cli.impl.jira;
+
+public class Version {
+    private int id;
+    private String name;
+    private int issuesFixedCount;
+
+    public int getId() {
+        return id;
+    }
+
+    public void setId(int id) {
+        this.id = id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    public int getIssuesFixedCount() {
+        return issuesFixedCount;
+    }
+    
+    public void setRelatedIssuesCount(int relatedIssuesCount) {
+        this.issuesFixedCount = relatedIssuesCount;
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
new file mode 100644
index 0000000..5bf0406
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/jira/VersionFinder.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cli.impl.jira;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.osgi.service.component.annotations.Component;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+@Component(service = VersionFinder.class)
+public class VersionFinder {
+
+    public Version find(String versionName) throws IOException {
+        Version version;
+        
+        try (CloseableHttpClient client = HttpClients.createDefault()) {
+            version = findVersion(versionName, client);
+            populateRelatedIssuesCount(client, version);
+        }
+        
+        return version;
+    }
+
+    private Version findVersion(String versionName, CloseableHttpClient client) throws IOException {
+        Version version;
+        HttpGet get = new HttpGet("https://issues.apache.org/jira/rest/api/2/project/SLING/versions");
+        get.addHeader("Accept", "application/json");
+        try (CloseableHttpResponse response = client.execute(get)) {
+            try (InputStream content = response.getEntity().getContent();
+                    InputStreamReader reader = new InputStreamReader(content)) {
+                if (response.getStatusLine().getStatusCode() != 200)
+                    throw new IOException("Status line : " + response.getStatusLine());
+                Gson gson = new Gson();
+                Type collectionType = TypeToken.getParameterized(List.class, Version.class).getType();
+                List<Version> versions = gson.fromJson(reader, collectionType);
+                version = versions.stream()
+                    .filter(v -> v.getName().equals(versionName))
+                    .findFirst()
+                    .orElseThrow( () -> new IllegalArgumentException("No version found with name " + versionName));
+            }
+        }
+        return version;
+    }
+
+    private void populateRelatedIssuesCount(CloseableHttpClient client, Version version) throws IOException {
+
+        HttpGet get = new HttpGet("https://issues.apache.org/jira/rest/api/2/version/" + version.getId() +"/relatedIssueCounts");
+        get.addHeader("Accept", "application/json");
+        try (CloseableHttpResponse response = client.execute(get)) {
+            try (InputStream content = response.getEntity().getContent();
+                    InputStreamReader reader = new InputStreamReader(content)) {
+                if (response.getStatusLine().getStatusCode() != 200)
+                    throw new IOException("Status line : " + response.getStatusLine());
+                Gson gson = new Gson();
+                VersionRelatedIssuesCount issuesCount = gson.fromJson(reader, VersionRelatedIssuesCount.class);
+                
+                version.setRelatedIssuesCount(issuesCount.getIssuesFixedCount());
+            }
+        }
+    }
+
+    static class VersionRelatedIssuesCount {
+
+        private int issuesFixedCount;
+
+        public int getIssuesFixedCount() {
+            return issuesFixedCount;
+        }
+
+        public void setIssuesFixedCount(int issuesFixedCount) {
+            this.issuesFixedCount = issuesFixedCount;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositories.java b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositories.java
new file mode 100644
index 0000000..84e1a77
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositories.java
@@ -0,0 +1,33 @@
+/*
+ * 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.cli.impl.nexus;
+
+import java.util.List;
+
+public class StagingRepositories {
+
+    private List<StagingRepository> data;
+
+    public List<StagingRepository> getData() {
+        return data;
+    }
+
+    public void setData(List<StagingRepository> data) {
+        this.data = data;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepository.java b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepository.java
new file mode 100644
index 0000000..167cebb
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepository.java
@@ -0,0 +1,65 @@
+/*
+ * 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.cli.impl.nexus;
+
+/**
+ * DTO for GSON usage
+ *
+ */
+public class StagingRepository {
+    
+    enum Status {
+        open, closed;
+    }
+    
+    private String description;
+    private String repositoryId;
+    private String repositoryURI;
+    private Status type;
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getRepositoryId() {
+        return repositoryId;
+    }
+
+    public void setRepositoryId(String repositoryId) {
+        this.repositoryId = repositoryId;
+    }
+
+    public String getRepositoryURI() {
+        return repositoryURI;
+    }
+
+    public void setRepositoryURI(String repositoryURI) {
+        this.repositoryURI = repositoryURI;
+    }
+    
+    public Status getType() {
+        return type;
+    }
+    
+    public void setType(Status type) {
+        this.type = type;
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
new file mode 100644
index 0000000..3ef7992
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/nexus/StagingRepositoryFinder.java
@@ -0,0 +1,86 @@
+/*
+ * 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.cli.impl.nexus;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.sling.cli.impl.nexus.StagingRepository.Status;
+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.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+
+import com.google.gson.Gson;
+
+@Component(
+    configurationPolicy = ConfigurationPolicy.REQUIRE,
+    service = StagingRepositoryFinder.class
+)
+@Designate(ocd = StagingRepositoryFinder.Config.class)
+public class StagingRepositoryFinder {
+
+    @ObjectClassDefinition
+    static @interface Config {
+        @AttributeDefinition(name="Username")
+        String username();
+        
+        @AttributeDefinition(name="Password")
+        String password();
+    }
+
+    private BasicCredentialsProvider credentialsProvider;
+    
+    @Activate
+    protected void activate(Config cfg) {
+        credentialsProvider = new BasicCredentialsProvider();
+        credentialsProvider.setCredentials(new AuthScope("repository.apache.org", 443), 
+                new UsernamePasswordCredentials(cfg.username(), cfg.password()));
+    }
+
+    public StagingRepository find(int stagingRepositoryId) throws IOException {
+        try ( CloseableHttpClient client = HttpClients.custom()
+                .setDefaultCredentialsProvider(credentialsProvider)
+                .build() ) {
+            HttpGet get = new HttpGet("https://repository.apache.org/service/local/staging/profile_repositories");
+            get.addHeader("Accept", "application/json");
+            try ( CloseableHttpResponse response = client.execute(get)) {
+                try ( InputStream content = response.getEntity().getContent();
+                        InputStreamReader reader = new InputStreamReader(content)) {
+                    if ( response.getStatusLine().getStatusCode() != 200 )
+                        throw new IOException("Status line : " + response.getStatusLine());
+                    Gson gson = new Gson();
+                    return gson.fromJson(reader, StagingRepositories.class).getData().stream()
+                        .filter( r -> r.getType() == Status.closed)
+                        .filter( r -> r.getRepositoryId().endsWith("-" + stagingRepositoryId))
+                        .findFirst()
+                        .orElseThrow(() -> new IllegalArgumentException("No repository found with id " + stagingRepositoryId));
+                }
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
new file mode 100644
index 0000000..eff3a3f
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommand.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cli.impl.release;
+
+import java.io.IOException;
+
+import org.apache.sling.cli.impl.Command;
+import org.apache.sling.cli.impl.jira.Version;
+import org.apache.sling.cli.impl.jira.VersionFinder;
+import org.apache.sling.cli.impl.nexus.StagingRepository;
+import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Command.class, property = {
+    Command.PROPERTY_NAME_COMMAND + "=release",
+    Command.PROPERTY_NAME_SUBCOMMAND + "=prepare-email",
+    Command.PROPERTY_NAME_SUMMARY + "=Prepares an email vote for the specified release." })
+public class PrepareVoteEmailCommand implements Command {
+
+    // TODO - replace with file template
+    private static final String EMAIL_TEMPLATE ="To: \"Sling Developers List\" <de...@sling.apache.org>\n" + 
+            "Subject: [VOTE] Release Apache Sling ##RELEASE_NAME##\n" + 
+            "\n" + 
+            "Hi,\n" + 
+            "\n" + 
+            "We solved ##FIXED_ISSUES_COUNT## issues in this release:\n" + 
+            "https://issues.apache.org/jira/browse/SLING/fixforversion/##VERSION_ID##\n" + 
+            "\n" + 
+            "Staging repository:\n" + 
+            "https://repository.apache.org/content/repositories/orgapachesling-##RELEASE_ID##/\n" + 
+            "\n" + 
+            "You can use this UNIX script to download the release and verify the signatures:\n" + 
+            "https://gitbox.apache.org/repos/asf?p=sling-tooling-release.git;a=blob;f=check_staged_release.sh;hb=HEAD\n" + 
+            "\n" + 
+            "Usage:\n" + 
+            "sh check_staged_release.sh ##RELEASE_ID## /tmp/sling-staging\n" + 
+            "\n" + 
+            "Please vote to approve this release:\n" + 
+            "\n" + 
+            "  [ ] +1 Approve the release\n" + 
+            "  [ ]  0 Don't care\n" + 
+            "  [ ] -1 Don't release, because ...\n" + 
+            "\n" + 
+            "This majority vote is open for at least 72 hours.\n";
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+    
+    @Reference
+    private StagingRepositoryFinder repoFinder;
+    
+    @Reference
+    private VersionFinder versionFinder;
+
+    @Override
+    public void execute(String target) {
+        try {
+            int repoId = Integer.parseInt(target);
+            StagingRepository repo = repoFinder.find(repoId);
+            String cleanVersion = getCleanVersion(repo.getDescription());
+            Version version = versionFinder.find(cleanVersion);
+            
+            String emailContents = EMAIL_TEMPLATE
+                    .replace("##RELEASE_NAME##", cleanVersion)
+                    .replace("##RELEASE_ID##", String.valueOf(repoId))
+                    .replace("##VERSION_ID##", String.valueOf(version.getId()))
+                    .replace("##FIXED_ISSUES_COUNT##", String.valueOf(version.getIssuesFixedCount()));
+                    
+            logger.info(emailContents);
+
+        } catch (IOException e) {
+            logger.warn("Failed executing command", e);
+        }
+    }
+
+    static String getCleanVersion(String repoDescription) {
+        return repoDescription
+                .replace("Apache Sling ", "") // Apache Sling prefix
+                .replaceAll(" RC[0-9]*$", ""); // 'release candidate' suffix 
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
new file mode 100644
index 0000000..690a4d2
--- /dev/null
+++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java
@@ -0,0 +1,39 @@
+/*
+ * 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.cli.impl.release;
+
+import org.apache.sling.cli.impl.Command;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(service = Command.class, property = {
+    Command.PROPERTY_NAME_COMMAND+"=release",
+    Command.PROPERTY_NAME_SUBCOMMAND+"=tally-votes",
+    Command.PROPERTY_NAME_SUMMARY+"=Counts votes cast for a release and generates the result email"
+})
+public class TallyVotesCommand implements Command {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void execute(String target) {
+        logger.info("Tallying votes for release {}", target);
+
+    }
+
+}
diff --git a/src/main/resources/conf/logback-default.xml b/src/main/resources/conf/logback-default.xml
new file mode 100644
index 0000000..8f2963f
--- /dev/null
+++ b/src/main/resources/conf/logback-default.xml
@@ -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. -->
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <logger name="org.apache.sling.cli" level="INFO" />
+
+    <root level="WARN">
+        <appender-ref ref="STDOUT" />
+    </root>
+</configuration>
\ No newline at end of file
diff --git a/src/main/resources/scripts/launcher.sh b/src/main/resources/scripts/launcher.sh
new file mode 100755
index 0000000..6f0bcfb
--- /dev/null
+++ b/src/main/resources/scripts/launcher.sh
@@ -0,0 +1,29 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------------------
+
+# TODO - contribute '-q' flag to launcher OR allow passthrough of org.slf4j.simpleLogger system properties
+
+
+# funky syntax needed to properly preserve arguments with whitespace
+ARGS_PROP="exec.args=$@"
+
+# Use exec to become pid 1, see https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
+exec /usr/bin/java \
+	 -Dorg.slf4j.simpleLogger.logFile=/dev/null \
+	 -Dlogback.configurationFile=file:/usr/share/sling-cli/conf/logback-default.xml \
+	 -jar /usr/share/sling-cli/launcher/org.apache.sling.feature.launcher.jar \
+	 -f /usr/share/sling-cli/sling-cli.feature \
+	 -c /usr/share/sling-cli/artifacts \
+	 -D "$ARGS_PROP" \
+	 -V "asf.username=${ASF_USERNAME}" \
+	 -V "asf.password=${ASF_PASSWORD}"
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java b/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
new file mode 100644
index 0000000..8dd81aa
--- /dev/null
+++ b/src/test/java/org/apache/sling/cli/impl/release/PrepareVoteEmailCommandTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.cli.impl.release;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class PrepareVoteEmailCommandTest {
+
+    @Test
+    public void cleanVersion() {
+        
+        assertEquals("Resource Merger 1.3.10", 
+                PrepareVoteEmailCommand.getCleanVersion("Apache Sling Resource Merger 1.3.10 RC1"));
+    }
+}