You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by kw...@apache.org on 2022/06/24 16:53:46 UTC

[jackrabbit-filevault] branch master updated: JCRVLT-640 get rid of Sling Commons Classloader and JSON dependencies (#232)

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

kwin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jackrabbit-filevault.git


The following commit(s) were added to refs/heads/master by this push:
     new 18f8cdc1 JCRVLT-640 get rid of Sling Commons Classloader and JSON dependencies (#232)
18f8cdc1 is described below

commit 18f8cdc15d36d572647ad984f7b1606dd970d167
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Fri Jun 24 18:53:41 2022 +0200

    JCRVLT-640 get rid of Sling Commons Classloader and JSON dependencies (#232)
    
    Ignore false-positive warning for
    https://www.cve.org/CVERecord?id=CVE-2022-32549
    fix typo in pom.xml
---
 suppressions.xml                                   |   9 +-
 target-osgi-environment/pom.xml                    |  12 ---
 vault-core/pom.xml                                 |   4 +-
 vault-davex/pom.xml                                |   4 +-
 vault-hook-externalclass-test/pom.xml              |  15 ++++
 vault-rcp/bnd.bnd                                  |   2 +-
 vault-rcp/pom.xml                                  |  39 ++++----
 .../jackrabbit/vault/rcp/impl/RcpServlet.java      |  71 ++++++---------
 .../jackrabbit/vault/rcp/impl/TypedMapWrapper.java | 100 +++++++++++++++++++++
 .../vault/rcp/impl/TypedMapWrapperTest.java        |  46 ++++++++++
 vault-sync/pom.xml                                 |   4 +-
 vault-validation/pom.xml                           |   4 +-
 vault-vlt/pom.xml                                  |   6 +-
 13 files changed, 220 insertions(+), 96 deletions(-)

diff --git a/suppressions.xml b/suppressions.xml
index ac2b5813..95a81f1a 100644
--- a/suppressions.xml
+++ b/suppressions.xml
@@ -17,7 +17,7 @@
 <suppressions xmlns="https://jeremylong.github.io/DependencyCheck/dependency-suppression.1.3.xsd">
     <suppress>
        <notes><![CDATA[
-       Oak GAV must not apply to JR2 CPE (like for 
+       Oak GAV must not apply to JR2 CPE
        ]]></notes>
        <packageUrl regex="true">^pkg:maven/org\.apache\.jackrabbit/oak\-.*@.*$</packageUrl>
        <cpe>cpe:/a:apache:jackrabbit</cpe>
@@ -36,13 +36,6 @@
        <packageUrl regex="true">^pkg:maven/com\.h2database/h2@.*$</packageUrl>
        <vulnerabilityName>CVE-2018-14335</vulnerabilityName>
     </suppress>
-    <suppress>
-       <notes><![CDATA[
-       file name: org.apache.sling.commons.classloader-1.2.2.jar
-       ]]></notes>
-       <packageUrl regex="true">^pkg:maven/org\.apache\.sling/org\.apache\.sling\.commons\.classloader@.*$</packageUrl>
-       <cpe>cpe:/a:apache-ssl:apache-ssl</cpe>
-    </suppress>
     <suppress>
        <notes><![CDATA[
        false positive for Oak classes e.g. for CVE-2021-40690
diff --git a/target-osgi-environment/pom.xml b/target-osgi-environment/pom.xml
index 3c44ddd2..58357ae4 100644
--- a/target-osgi-environment/pom.xml
+++ b/target-osgi-environment/pom.xml
@@ -188,18 +188,6 @@
             <version>5.1.0</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.classloader</artifactId>
-            <version>1.4.2</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.json</artifactId>
-            <version>2.0.20</version>
-            <scope>provided</scope>
-        </dependency>
         <dependency>
             <groupId>javax.jcr</groupId>
             <artifactId>jcr</artifactId>
diff --git a/vault-core/pom.xml b/vault-core/pom.xml
index f0ef1868..0b8b5a71 100644
--- a/vault-core/pom.xml
+++ b/vault-core/pom.xml
@@ -67,8 +67,8 @@
                         <suppressionFile>${project.basedir}/../suppressions.xml</suppressionFile>
                         <!-- only check embedded dependencies, therefore exclude all external dependencies with vulnerabilities (provided by the container) -->
                         <excludes>
-                            <exlude>*:commons-io</exlude>
-                            <exlude>*:httpclient</exlude>
+                            <exclude>*:commons-io</exclude>
+                            <exclude>*:httpclient</exclude>
                         </excludes>
                     </configuration>
                 </plugin>
diff --git a/vault-davex/pom.xml b/vault-davex/pom.xml
index 267f05fe..24c400bd 100644
--- a/vault-davex/pom.xml
+++ b/vault-davex/pom.xml
@@ -98,8 +98,8 @@
                         <suppressionFile>${project.basedir}/../suppressions.xml</suppressionFile>
                         <!-- only check embedded dependencies, therefore exclude all external dependencies with vulnerabilities (provided by the container) -->
                         <excludes>
-                            <exlude>*:commons-io</exlude>
-                            <exlude>*:httpclient</exlude>
+                            <exclude>*:commons-io</exclude>
+                            <exclude>*:httpclient</exclude>
                         </excludes>
                     </configuration>
                 </plugin>
diff --git a/vault-hook-externalclass-test/pom.xml b/vault-hook-externalclass-test/pom.xml
index 9144ab9e..5dbfd4a4 100644
--- a/vault-hook-externalclass-test/pom.xml
+++ b/vault-hook-externalclass-test/pom.xml
@@ -42,6 +42,21 @@
     <!-- B U I L D   D E F I N I T I O N                                        -->
     <!-- ====================================================================== -->
     <build>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.owasp</groupId>
+                    <artifactId>dependency-check-maven</artifactId>
+                    <configuration>
+                        <suppressionFile>${project.basedir}/../suppressions.xml</suppressionFile>
+                        <!-- only check embedded dependencies, therefore exclude all external dependencies with vulnerabilities (provided by the container) -->
+                        <excludes>
+                            <exclude>*:org.apache.sling.api</exclude>
+                        </excludes>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
         <plugins>
             <!-- assemble dist -->
             <plugin>
diff --git a/vault-rcp/bnd.bnd b/vault-rcp/bnd.bnd
index 0fd45fec..5daae1b0 100644
--- a/vault-rcp/bnd.bnd
+++ b/vault-rcp/bnd.bnd
@@ -19,7 +19,7 @@
                   @jackrabbit-spi2dav-[0-9.]*(-SNAPSHOT)?.jar,\
                   @jackrabbit-jcr-client-[0-9.]*(-SNAPSHOT)?.jar,\
                   @jackrabbit-jcr2spi-[0-9.]*(-SNAPSHOT)?.jar
--conditionalpackage: org.apache.jackrabbit.util,org.apache.jackrabbit.api.security
+-conditionalpackage: org.apache.jackrabbit.util,org.apache.jackrabbit.api.security,org.apache.felix.utils.json
 # make sure to only export the relevant packages (https://github.com/bndtools/bnd/issues/4219)
 -exportcontents: org.apache.jackrabbit.vault.rcp
 # due to Jackrabbit2 JARs poorly designed for OSGi, only export the class used in the API from package "org.apache.jackrabbit.spi2dav", as the other classes in that package have lots of private references
diff --git a/vault-rcp/pom.xml b/vault-rcp/pom.xml
index 6eb3eb9a..dd46f7cb 100644
--- a/vault-rcp/pom.xml
+++ b/vault-rcp/pom.xml
@@ -52,10 +52,11 @@
                         <suppressionFile>${project.basedir}/../suppressions.xml</suppressionFile>
                         <!-- only check embedded dependencies, therefore exclude all external dependencies with vulnerabilities (provided by the container) -->
                         <excludes>
-                            <exlude>*:commons-io</exlude>
-                            <exlude>*:httpclient</exlude>
-                            <exlude>*:jackson-core</exlude>
-                            <exlude>*:jackson-databind</exlude>
+                            <exclude>*:commons-io</exclude>
+                            <exclude>*:httpclient</exclude>
+                            <exclude>*:jackson-core</exclude>
+                            <exclude>*:jackson-databind</exclude>
+                            <exclude>*:org.apache.sling.api</exclude>
                         </excludes>
                     </configuration>
                 </plugin>
@@ -140,24 +141,7 @@
             <artifactId>slf4j-api</artifactId>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.json</artifactId>
-            <version>2.0.6</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.commons.classloader</artifactId>
-            <version>1.2.2</version>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.api</artifactId>
-            <version>2.16.4</version>
-            <scope>provided</scope>
-        </dependency>
+        
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
@@ -174,6 +158,17 @@
             <artifactId>org.osgi.service.cm</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <version>1.11.8</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.api</artifactId>
+            <version>2.16.4</version>
+            <scope>provided</scope>
+        </dependency>
         <!-- OSGi Annotations: @Version -->
         <dependency>
             <groupId>org.osgi</groupId>
diff --git a/vault-rcp/src/main/java/org/apache/jackrabbit/vault/rcp/impl/RcpServlet.java b/vault-rcp/src/main/java/org/apache/jackrabbit/vault/rcp/impl/RcpServlet.java
index 21e06a98..3a478ecc 100644
--- a/vault-rcp/src/main/java/org/apache/jackrabbit/vault/rcp/impl/RcpServlet.java
+++ b/vault-rcp/src/main/java/org/apache/jackrabbit/vault/rcp/impl/RcpServlet.java
@@ -21,7 +21,6 @@ import java.io.IOException;
 import java.io.Writer;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
-import java.util.LinkedList;
 import java.util.List;
 
 import javax.jcr.Credentials;
@@ -32,6 +31,8 @@ import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.commons.io.IOUtils;
+import org.apache.felix.utils.json.JSONParser;
+import org.apache.felix.utils.json.JSONWriter;
 import org.apache.jackrabbit.spi2dav.ConnectionOptions;
 import org.apache.jackrabbit.vault.fs.api.RepositoryAddress;
 import org.apache.jackrabbit.vault.fs.api.WorkspaceFilter;
@@ -41,14 +42,9 @@ import org.apache.jackrabbit.vault.rcp.RcpTaskManager;
 import org.apache.sling.api.SlingHttpServletRequest;
 import org.apache.sling.api.SlingHttpServletResponse;
 import org.apache.sling.api.servlets.SlingAllMethodsServlet;
-import org.apache.sling.commons.json.JSONArray;
-import org.apache.sling.commons.json.JSONException;
-import org.apache.sling.commons.json.JSONObject;
-import org.apache.sling.commons.json.io.JSONWriter;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
 import org.osgi.service.component.annotations.Activate;
 import org.osgi.service.component.annotations.Component;
 import org.osgi.service.component.annotations.Reference;
@@ -124,7 +120,6 @@ public class RcpServlet extends SlingAllMethodsServlet {
             } else {
                 String taskId = request.getRequestPathInfo().getSuffix();
                 JSONWriter w = new JSONWriter(response.getWriter());
-                w.setTidy(true);
     
                 if (taskId != null) {
                     taskId = taskId.substring(1);
@@ -146,14 +141,13 @@ public class RcpServlet extends SlingAllMethodsServlet {
                     w.endObject();
                 }
             }
-        } catch (JSONException e) {
-            throw new IOException(e.toString());
+        } catch (IOException e) {
+            throw new ServletException("Error writing JSON", e);
         }
     }
 
-    private void writeInfoJson(Writer writer) throws JSONException {
+    private void writeInfoJson(Writer writer) throws IOException {
         JSONWriter w = new JSONWriter(writer);
-        w.setTidy(true);
         w.object();
         w.key(Constants.BUNDLE_SYMBOLICNAME).value(bundle.getSymbolicName());
         w.key(Constants.BUNDLE_VERSION).value(bundle.getVersion().toString());
@@ -164,12 +158,11 @@ public class RcpServlet extends SlingAllMethodsServlet {
     @Override
     protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response)
             throws ServletException, IOException {
-        String json = IOUtils.toString(request.getReader());
-        JSONObject data;
+        TypedMapWrapper data;
         try {
-            data = new JSONObject(json);
-        } catch (JSONException e) {
-            log.error("Error while reading json: {}", e.toString());
+            data = new TypedMapWrapper(new JSONParser(request.getInputStream()).getParsed());
+        } catch (IOException e) {
+            log.error("Error while reading json", e);
             response.setStatus(500);
             return;
         }
@@ -205,14 +198,14 @@ public class RcpServlet extends SlingAllMethodsServlet {
                     creds = createCredentials(srcCreds);
                 }
                 Boolean recursive = null;
-                if (data.has(PARAM_RECURSIVE)) {
+                if (data.containsKey(PARAM_RECURSIVE)) {
                     recursive = data.optBoolean(PARAM_RECURSIVE, false);
                 }
 
                 ConnectionOptions.Builder connectionOptionsBuilder = ConnectionOptions.builder();
-                connectionOptionsBuilder.useSystemProperties(data.optBoolean(PARAM_USE_SYSTEM_PROPERTIES));
-                connectionOptionsBuilder.allowSelfSignedCertificates(data.optBoolean(PARAM_ALLOW_SELF_SIGNED_CERTIFICATE));
-                connectionOptionsBuilder.disableHostnameVerification(data.optBoolean(PARAM_DISABLE_HOSTNAME_VERIFICATION));
+                connectionOptionsBuilder.useSystemProperties(data.optBoolean(PARAM_USE_SYSTEM_PROPERTIES, false));
+                connectionOptionsBuilder.allowSelfSignedCertificates(data.optBoolean(PARAM_ALLOW_SELF_SIGNED_CERTIFICATE, false));
+                connectionOptionsBuilder.disableHostnameVerification(data.optBoolean(PARAM_DISABLE_HOSTNAME_VERIFICATION, false));
                 int connectionTimeoutMs = data.optInt(PARAM_CONNECTION_TIMEOUT_MS, -1);
                 connectionOptionsBuilder.connectionTimeoutMs(connectionTimeoutMs);
                 int requestTimeoutMs = data.optInt(PARAM_REQUEST_TIMEOUT_MS, -1);
@@ -220,27 +213,23 @@ public class RcpServlet extends SlingAllMethodsServlet {
                 int socketTimeoutMs = data.optInt(PARAM_SOCKET_TIMEOUT_MS, -1);
                 connectionOptionsBuilder.socketTimeoutMs(socketTimeoutMs);
 
-                if (data.has(PARAM_PROXY_HOST)) {
+                if (data.containsKey(PARAM_PROXY_HOST)) {
                     connectionOptionsBuilder.proxyHost(data.getString(PARAM_PROXY_HOST));
-                    if (data.has(PARAM_PROXY_PORT)) {
+                    if (data.containsKey(PARAM_PROXY_PORT)) {
                         connectionOptionsBuilder.proxyPort(data.getInt(PARAM_PROXY_PORT));
                     }
-                    if (data.has(PARAM_PROXY_PROTOCOL)) {
+                    if (data.containsKey(PARAM_PROXY_PROTOCOL)) {
                         connectionOptionsBuilder.proxyProtocol(data.getString(PARAM_PROXY_PROTOCOL));
                     }
-                    if (data.has(PARAM_PROXY_USERNAME)) {
+                    if (data.containsKey(PARAM_PROXY_USERNAME)) {
                         connectionOptionsBuilder.proxyUsername(data.getString(PARAM_PROXY_USERNAME));
-                        if (data.has(PARAM_PROXY_PASSWORD)) {
+                        if (data.containsKey(PARAM_PROXY_PASSWORD)) {
                             connectionOptionsBuilder.proxyPassword(data.getString(PARAM_PROXY_PASSWORD));
                         }
                     }
                 }
-                if (data.has(PARAM_EXCLUDES)) {
-                    List<String> excludeList = new LinkedList<>();
-                    JSONArray excludes = data.getJSONArray(PARAM_EXCLUDES);
-                    for (int idx = 0; idx < excludes.length(); idx++) {
-                        excludeList.add(excludes.getString(idx));
-                    }
+                if (data.containsKey(PARAM_EXCLUDES)) {
+                    List<String> excludeList = data.getStringList(PARAM_EXCLUDES);
                     if (isEdit) {
                         task = taskMgr.editTask(id, address, connectionOptionsBuilder.build(), creds, dst, excludeList, null, recursive);
                     } else {
@@ -248,7 +237,7 @@ public class RcpServlet extends SlingAllMethodsServlet {
                     }
                 } else {
                     final WorkspaceFilter filter;
-                    if (data.has(PARAM_FILTER)) {
+                    if (data.containsKey(PARAM_FILTER)) {
                         DefaultWorkspaceFilter filterImpl = new DefaultWorkspaceFilter();
                         filterImpl.load(IOUtils.toInputStream(data.getString(PARAM_FILTER), StandardCharsets.UTF_8));
                         filter = filterImpl;
@@ -263,22 +252,22 @@ public class RcpServlet extends SlingAllMethodsServlet {
                 }
 
                 // add additional data
-                if (data.has(PARAM_BATCHSIZE)) {
+                if (data.containsKey(PARAM_BATCHSIZE)) {
                     task.getRcp().setBatchSize((int) data.getLong(PARAM_BATCHSIZE));
                 }
-                if (data.has(PARAM_UPDATE)) {
+                if (data.containsKey(PARAM_UPDATE)) {
                     task.getRcp().setUpdate(data.optBoolean(PARAM_UPDATE, false));
                 }
-                if (data.has(PARAM_ONLY_NEWER)) {
+                if (data.containsKey(PARAM_ONLY_NEWER)) {
                     task.getRcp().setOnlyNewer(data.optBoolean(PARAM_ONLY_NEWER, false));
                 }
-                if (data.has(PARAM_NO_ORDERING)) {
+                if (data.containsKey(PARAM_NO_ORDERING)) {
                     task.getRcp().setNoOrdering(data.optBoolean(PARAM_NO_ORDERING, false));
                 }
-                if (data.has(PARAM_THROTTLE)) {
+                if (data.containsKey(PARAM_THROTTLE)) {
                     task.getRcp().setThrottle(data.getLong(PARAM_THROTTLE));
                 }
-                if (data.has(PARAM_RESUME_FROM)) {
+                if (data.containsKey(PARAM_RESUME_FROM)) {
                     task.getRcp().setResumeFrom(data.getString(PARAM_RESUME_FROM));
                 }
                 if (isEdit) {
@@ -340,7 +329,6 @@ public class RcpServlet extends SlingAllMethodsServlet {
             response.setContentType("application/json");
             response.setCharacterEncoding("utf-8");
             JSONWriter w = new JSONWriter(response.getWriter());
-            w.setTidy(true);
             w.object();
             w.key("status").value("ok");
             w.key("id").value(id);
@@ -352,13 +340,12 @@ public class RcpServlet extends SlingAllMethodsServlet {
             response.setCharacterEncoding("utf-8");
             response.setStatus(500);
             JSONWriter w = new JSONWriter(response.getWriter());
-            w.setTidy(true);
             try {
                 w.object();
                 w.key("status").value("error");
                 w.key("message").value("Error while executing '" + cmd + "': " + e.getMessage());
                 w.endObject();
-            } catch (JSONException e1) {
+            } catch (IOException e1) {
                 // ignore
             }
         }
@@ -377,7 +364,7 @@ public class RcpServlet extends SlingAllMethodsServlet {
         return creds;
     }
 
-    private static void write(JSONWriter w, RcpTask rcpTask) throws JSONException {
+    private static void write(JSONWriter w, RcpTask rcpTask) throws IOException {
         w.object();
         w.key(RcpServlet.PARAM_ID).value(rcpTask.getId());
         w.key(RcpServlet.PARAM_SRC).value(rcpTask.getSource().toString());
diff --git a/vault-rcp/src/main/java/org/apache/jackrabbit/vault/rcp/impl/TypedMapWrapper.java b/vault-rcp/src/main/java/org/apache/jackrabbit/vault/rcp/impl/TypedMapWrapper.java
new file mode 100644
index 00000000..94dc62b3
--- /dev/null
+++ b/vault-rcp/src/main/java/org/apache/jackrabbit/vault/rcp/impl/TypedMapWrapper.java
@@ -0,0 +1,100 @@
+/*
+ * 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.jackrabbit.vault.rcp.impl;
+
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Adds helper methods to a wrapped Map containing objects of different types.
+ * Those additional getter methods allow to retrieve a specific typed value.
+ * The method names are inspired by <a href="https://sling.apache.org/apidocs/sling8/org/apache/sling/commons/json/JSONObject.html">org.apache.sling.commons.json.JSONObject</a>.
+ */
+public class TypedMapWrapper extends AbstractMap<String,Object> implements Map<String, Object> {
+
+    private final Map<String, Object> wrappedMap;
+    public TypedMapWrapper(Map<String, Object> wrappedMap) {
+        this.wrappedMap = wrappedMap;
+    }
+
+    @Override
+    public Set<Entry<String, Object>> entrySet() {
+        return wrappedMap.entrySet();
+    }
+
+    public String getString(String key) {
+        return getTypedOrThrow(key, String.class);
+    }
+
+    public String optString(String key, String defaultValue) {
+        return getTypedOrDefault(key, String.class, defaultValue);
+    }
+
+    public List<String> getStringList(String key) {
+        List<?> objects = getTypedOrThrow(key, List.class);
+        return objects.stream().map( o -> {
+            if (o instanceof String) {
+                return String.class.cast(o);
+            }
+            throw new IllegalArgumentException("List does not contain out of strings");
+        }).collect(Collectors.toList());
+    }
+
+    public long getLong(String key) throws IllegalArgumentException {
+        return getTypedOrThrow(key, Long.class);
+    }
+
+    public boolean getBoolean(String key) throws IllegalArgumentException {
+        return getTypedOrThrow(key, Boolean.class);
+    }
+
+    public boolean optBoolean(String key, boolean defaultValue) {
+        return getTypedOrDefault(key, Boolean.class, defaultValue);
+    }
+
+    public int getInt(String key) throws IllegalArgumentException {
+        return getTypedOrThrow(key, Integer.class);
+    }
+
+    public int optInt(String key, int defaultValue) {
+        return getTypedOrDefault(key, Integer.class, defaultValue);
+    }
+
+    private <T> T getTypedOrDefault(String key, Class<T> clazz, T defaultValue) {
+        return getTyped(key, clazz).orElse(defaultValue);
+    }
+
+    private <T> T getTypedOrThrow(String key, Class<T> clazz) {
+        return getTyped(key, clazz).orElseThrow(() -> new IllegalArgumentException("Key " + key + " is unknown or value is no " + clazz));
+    }
+
+    private <T> Optional<T> getTyped(String key, Class<T> clazz) {
+        Object object = get(key);
+        if (clazz.isInstance(object)) {
+            return Optional.of(clazz.cast(object));
+        } else {
+            return Optional.empty();
+        }
+    }
+
+    
+}
diff --git a/vault-rcp/src/test/java/org/apache/jackrabbit/vault/rcp/impl/TypedMapWrapperTest.java b/vault-rcp/src/test/java/org/apache/jackrabbit/vault/rcp/impl/TypedMapWrapperTest.java
new file mode 100644
index 00000000..dc44d8a2
--- /dev/null
+++ b/vault-rcp/src/test/java/org/apache/jackrabbit/vault/rcp/impl/TypedMapWrapperTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.jackrabbit.vault.rcp.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.felix.utils.json.JSONParser;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+public class TypedMapWrapperTest {
+
+    @Test
+    public void testParsedJson() throws IOException {
+        try (InputStream is = this.getClass().getResourceAsStream("/edit_task.json")) {
+            assertNotNull(is);
+            TypedMapWrapper map = new TypedMapWrapper(new JSONParser(is).getParsed());
+            assertTrue(map.getBoolean("onlyNewer"));
+            assertEquals("copy-geometrixx",map.getString("id"));
+            MatcherAssert.assertThat(map.getStringList("excludes"), Matchers.contains("/content/geometrixx/en/tools(/.*)?"));
+            assertThrows(IllegalArgumentException.class, () -> map.getString("invalid")); // no key with that name
+            assertThrows(IllegalArgumentException.class, () -> map.getString("onlyNewer")); // value of different type
+        }
+    }
+}
diff --git a/vault-sync/pom.xml b/vault-sync/pom.xml
index f9c48a4f..1f50ec06 100644
--- a/vault-sync/pom.xml
+++ b/vault-sync/pom.xml
@@ -54,8 +54,8 @@
                         <suppressionFile>${project.basedir}/../suppressions.xml</suppressionFile>
                         <!-- only check embedded dependencies, therefore exclude all external dependencies with vulnerabilities (provided by the container) -->
                         <excludes>
-                            <exlude>*:commons-io</exlude>
-                            <exlude>*:guava</exlude>
+                            <exclude>*:commons-io</exclude>
+                            <exclude>*:guava</exclude>
                         </excludes>
                     </configuration>
                 </plugin>
diff --git a/vault-validation/pom.xml b/vault-validation/pom.xml
index f9f453ed..5c9a499b 100644
--- a/vault-validation/pom.xml
+++ b/vault-validation/pom.xml
@@ -49,8 +49,8 @@
                         <suppressionFile>${project.basedir}/../suppressions.xml</suppressionFile>
                         <!-- only check embedded dependencies, therefore exclude all external dependencies with vulnerabilities (provided by the container) -->
                         <excludes>
-                            <exlude>*:commons-io</exlude>
-                            <exlude>*:httpclient</exlude>
+                            <exclude>*:commons-io</exclude>
+                            <exclude>*:httpclient</exclude>
                         </excludes>
                     </configuration>
                 </plugin>
diff --git a/vault-vlt/pom.xml b/vault-vlt/pom.xml
index 99f27a05..d8462e40 100644
--- a/vault-vlt/pom.xml
+++ b/vault-vlt/pom.xml
@@ -95,9 +95,9 @@
                         <suppressionFile>${project.basedir}/../suppressions.xml</suppressionFile>
                         <!-- only check embedded dependencies, therefore exclude all external dependencies with vulnerabilities (provided by the container) -->
                         <excludes>
-                            <exlude>*:commons-io</exlude>
-                            <exlude>*:httpclient</exlude>
-                            <exlude>*:log4j</exlude>
+                            <exclude>*:commons-io</exclude>
+                            <exclude>*:httpclient</exclude>
+                            <exclude>*:log4j</exclude>
                         </excludes>
                     </configuration>
                 </plugin>