You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2020/02/28 18:13:29 UTC

[sling-scriptingbundle-maven-plugin] 01/01: SLING-9161 - The Scripting Bundle Maven Plugin should allow more flexible ways for the project structure

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

radu pushed a commit to branch issue/SLING-9161
in repository https://gitbox.apache.org/repos/asf/sling-scriptingbundle-maven-plugin.git

commit a1d1aa77a178f750063657d29ab60a917a4d869c
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Fri Feb 28 19:12:27 2020 +0100

    SLING-9161 - The Scripting Bundle Maven Plugin should allow more flexible ways for the project structure
    
    * started refactoring (WIP)
---
 pom.xml                                            | 163 +++++++++++++--
 .../scriptingbundle/maven/plugin/Capabilities.java |  41 ++++
 .../scriptingbundle/maven/plugin/MetadataMojo.java | 227 +++++++++++++++++++++
 .../maven/plugin/ProvidedCapability.java           | 134 ++++++++++++
 .../maven/plugin/RequiredCapability.java           |  78 +++++++
 .../maven/plugin/ResourceTypeFolderAnalyser.java   | 190 +++++++++++++++++
 .../maven/plugin/ResourceTypeFolderPredicate.java  |  76 +++++++
 .../maven/plugin/ScriptingMavenPlugin.java         | 213 -------------------
 .../plugin/MetadataMojoCapabilityFindingTest.java  | 148 ++++++++++++++
 .../maven/plugin/MetadataMojoTest.java             |  79 +++++++
 .../maven/plugin/ScriptingMavenPluginTest.java     |  80 --------
 src/test/resources/project-1/pom.xml               |  46 +++++
 .../org.apache.sling.foobar/1.0.0/extends          |   1 +
 .../org.apache.sling.foobar/1.0.0/foobar.html      |   0
 .../org.apache.sling.foobar/1.0.0/requires         |   1 +
 .../javax.script/org.apache.sling.foobar/GET.html  |   0
 .../org/apache/sling/bar/1.0.0/bar.html            |   0
 .../org/apache/sling/foo/foo.GET.html.html         |  19 ++
 src/test/resources/spotbugs-exclude.xml            |  24 +++
 19 files changed, 1215 insertions(+), 305 deletions(-)

diff --git a/pom.xml b/pom.xml
index d0d4654..886c700 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,39 +39,178 @@
         </scm>
 
     <properties>
-        <sling.java.version>8</sling.java.version>
+        <sling.java.version>11</sling.java.version>
+        <maven.version>3.6.0</maven.version>
     </properties>
 
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-plugin-plugin</artifactId>
+                <version>${maven.version}</version>
+                <executions>
+                    <execution>
+                        <id>mojo-descriptor</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>descriptor</goal>
+                        </goals>
+                    </execution>
+                    <execution>
+                        <id>generated-helpmojo</id>
+                        <goals>
+                            <goal>helpmojo</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.codehaus.plexus</groupId>
+                <artifactId>plexus-component-metadata</artifactId>
+                <version>2.1.0</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>generate-metadata</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.rat</groupId>
+                <artifactId>apache-rat-plugin</artifactId>
+                <configuration>
+                    <excludes>
+                        <exclude>**/*.iml</exclude>
+                        <exclude>**/target/**/*</exclude>
+                        <exclude>src/site/markdown/**</exclude>
+                    </excludes>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-scm-publish-plugin</artifactId>
+                <configuration>
+                    <checkoutDirectory>${user.home}/maven-sites/${maven.site.path}</checkoutDirectory>
+                    <tryUpdate>true</tryUpdate>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>com.github.spotbugs</groupId>
+                <artifactId>spotbugs-maven-plugin</artifactId>
+                <version>3.1.12.2</version>
+                <configuration>
+                    <effort>Max</effort>
+                    <xmlOutput>true</xmlOutput>
+                    <excludeFilterFile>src/test/resources/spotbugs-exclude.xml</excludeFilterFile>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>find-bugs</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>check</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+            </plugin>
+        </plugins>
+    </build>
+
+    <!-- force maven-plugin-testing-harness to use newer plexus container (https://issues.apache.org/jira/browse/MPLUGINTESTING-53) -->
+    <dependencyManagement>
+        <dependencies>
+            <!-- maven -->
+            <dependency>
+                <groupId>org.apache.maven</groupId>
+                <artifactId>maven-core</artifactId>
+                <version>${maven.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.maven</groupId>
+                <artifactId>maven-compat</artifactId>
+                <version>${maven.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.maven</groupId>
+                <artifactId>maven-model</artifactId>
+                <version>${maven.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.maven</groupId>
+                <artifactId>maven-plugin-api</artifactId>
+                <version>${maven.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.maven</groupId>
+                <artifactId>maven-aether-provider</artifactId>
+                <version>${maven.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
 
     <dependencies>
         <dependency>
-          <groupId>org.apache.maven</groupId>
-          <artifactId>maven-plugin-api</artifactId>
-          <version>3.0</version>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
         </dependency>
 
-        <!-- dependencies to annotations -->
         <dependency>
-          <groupId>org.apache.maven.plugin-tools</groupId>
-          <artifactId>maven-plugin-annotations</artifactId>
-          <version>3.4</version>
-          <scope>provided</scope>
+          <groupId>org.apache.maven</groupId>
+          <artifactId>maven-plugin-api</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
-            <artifactId>maven-project</artifactId>
-            <version>2.2.1</version>
+            <artifactId>maven-model</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-core</artifactId>
-            <version>3.5.0</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-compat</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven.plugin-tools</groupId>
+            <artifactId>maven-plugin-annotations</artifactId>
+            <version>3.4</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
             <version>7.0.0</version>
             <scope>compile</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.maven.plugin-testing</groupId>
+            <artifactId>maven-plugin-testing-harness</artifactId>
+            <version>3.3.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>3.3.0</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.sonatype.plexus</groupId>
+            <artifactId>plexus-build-api</artifactId>
+            <version>0.0.7</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/Capabilities.java b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/Capabilities.java
new file mode 100644
index 0000000..b885fb7
--- /dev/null
+++ b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/Capabilities.java
@@ -0,0 +1,41 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scriptingbundle.maven.plugin;
+
+import java.util.Collections;
+import java.util.List;
+
+public class Capabilities {
+
+    private final List<ProvidedCapability> providedCapabilities;
+    private final List<RequiredCapability> requiredCapabilities;
+
+    Capabilities(List<ProvidedCapability> providedCapabilities, List<RequiredCapability> requiredCapabilities) {
+        this.providedCapabilities = providedCapabilities;
+        this.requiredCapabilities = requiredCapabilities;
+    }
+
+    public List<ProvidedCapability> getProvidedCapabilities() {
+        return Collections.unmodifiableList(providedCapabilities);
+    }
+
+    public List<RequiredCapability> getRequiredCapabilities() {
+        return Collections.unmodifiableList(requiredCapabilities);
+    }
+}
diff --git a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojo.java b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojo.java
new file mode 100644
index 0000000..02109e1
--- /dev/null
+++ b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojo.java
@@ -0,0 +1,227 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scriptingbundle.maven.plugin;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.shared.utils.io.DirectoryScanner;
+import org.osgi.framework.Version;
+
+/**
+ * The {@code metadata} goal will generate two Maven project properties, namely {@code org.apache.sling.scriptingbundle.maven.plugin
+ * .Require-Capability} and {@code org.apache.sling.scriptingbundle.maven.plugin.Provide-Capability} which can be used to generate the
+ * corresponding OSGi bundle headers for bundles providing scripts executable by a {@link javax.script.ScriptEngine}.
+ */
+@Mojo(name = "metadata",
+      defaultPhase = LifecyclePhase.PACKAGE)
+public class MetadataMojo extends AbstractMojo {
+
+    @Parameter(defaultValue = "${project}",
+               readonly = true)
+    private MavenProject project;
+
+    @Parameter(defaultValue = "${session}",
+               readonly = true)
+    private MavenSession session;
+
+    /**
+     * Defines where this goal will look for scripts in the project.
+     *
+     * @since 0.1.0
+     */
+    @Parameter(defaultValue = "${project.basedir}/src/main/resources/javax.script")
+    private String scriptsDirectory;
+
+    static final Set<String> METHODS = new HashSet<>(Arrays.asList("TRACE", "OPTIONS", "GET", "HEAD", "POST", "PUT",
+            "DELETE", "PATCH"));
+    static final String EXTENDS_FILE = "extends";
+    static final String REQUIRES_FILE = "requires";
+
+    private final Log log;
+
+    public MetadataMojo() {
+        log = getLog();
+    }
+
+    public void execute() throws MojoExecutionException {
+        File sdFile = new File(scriptsDirectory);
+        if (!sdFile.exists()) {
+            sdFile = new File(project.getBasedir(), scriptsDirectory);
+            if (!sdFile.exists()) {
+                throw new MojoExecutionException("Cannot find file " + scriptsDirectory + ".");
+            }
+        }
+        final AtomicReference<File> scriptsDirectoryReference = new AtomicReference<>();
+        scriptsDirectoryReference.set(sdFile);
+        DirectoryScanner scanner = new DirectoryScanner();
+        scanner.setBasedir(sdFile);
+        scanner.setIncludes("**");
+        scanner.setExcludes("**/*.class");
+        scanner.addDefaultExcludes();
+        scanner.scan();
+
+        List<String> includedDirectories = Arrays.asList(scanner.getIncludedDirectories());
+        includedDirectories.sort(Collections.reverseOrder());
+        List<Path> resourceTypeDirectories =
+                includedDirectories.stream()
+                        .map(includedDirectory -> Paths.get(scriptsDirectoryReference.get().getAbsolutePath(), includedDirectory))
+                        .filter(new ResourceTypeFolderPredicate()).collect(Collectors.toList());
+        ResourceTypeFolderAnalyser analyser = new ResourceTypeFolderAnalyser(log, Paths.get(sdFile.getAbsolutePath()));
+        log.info("Detected resource type directories: " + resourceTypeDirectories);
+        for (Path resourceTypeDirectory : resourceTypeDirectories) {
+            Capabilities capabilities = analyser.getCapabilities(resourceTypeDirectory);
+            log.info(String.format("folder: %s, require: %s, provide: %s", resourceTypeDirectory.toString(),
+                    capabilities.getRequiredCapabilities(), capabilities.getProvidedCapabilities()));
+        }
+    }
+
+    ProvidedCapability getScript(String script) {
+        String[] parts = script.split(Pattern.quote(File.separator));
+        String scriptName = null;
+        String resourceType = null;
+        String scriptExtension = null;
+        String extension = null;
+        String method = null;
+        String version = null;
+        List<String> selectors = new ArrayList<>();
+        if (parts.length > 2) {
+            Version v = null;
+            int versionIndex = -1;
+            for (int i = parts.length - 1; i >= 1; i--) {
+                try {
+                    v = new Version(parts[i]);
+                    versionIndex = i;
+                } catch (IllegalArgumentException e) {
+                    // do nothing
+                }
+            }
+            if (v != null) {
+                version = v.toString();
+                resourceType = String.join("/", Arrays.copyOfRange(parts, 0, versionIndex - 1));
+                if (parts.length - versionIndex > 2) {
+                    Collections.addAll(selectors, Arrays.copyOfRange(parts, versionIndex + 1, parts.length - 1));
+                }
+            } else {
+                resourceType = String.join("/", Arrays.copyOfRange(parts, 0, versionIndex - 1));
+                if (parts.length - versionIndex > 2) {
+                    Collections.addAll(selectors, Arrays.copyOfRange(parts, versionIndex + 1, parts.length - 1));
+                }
+            }
+            scriptName = parts[parts.length - 1];
+            String[] scriptParts = scriptName.split("\\.");
+            if (scriptParts.length < 2) {
+                throw new IllegalStateException("Missing script extension.");
+            }
+            scriptExtension = scriptParts[scriptParts.length - 1];
+            String resourceTypeLabel;
+            int index = resourceType.lastIndexOf('.') == -1 ? resourceType.lastIndexOf('/') : -1;
+            if (index == -1) {
+                resourceTypeLabel = resourceType;
+            } else {
+                resourceTypeLabel = resourceType.substring(index + 1);
+            }
+            String first = scriptParts[0];
+            if (!first.equals(resourceTypeLabel)) {
+                int selectorsStart;
+                if (METHODS.contains(first)) {
+                    method = first;
+                    selectorsStart = 1;
+                } else {
+                    selectorsStart = 0;
+                }
+                for (int i = selectorsStart; i < scriptParts.length - 3; i++) {
+                    selectors.add(scriptParts[i]);
+                }
+                if (scriptParts.length >= 3) {
+                    extension = scriptParts[scriptParts.length - 2];
+                }
+            }
+        } else if (parts.length == 2) {
+            resourceType = parts[0];
+        }
+
+
+        if (parts.length >= 2) {
+            scriptName = parts[parts.length - 1];
+            Version v = null;
+            try {
+                if (parts.length > 2) {
+                    v = new Version(parts[parts.length - 2]);
+                }
+            } catch (IllegalArgumentException e) {
+                log.debug(String.format("No resource type version available for script %s.", script));
+            }
+            version = v == null ? null : v.toString();
+            resourceType = version == null ? String.join("/", Arrays.copyOfRange(parts, 0, parts.length - 1)) : String.join("/",
+                    Arrays.copyOfRange(parts, 0, parts.length - 2));
+            int idx = scriptName.lastIndexOf('.');
+            if (idx != -1) {
+                scriptExtension = scriptName.substring(idx + 1);
+                scriptName = scriptName.substring(0, idx);
+                if (scriptExtension.isEmpty()) {
+                    scriptExtension = null;
+                }
+            }
+
+            idx = scriptName.lastIndexOf('.');
+            if (idx != -1) {
+                extension = scriptName.substring(idx + 1);
+                scriptName = scriptName.substring(0, idx);
+                if (extension.isEmpty() || extension.equalsIgnoreCase("html")) {
+                    extension = null;
+                }
+            } else {
+                extension = null;
+            }
+
+            idx = scriptName.indexOf('.');
+            if (idx != -1) {
+                String methodString = scriptName.substring(0, idx).toUpperCase();
+                if (METHODS.contains(methodString)) {
+                    method = methodString;
+                    scriptName = scriptName.substring(idx + 1);
+                }
+            } else if (METHODS.contains(scriptName.toUpperCase())) {
+                method = scriptName.toUpperCase();
+            }
+        }
+//        return Capability.builder().withName(scriptName).withResourceType(resourceType).withScriptExtension(scriptExtension)
+//                .withVersion(version).withExtension(extension).withMethod(method).build();
+        return null;
+    }
+}
diff --git a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ProvidedCapability.java b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ProvidedCapability.java
new file mode 100644
index 0000000..70b304d
--- /dev/null
+++ b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ProvidedCapability.java
@@ -0,0 +1,134 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scriptingbundle.maven.plugin;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class ProvidedCapability {
+    private final String resourceType;
+    private final String extendsResourceType;
+    private final String version;
+    private final String requestExtension;
+    private final String requestMethod;
+
+    private ProvidedCapability(@NotNull String resourceType, @Nullable String extendsResourceType,
+                               @Nullable String version, @Nullable String requestExtension, @Nullable String requestMethod) {
+        this.resourceType = resourceType;
+        this.extendsResourceType = extendsResourceType;
+        this.version = version;
+        this.requestExtension = requestExtension;
+        this.requestMethod = requestMethod;
+    }
+
+    static Builder builder() {
+        return new Builder();
+    }
+
+    @NotNull
+    public String getResourceType() {
+        return resourceType;
+    }
+
+    @Nullable
+    public String getVersion() {
+        return version;
+    }
+
+    @Nullable
+    public String getRequestExtension() {
+        return requestExtension;
+    }
+
+    @Nullable
+    public String getExtendsResourceType() {
+        return extendsResourceType;
+    }
+
+    @Nullable
+    public String getRequestMethod() {
+        return requestMethod;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(resourceType, version, requestExtension, requestMethod);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof ProvidedCapability) {
+            ProvidedCapability other = (ProvidedCapability) obj;
+            return Objects.equals(resourceType, other.resourceType) && Objects.equals(version, other.version) && Objects.equals(requestExtension, other.requestExtension) && Objects.equals(requestMethod, other.requestMethod);
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s{resourceType=%s, version=%s, requestExtension=%s, requestMethod=%s}", this.getClass().getSimpleName(),
+                resourceType, version, requestExtension, requestMethod);
+    }
+
+    static class Builder {
+        private String resourceType;
+        private String extendsResourceType;
+        private String version;
+        private String requestExtension;
+        private String requestMethod;
+
+        Builder withResourceType(String resourceType) {
+            if (StringUtils.isEmpty(resourceType)) {
+                throw new NullPointerException("The script's resourceType cannot be null or empty.");
+            }
+            this.resourceType = resourceType;
+            return this;
+        }
+
+        Builder withExtendsResourceType(String extendsResourceType) {
+            this.extendsResourceType = extendsResourceType;
+            return this;
+        }
+
+        Builder withVersion(String version) {
+            this.version = version;
+            return this;
+        }
+
+        Builder withRequestExtension(String requestExtension) {
+            this.requestExtension = requestExtension;
+            return this;
+        }
+
+        Builder withRequestMethod(String requestMethod) {
+            this.requestMethod = requestMethod;
+            return this;
+        }
+
+        ProvidedCapability build() {
+            return new ProvidedCapability(resourceType, extendsResourceType, version, requestExtension, requestMethod);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/RequiredCapability.java b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/RequiredCapability.java
new file mode 100644
index 0000000..4c738bd
--- /dev/null
+++ b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/RequiredCapability.java
@@ -0,0 +1,78 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scriptingbundle.maven.plugin;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.VersionRange;
+
+public class RequiredCapability {
+
+    private final String resourceType;
+    private final VersionRange versionRange;
+
+    private RequiredCapability(@NotNull String resourceType, @Nullable VersionRange versionRange) {
+        this.resourceType = resourceType;
+        this.versionRange = versionRange;
+    }
+
+    static Builder builder() {
+        return new Builder();
+    }
+
+    @NotNull
+    public String getResourceType() {
+        return resourceType;
+    }
+
+    @Nullable
+    public VersionRange getVersionRange() {
+        return versionRange;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s{resourceType=%s, versionRange=%s}", this.getClass().getSimpleName(),
+                resourceType, versionRange);
+    }
+
+    static class Builder {
+        private String resourceType;
+        private VersionRange versionRange;
+
+        RequiredCapability build() {
+            return new RequiredCapability(resourceType, versionRange);
+        }
+
+        Builder withResourceType(String resourceType) {
+            if (StringUtils.isEmpty(resourceType)) {
+                throw new NullPointerException("The required resourceType cannot be null or empty.");
+            }
+            this.resourceType = resourceType;
+            return this;
+        }
+
+        Builder withVersionRange(VersionRange versionRange) {
+            this.versionRange = versionRange;
+            return this;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ResourceTypeFolderAnalyser.java b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ResourceTypeFolderAnalyser.java
new file mode 100644
index 0000000..a6cce74
--- /dev/null
+++ b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ResourceTypeFolderAnalyser.java
@@ -0,0 +1,190 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scriptingbundle.maven.plugin;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.plugin.logging.Log;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+
+
+class ResourceTypeFolderAnalyser {
+
+    private final Log log;
+    private final Path scriptsDirectory;
+
+    ResourceTypeFolderAnalyser(@NotNull Log log, @NotNull Path scriptsDirectory) {
+        this.log = log;
+        this.scriptsDirectory = scriptsDirectory;
+    }
+
+    Capabilities getCapabilities(@NotNull Path resourceTypeDirectory) {
+        var providedCapabilities = new ArrayList<ProvidedCapability>();
+        var requiredCapabilities = new ArrayList<RequiredCapability>();
+        String version = null;
+        String resourceType = null;
+        try {
+            version = Version.parseVersion(resourceTypeDirectory.getFileName().toString()).toString();
+        } catch (IllegalArgumentException ignored) {
+            // no version
+        }
+        if (StringUtils.isNotEmpty(version)) {
+            Path parent = resourceTypeDirectory.getParent();
+            if (parent != null) {
+                resourceType = getResourceType(parent);
+            }
+        } else {
+            resourceType = getResourceType(resourceTypeDirectory);
+        }
+        if (resourceType != null) {
+            String resourceTypeLabel = getResourceTypeLabel(resourceType);
+            try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(scriptsDirectory.resolve(resourceTypeDirectory))) {
+                for (Path child : directoryStream) {
+                    if (Files.isRegularFile(child)) {
+                        Path file = child.getFileName();
+                        if (file != null) {
+                            String[] scriptNameParts = file.toString().split("\\.");
+                            if (scriptNameParts.length == 1) {
+                                if (MetadataMojo.EXTENDS_FILE.equals(file.toString())) {
+                                    processExtendsFile(resourceType, version, child, providedCapabilities, requiredCapabilities);
+                                } else if (MetadataMojo.REQUIRES_FILE.equals(file.toString())) {
+                                    processRequiresFile(child, requiredCapabilities);
+                                }
+                            } else {
+
+                            }
+                        }
+                    } else if (Files.isDirectory(child)) {
+
+                    }
+                }
+            } catch (IOException | IllegalArgumentException e) {
+                log.warn(String.format("Cannot analyser folder %s.", scriptsDirectory.resolve(resourceTypeDirectory).toString()));
+                if (log.isDebugEnabled()) {
+                    log.debug(e);
+                }
+            }
+        }
+
+        return new Capabilities(providedCapabilities, requiredCapabilities);
+    }
+
+    private void processExtendsFile(@NotNull String resourceType, @Nullable String version, @NotNull Path extendsFile,
+                                    @NotNull ArrayList<ProvidedCapability> providedCapabilities,
+                                    @NotNull ArrayList<RequiredCapability> requiredCapabilities)
+            throws IOException {
+        List<String> extendResources = Files.readAllLines(extendsFile, StandardCharsets.UTF_8);
+        if (extendResources.size() == 1) {
+            String extend = extendResources.get(0);
+            if (StringUtils.isNotEmpty(extend)) {
+                String[] extendParts = extend.split(";");
+                String extendedResourceType = extendParts[0];
+                String extendedResourceTypeVersion = extendParts.length > 1 ? extendParts[1] : null;
+                providedCapabilities.add(
+                        ProvidedCapability.builder()
+                                .withResourceType(resourceType)
+                                .withVersion(version)
+                                .withExtendsResourceType(extendedResourceType)
+                                .build());
+                RequiredCapability.Builder requiredBuilder =
+                        RequiredCapability.builder().withResourceType(extendedResourceType);
+                try {
+                    if (extendedResourceTypeVersion != null) {
+                        requiredBuilder.withVersionRange(VersionRange.valueOf(extendedResourceTypeVersion));
+                    }
+                } catch (IllegalArgumentException ignored) {
+                    log.warn(String.format("Invalid version range '%s' defined for extended resourceType '%s'" +
+                                    " in file %s.", extendedResourceTypeVersion, extendedResourceType,
+                            extendsFile.toString()));
+                }
+                requiredCapabilities.add(requiredBuilder.build());
+            }
+        }
+    }
+
+    private void processRequiresFile(@NotNull Path requiresFile,
+                                    @NotNull ArrayList<RequiredCapability> requiredCapabilities)
+            throws IOException {
+        List<String> requiredResourceTypes = Files.readAllLines(requiresFile, StandardCharsets.UTF_8);
+        for (String requiredResourceType : requiredResourceTypes) {
+            if (StringUtils.isNotEmpty(requiredResourceType)) {
+                String[] requireParts = requiredResourceType.split(";");
+                String resourceType = requireParts[0];
+                String version = requireParts.length > 1 ? requireParts[1] : null;
+                RequiredCapability.Builder requiredBuilder =
+                        RequiredCapability.builder().withResourceType(resourceType);
+                try {
+                    if (version != null) {
+                        requiredBuilder.withVersionRange(VersionRange.valueOf(version));
+                    }
+                } catch (IllegalArgumentException ignored) {
+                    log.warn(String.format("Invalid version range '%s' defined for required resourceType '%s'" +
+                                    " in file %s.", version, resourceType,
+                            requiresFile.toString()));
+                }
+                requiredCapabilities.add(requiredBuilder.build());
+            }
+        }
+    }
+
+    private String getResourceType(@NotNull Path resourceTypeFolder) {
+        StringBuilder stringBuilder = new StringBuilder();
+        Path relativeResourceTypePath = scriptsDirectory.relativize(resourceTypeFolder);
+        if (StringUtils.isNotEmpty(relativeResourceTypePath.toString())) {
+            int parts = relativeResourceTypePath.getNameCount();
+            for (int i = 0; i < parts; i++) {
+                stringBuilder.append(relativeResourceTypePath.getName(i));
+                if (i < parts - 1) {
+                    stringBuilder.append('/');
+                }
+            }
+        }
+        return stringBuilder.toString();
+    }
+
+    private @NotNull String getResourceTypeLabel(@NotNull String resourceType) {
+        String resourceTypeLabel = null;
+        if (resourceType.contains("/")) {
+            int lastIndex = resourceType.lastIndexOf('/');
+            if (lastIndex < resourceType.length() - 2) {
+                resourceTypeLabel = resourceType.substring(++lastIndex);
+            }
+        } else if (resourceType.contains(".")) {
+            int lastIndex = resourceType.lastIndexOf('.');
+            if (lastIndex < resourceType.length() - 2) {
+                resourceTypeLabel = resourceType.substring(++lastIndex);
+            }
+        }
+        if (StringUtils.isEmpty(resourceTypeLabel)) {
+            throw new IllegalArgumentException(String.format("Resource type '%s' does not provide a resourceTypeLabel.", resourceType));
+        }
+        return resourceTypeLabel;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ResourceTypeFolderPredicate.java b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ResourceTypeFolderPredicate.java
new file mode 100644
index 0000000..f84b4b5
--- /dev/null
+++ b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ResourceTypeFolderPredicate.java
@@ -0,0 +1,76 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scriptingbundle.maven.plugin;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.function.Predicate;
+
+import org.osgi.framework.Version;
+
+public class ResourceTypeFolderPredicate implements Predicate<Path> {
+
+    @Override
+    public boolean test(Path folder) {
+        if (folder == null) {
+            return false;
+        }
+        Path lastSegment = folder.getFileName();
+        if (lastSegment == null) {
+            return false;
+        }
+        try {
+            Version.parseVersion(lastSegment.toString());
+            Path parent = folder.getParent();
+            if (parent != null) {
+                lastSegment = parent.getFileName();
+            }
+        } catch (IllegalArgumentException ignored) {
+            // last segment does not denote a version
+        }
+        String resourceTypeLabel;
+        if (lastSegment != null) {
+            String lastSegmentString = lastSegment.toString();
+            int lastDotIndex = lastSegmentString.lastIndexOf('.');
+            if (lastDotIndex != -1 && lastDotIndex < lastSegmentString.length() - 1) {
+                resourceTypeLabel = lastSegmentString.substring(++lastDotIndex);
+            } else {
+                resourceTypeLabel = lastSegmentString;
+            }
+            try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(folder, Files::isRegularFile)) {
+                for (Path path : directoryStream) {
+                    Path fileName = path.getFileName();
+                    if (fileName != null) {
+                        String childName = fileName.toString();
+                        String scriptName = childName.indexOf('.') != -1 ? childName.substring(0, childName.indexOf('.')) : null;
+                        if (resourceTypeLabel.equals(scriptName) || MetadataMojo.EXTENDS_FILE.equals(childName) ||
+                                MetadataMojo.METHODS.contains(scriptName)) {
+                            return true;
+                        }
+                    }
+                }
+            } catch (IOException ignored) {
+                // ignore; will return false anyways
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ScriptingMavenPlugin.java b/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ScriptingMavenPlugin.java
deleted file mode 100644
index 1c13d5e..0000000
--- a/src/main/java/org/apache/sling/scriptingbundle/maven/plugin/ScriptingMavenPlugin.java
+++ /dev/null
@@ -1,213 +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.scriptingbundle.maven.plugin;
-
-import org.apache.commons.lang3.StringEscapeUtils;
-import org.apache.maven.execution.MavenSession;
-import org.apache.maven.plugin.AbstractMojo;
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugins.annotations.LifecyclePhase;
-import org.apache.maven.plugins.annotations.Mojo;
-import org.apache.maven.plugins.annotations.Parameter;
-import org.apache.maven.project.MavenProject;
-import org.apache.maven.shared.utils.io.DirectoryScanner;
-import org.osgi.framework.Constants;
-import org.osgi.framework.Version;
-import org.osgi.framework.VersionRange;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.regex.Pattern;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-@Mojo(name = "metadata",
-      defaultPhase = LifecyclePhase.PACKAGE)
-public class ScriptingMavenPlugin extends AbstractMojo {
-
-    @Parameter(defaultValue = "${project}",
-               readonly = true)
-    private MavenProject project;
-
-    @Parameter(defaultValue = "${session}",
-               readonly = true)
-    private MavenSession session;
-
-    @Parameter(defaultValue = "${project.basedir}/src/main/resources/javax.script")
-    private String scriptsDirectory;
-
-    private static final Set<String> METHODS = new HashSet<>(Arrays.asList(new String[]{"TRACE", "OPTIONS", "GET", "HEAD", "POST", "PUT",
-            "DELETE", "PATCH"}));
-
-    private static final Set<String> FILE_SEPARATORS = new HashSet<>(Arrays.asList("\\", "/"));
-
-    public void execute() throws MojoExecutionException {
-        File sdFile = new File(scriptsDirectory);
-        if (!sdFile.exists()) {
-            sdFile = new File(project.getBasedir(), scriptsDirectory);
-            if (!sdFile.exists()) {
-                throw new MojoExecutionException("Cannot find file " + scriptsDirectory + ".");
-            }
-        }
-        final AtomicReference<File> scriptsDirectoryReference = new AtomicReference<>();
-        scriptsDirectoryReference.set(sdFile);
-        ;
-        DirectoryScanner scanner = new DirectoryScanner();
-        scanner.setBasedir(sdFile);
-        scanner.setIncludes("**");
-        scanner.setExcludes("**/*.class");
-        scanner.addDefaultExcludes();
-        scanner.scan();
-
-        List<String> scriptPaths = Stream.of(scanner.getIncludedFiles()).map(path -> new File(scriptsDirectoryReference.get(), path))
-                .map(file -> file.getPath().substring((scriptsDirectoryReference.get().getPath() + File.pathSeparatorChar).length()))
-                .collect(Collectors.toList());
-
-
-        List<String> requires = new ArrayList<>();
-
-        List<String> capabilities = new ArrayList<>();
-        for (String scriptPath : scriptPaths) {
-            Script script = getScripts(scriptPath);
-
-            String capability = "sling.resourceType;sling.resourceType=\"" + script.rt.replace("\"", "\\\"") + "\"";
-
-            if (!(script.rt.equals(script.name) || script.rt.endsWith("." + script.name) || script.name.isEmpty())) {
-                if (!script.name.equalsIgnoreCase("requires")) {
-                    if (!script.name.equalsIgnoreCase("extends")) {
-                        capability += ";sling.resourceType.selectors:List<String>=\"" + script.name.replace("\"", "\\\"") + "\"";
-                    } else {
-                        try (BufferedReader input = new BufferedReader(
-                                new FileReader(new File(scriptsDirectoryReference.get(), scriptPath)))) {
-                            String extend = input.readLine();
-
-                            capability += ";extends=\"" + extend.split(";")[0].replace("\"", "\\\"") + "\"";
-                            requires.add(extend + ";extends=true");
-                        } catch (Exception ex) {
-                            getLog().error(ex);
-                        }
-                    }
-                } else {
-                    try (BufferedReader input = new BufferedReader(new FileReader(new File(scriptsDirectoryReference.get(), scriptPath)))) {
-                        for (String line = input.readLine(); line != null; line = input.readLine()) {
-                            requires.add(line);
-                        }
-                    } catch (Exception ex) {
-                        getLog().error(ex);
-                    }
-                }
-            }
-            if (script.extension != null) {
-                capability += ";sling.resourceType.extensions:List<String>=\"" + script.extension.replace("\"", "\\\"") + "\"";
-            }
-
-            if (script.method != null) {
-                capability += ";sling.servlet.methods:List<String>=\"" + script.method.replace("\"", "\\\"") + "\"";
-            }
-            if (script.version != null) {
-                capability += ";version:Version=\"" + script.version + "\"";
-            }
-            capabilities.add(capability);
-        }
-        List<String> requirements = new ArrayList<>();
-        for (String require : requires) {
-            String[] parts = require.split(";");
-            String rt = parts[0];
-            String filter = "(sling.resourceType=" + rt.replace("\"", "\\\"") + ")";
-
-            if (parts.length > 1) {
-                VersionRange range = new VersionRange(parts[1].substring(parts[1].indexOf("=") + 1).replace("\"", "").trim());
-                filter = "(&" + filter + range.toFilterString("version") + ")";
-            }
-            if (parts.length > 2) {
-                filter = "(&" + filter + "(!(sling.resourceType.selectors=*)))";
-            }
-            requirements.add("sling.resourceType;filter:=\"" + filter + "\"");
-        }
-
-        project.getProperties().setProperty(ScriptingMavenPlugin.class.getPackage().getName() + "." + Constants.PROVIDE_CAPABILITY,
-                String.join(",", capabilities));
-        project.getProperties().setProperty(ScriptingMavenPlugin.class.getPackage().getName() + "." + Constants.REQUIRE_CAPABILITY,
-                String.join(",", requirements));
-    }
-
-    static class Script {
-        String rt;
-        String version;
-        String name;
-        String extension;
-        String scriptExtension;
-        String method;
-    }
-
-    static Script getScripts(String script) {
-        String fileSeparator = null;
-        for (String sep : FILE_SEPARATORS) {
-            if (script.contains(sep)) {
-                fileSeparator = sep;
-                break;
-            }
-        }
-        Script result = new Script();
-        String[] parts = script.split(Pattern.quote(fileSeparator));
-
-        result.rt = parts[0];
-        result.version = parts.length > 2 ? new Version(parts[1]).toString() : null;
-        result.name = parts.length > 2 ? parts[2] : parts[1];
-        int idx = result.name.lastIndexOf('.');
-        if (idx != -1) {
-            result.scriptExtension = result.name.substring(idx + 1);
-            result.name = result.name.substring(0, idx);
-            if (result.scriptExtension.isEmpty()) {
-                result.scriptExtension = null;
-            }
-        }
-
-        idx = result.name.lastIndexOf('.');
-        if (idx != -1) {
-            result.extension = result.name.substring(idx + 1);
-            result.name = result.name.substring(0, idx);
-            if (result.extension.isEmpty() || result.extension.equalsIgnoreCase("html")) {
-                result.extension = null;
-            }
-        } else {
-            result.extension = null;
-        }
-
-        idx = result.name.indexOf('.');
-        if (idx != -1) {
-            String methodString = result.name.substring(0, idx).toUpperCase();
-            if (METHODS.contains(methodString)) {
-                result.method = methodString;
-                result.name = result.name.substring(idx + 1);
-            }
-        } else if (METHODS.contains(result.name.toUpperCase())) {
-            result.method = result.name.toUpperCase();
-            result.name = "";
-        }
-        return result;
-    }
-}
diff --git a/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojoCapabilityFindingTest.java b/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojoCapabilityFindingTest.java
new file mode 100644
index 0000000..5f06a1f
--- /dev/null
+++ b/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojoCapabilityFindingTest.java
@@ -0,0 +1,148 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.scriptingbundle.maven.plugin;
+
+import org.apache.maven.plugin.logging.Log;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+@Ignore
+public class MetadataMojoCapabilityFindingTest {
+
+    private MetadataMojo plugin;
+
+    @Rule
+    public ExpectedException thrown= ExpectedException.none();
+
+    @Before
+    public void setUp() {
+        plugin = spy(new MetadataMojo());
+        when(plugin.getLog()).thenReturn(mock(Log.class));
+    }
+
+    @After
+    public void tearDown() {
+        plugin = null;
+    }
+
+    @Test
+    public void testScriptNameFullCalculation() {
+        String scriptPath = "org.apache.foo/1.0.0/POST.hi.xml.jsp";
+
+        ProvidedCapability expected = ProvidedCapability.builder()
+                .withResourceType("org.apache.foo")
+                .withVersion("1.0.0")
+//                .withName("hi")
+//                .withMethod("POST")
+//                .withExtension("xml")
+//                .withScriptExtension("jsp")
+                .build();
+        assertEquals(expected, plugin.getScript(scriptPath));
+    }
+
+    @Test
+    public void testScriptNameFullCalculationFolderHierarchy() {
+        String scriptPath = "org/apache/foo/1.0.0/POST.hi.xml.jsp";
+
+        ProvidedCapability expected = ProvidedCapability.builder()
+                .withResourceType("org/apache/foo")
+                .withVersion("1.0.0")
+//                .withName("hi")
+//                .withMethod("POST")
+//                .withExtension("xml")
+//                .withScriptExtension("jsp")
+                .build();
+        assertEquals(expected, plugin.getScript(scriptPath));
+    }
+
+    @Test
+    public void testScriptNameMinCalculation() {
+        String scriptPath = "org.apache.foo/foo.jsp";
+
+        ProvidedCapability expected = ProvidedCapability.builder()
+                .withResourceType("org.apache.foo")
+//                .withName("foo")
+//                .withScriptExtension("jsp")
+                .build();
+
+        assertEquals(expected, plugin.getScript(scriptPath));
+    }
+
+    @Test
+    public void testScriptNameMinCalculationFolderHierarchy() {
+        String scriptPath = "org/apache/foo/foo.jsp";
+
+        ProvidedCapability expected = ProvidedCapability.builder()
+                .withResourceType("org/apache/foo")
+//                .withName("foo")
+//                .withScriptExtension("jsp")
+                .build();
+
+        assertEquals(expected, plugin.getScript(scriptPath));
+    }
+
+    @Test
+    public void testScriptNameVersionAndMethodCalculation() {
+        String scriptPath = "org.apache.foo/1.2.0/Post.jsp";
+
+        ProvidedCapability expected = ProvidedCapability.builder()
+                .withResourceType("org.apache.foo")
+                .withVersion("1.2.0")
+//                .withMethod("POST")
+//                .withScriptExtension("jsp")
+                .build();
+        assertEquals(expected, plugin.getScript(scriptPath));
+    }
+
+    @Test()
+    public void testScriptNameVersionAndMethodMinCalculation() {
+        thrown.expect(NullPointerException.class);
+        thrown.expectMessage("The script's scriptExtension cannot be null");
+
+        String scriptPath = "org.apache.foo/1.2.0/Post";
+        plugin.getScript(scriptPath);
+    }
+
+    @Test()
+    public void testScriptNameMinCalculationInvalidPath() {
+        thrown.expect(NullPointerException.class);
+        thrown.expectMessage("The script's scriptExtension cannot be null");
+
+        String scriptPath = "org.apache.foo/1.2.0/foo.";
+        plugin.getScript(scriptPath);
+    }
+
+    @Test()
+    public void testScriptNameMinCalculationNoResourceType() {
+        thrown.expect(NullPointerException.class);
+        thrown.expectMessage("The script's resourceType cannot be null");
+
+        String scriptPath = "foo.html";
+        plugin.getScript(scriptPath);
+    }
+}
diff --git a/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojoTest.java b/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojoTest.java
new file mode 100644
index 0000000..8530695
--- /dev/null
+++ b/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/MetadataMojoTest.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.scriptingbundle.maven.plugin;
+
+import java.io.File;
+import java.nio.file.Paths;
+import java.util.Properties;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.testing.MojoRule;
+import org.apache.maven.plugin.testing.SilentLog;
+import org.apache.maven.project.MavenProject;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.osgi.framework.Constants;
+import org.sonatype.plexus.build.incremental.DefaultBuildContext;
+
+import static org.junit.Assert.assertTrue;
+
+public class MetadataMojoTest {
+
+    @Rule
+    public MojoRule mojoRule = new MojoRule();
+
+    @After
+    public void after() {
+        System.clearProperty("basedir");
+    }
+
+    @Test
+    public void testProject1() throws Exception {
+        MojoProject mojoProject = getMojoProject(getProjectLocation("project-1"));
+        mojoProject.mojo.execute();
+        Properties properties = mojoProject.project.getProperties();
+        assertTrue(properties.containsKey(MetadataMojo.class.getPackage() + "." + Constants.PROVIDE_CAPABILITY));
+    }
+
+    private MojoProject getMojoProject(File projectDirectory) throws Exception {
+        SilentLog log = new SilentLog();
+        DefaultBuildContext buildContext = new DefaultBuildContext();
+        buildContext.enableLogging(log);
+        MavenProject project = mojoRule.readMavenProject(projectDirectory);
+        MavenSession session = mojoRule.newMavenSession(project);
+        MojoExecution execution = mojoRule.newMojoExecution("metadata");
+        MetadataMojo validateMojo = (MetadataMojo) mojoRule.lookupConfiguredMojo(session, execution);
+        validateMojo.setLog(log);
+        MojoProject mojoProject = new MojoProject();
+        mojoProject.mojo = validateMojo;
+        mojoProject.project = project;
+        return mojoProject;
+    }
+
+    private class MojoProject {
+        MetadataMojo mojo;
+        MavenProject project;
+    }
+
+    private File getProjectLocation(String projectName) {
+        return Paths.get("src", "test", "resources", projectName).toFile();
+    }
+}
diff --git a/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/ScriptingMavenPluginTest.java b/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/ScriptingMavenPluginTest.java
deleted file mode 100644
index abc4f7b..0000000
--- a/src/test/java/org/apache/sling/scriptingbundle/maven/plugin/ScriptingMavenPluginTest.java
+++ /dev/null
@@ -1,80 +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.scriptingbundle.maven.plugin;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-public class ScriptingMavenPluginTest {
-    @Test
-    public void testScriptNameFullCalculation() {
-        String scriptPath = "org.apache.foo/1.0.0/POST.hi.xml.jsp";
-
-        ScriptingMavenPlugin.Script script = ScriptingMavenPlugin.getScripts(scriptPath);
-
-        Assert.assertEquals("org.apache.foo", script.rt);
-        Assert.assertEquals("1.0.0", script.version);
-        Assert.assertEquals("hi", script.name);
-        Assert.assertEquals("POST", script.method);
-        Assert.assertEquals("xml", script.extension);
-        Assert.assertEquals("jsp", script.scriptExtension);
-    }
-
-    @Test
-    public void testScriptNameMinCalculation() {
-        String scriptPath = "org.apache.foo/foo";
-
-        ScriptingMavenPlugin.Script script = ScriptingMavenPlugin.getScripts(scriptPath);
-
-        Assert.assertEquals("org.apache.foo", script.rt);
-        Assert.assertNull("1.0.0", script.version);
-        Assert.assertEquals("foo", script.name);
-        Assert.assertNull(script.method);
-        Assert.assertNull(script.extension);
-        Assert.assertNull(script.scriptExtension);
-    }
-
-    @Test
-    public void testScriptNameVersionAndMethodCalculation() {
-        String scriptPath = "org.apache.foo/1.2.0/Post.jsp";
-
-        ScriptingMavenPlugin.Script script = ScriptingMavenPlugin.getScripts(scriptPath);
-
-        Assert.assertEquals("org.apache.foo", script.rt);
-        Assert.assertEquals("1.2.0", script.version);
-        Assert.assertEquals("", script.name);
-        Assert.assertEquals("POST", script.method);
-        Assert.assertNull(script.extension);
-        Assert.assertEquals("jsp", script.scriptExtension);
-    }
-
-    @Test
-    public void testScriptNameVersionAndMethodMinCalculation() {
-        String scriptPath = "org.apache.foo/1.2.0/Post.";
-
-        ScriptingMavenPlugin.Script script = ScriptingMavenPlugin.getScripts(scriptPath);
-
-        Assert.assertEquals("org.apache.foo", script.rt);
-        Assert.assertEquals("1.2.0", script.version);
-        Assert.assertEquals("", script.name);
-        Assert.assertEquals("POST", script.method);
-        Assert.assertNull(script.extension);
-        Assert.assertNull(script.scriptExtension);
-    }
-}
diff --git a/src/test/resources/project-1/pom.xml b/src/test/resources/project-1/pom.xml
new file mode 100644
index 0000000..0718199
--- /dev/null
+++ b/src/test/resources/project-1/pom.xml
@@ -0,0 +1,46 @@
+<?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>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>scriptingbundle-maven-plugin-project-1</artifactId>
+    <version>0.0.1</version>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>scriptingbundle-maven-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>metadata</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/extends b/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/extends
new file mode 100644
index 0000000..2d24f10
--- /dev/null
+++ b/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/extends
@@ -0,0 +1 @@
+org/apache/sling/bar
diff --git a/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/foobar.html b/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/foobar.html
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/requires b/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/requires
new file mode 100644
index 0000000..437b4ae
--- /dev/null
+++ b/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/1.0.0/requires
@@ -0,0 +1 @@
+sling/default
diff --git a/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/GET.html b/src/test/resources/project-1/src/main/resources/javax.script/org.apache.sling.foobar/GET.html
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/project-1/src/main/resources/javax.script/org/apache/sling/bar/1.0.0/bar.html b/src/test/resources/project-1/src/main/resources/javax.script/org/apache/sling/bar/1.0.0/bar.html
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/project-1/src/main/resources/javax.script/org/apache/sling/foo/foo.GET.html.html b/src/test/resources/project-1/src/main/resources/javax.script/org/apache/sling/foo/foo.GET.html.html
new file mode 100644
index 0000000..93f1f4d
--- /dev/null
+++ b/src/test/resources/project-1/src/main/resources/javax.script/org/apache/sling/foo/foo.GET.html.html
@@ -0,0 +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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<span>It works!</span>
diff --git a/src/test/resources/spotbugs-exclude.xml b/src/test/resources/spotbugs-exclude.xml
new file mode 100644
index 0000000..fd5f18d
--- /dev/null
+++ b/src/test/resources/spotbugs-exclude.xml
@@ -0,0 +1,24 @@
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<FindBugsFilter>
+    <!-- false positive in Java 11, see https://github.com/spotbugs/spotbugs/issues/756 -->
+    <Match>
+        <Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
+    </Match>
+</FindBugsFilter>