This is an automated email from the ASF dual-hosted git repository. radu pushed a commit to branch issue/SLING-10811 in repository https://gitbox.apache.org/repos/asf/sling-scriptingbundle-maven-plugin.git
commit 3552a28c62ae165a963198b25e70ff361f2f2296 Author: Radu Cotescu <[email protected]> AuthorDate: Wed Sep 15 11:01:10 2021 +0200 SLING-10811 - Mark unresolved required capabilities as optional * required capabilities which are not satisfied by the current project are marked as optional; a flag can disable this behaviour, making these required capabilities mandatory --- .../plugin/bnd/BundledScriptsScannerPlugin.java | 12 ++++- .../plugin/capability/Capabilities.java | 35 +++++++++---- .../capability/RequiredResourceTypeCapability.java | 2 +- .../scriptingbundle/plugin/maven/MetadataMojo.java | 39 ++++---------- .../plugin/processor/Constants.java | 1 + src/site/markdown/usage.md.vm | 5 ++ .../scriptingbundle/plugin/AbstractPluginTest.java | 61 +++++++++------------- src/test/resources/project-4/bnd.bnd | 18 +++++++ src/test/resources/project-4/pom.xml | 49 +++++++++++++++++ .../src/main/scripts/apps/components/test/requires | 1 + .../main/scripts/apps/components/test/test.html | 18 +++++++ 11 files changed, 161 insertions(+), 80 deletions(-) diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java index e94d585..caa3293 100644 --- a/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java +++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java @@ -93,7 +93,7 @@ public class BundledScriptsScannerPlugin implements AnalyzerPlugin, Plugin { scriptEngineMappings = getConfiguredScriptEngineMappings(); capabilities = Capabilities .fromFileSystemTree(workDirectory, walkPath(workDirectory, includes, excludes), logger, - getConfiguredSearchPaths(), scriptEngineMappings); + getConfiguredSearchPaths(), scriptEngineMappings, getMissingRequirementsOptional()); String providedCapabilitiesDefinition = capabilities.getProvidedCapabilitiesString(); String requiredCapabilitiesDefinition = capabilities.getRequiredCapabilitiesString(); @@ -142,7 +142,6 @@ public class BundledScriptsScannerPlugin implements AnalyzerPlugin, Plugin { } private Set<PathMatcher> getConfiguredExcludes() { - String excludesCSV = pluginProperties.get(Constants.BND_EXCLUDES); if (StringUtils.isNotEmpty(excludesCSV)) { return Collections.unmodifiableSet(Arrays.stream(excludesCSV.split(",")).map(String::trim) @@ -189,6 +188,15 @@ public class BundledScriptsScannerPlugin implements AnalyzerPlugin, Plugin { return Constants.DEFAULT_SEARCH_PATHS; } + private boolean getMissingRequirementsOptional() { + String missingRequirementsOptionalString = pluginProperties.get(Constants.BND_MISSING_REQUIREMENTS_OPTIONAL); + if (missingRequirementsOptionalString != null) { + missingRequirementsOptionalString = missingRequirementsOptionalString.trim().toLowerCase(); + return !"false".equals(missingRequirementsOptionalString); + } + return true; + } + private Stream<Path> walkPath(Path path, Set<PathMatcher> includes, Set<PathMatcher> excludes) throws IOException { return Files.walk(path).filter(file -> { boolean include = false; diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java index d8fc5cb..bd8e4de 100644 --- a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java +++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java @@ -21,7 +21,6 @@ package org.apache.sling.scriptingbundle.plugin.capability; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; @@ -45,7 +44,6 @@ public class Capabilities { private final Set<ProvidedResourceTypeCapability> providedResourceTypeCapabilities; private final Set<ProvidedScriptCapability> providedScriptCapabilities; private final Set<RequiredResourceTypeCapability> requiredResourceTypeCapabilities; - private final Set<RequiredResourceTypeCapability> unresolvedRequiredResourceTypeCapabilities; public static final Capabilities EMPTY = new Capabilities(Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); public Capabilities( @@ -55,9 +53,6 @@ public class Capabilities { this.providedResourceTypeCapabilities = providedResourceTypeCapabilities; this.providedScriptCapabilities = providedScriptCapabilities; this.requiredResourceTypeCapabilities = requiredResourceTypeCapabilities; - unresolvedRequiredResourceTypeCapabilities = new HashSet<>(requiredResourceTypeCapabilities); - providedResourceTypeCapabilities.forEach(providedResourceTypeCapability -> unresolvedRequiredResourceTypeCapabilities - .removeIf(requiredResourceTypeCapability -> requiredResourceTypeCapability.isSatisfied(providedResourceTypeCapability))); } public @NotNull Set<ProvidedResourceTypeCapability> getProvidedResourceTypeCapabilities() { @@ -72,10 +67,6 @@ public class Capabilities { return Collections.unmodifiableSet(requiredResourceTypeCapabilities); } - public @NotNull Set<RequiredResourceTypeCapability> getUnresolvedRequiredResourceTypeCapabilities() { - return Collections.unmodifiableSet(unresolvedRequiredResourceTypeCapabilities); - } - public @NotNull String getProvidedCapabilitiesString() { Parameters parameters = new Parameters(); for (ProvidedResourceTypeCapability capability : getProvidedResourceTypeCapabilities()) { @@ -139,7 +130,9 @@ public class Capabilities { } public static @NotNull Capabilities fromFileSystemTree(@NotNull Path root, @NotNull Stream<Path> files, @NotNull Logger logger, - @NotNull Set<String> searchPaths, @NotNull Map<String, String> scriptEngineMappings) { + @NotNull Set<String> searchPaths, + @NotNull Map<String, String> scriptEngineMappings, + boolean missingRequirementsOptional) { Set<ProvidedResourceTypeCapability> providedResourceTypeCapabilities = new LinkedHashSet<>(); Set<ProvidedScriptCapability> providedScriptCapabilities = new LinkedHashSet<>(); Set<RequiredResourceTypeCapability> requiredResourceTypeCapabilities = new LinkedHashSet<>(); @@ -158,7 +151,27 @@ public class Capabilities { requiredResourceTypeCapabilities.addAll(pathCapabilities.getRequiredResourceTypeCapabilities()); } }); - return new Capabilities(providedResourceTypeCapabilities, providedScriptCapabilities, requiredResourceTypeCapabilities); + final Set<RequiredResourceTypeCapability> required = new LinkedHashSet<>(); + if (missingRequirementsOptional) { + Set<RequiredResourceTypeCapability> unresolvedRequiredResourceTypeCapabilities = + new LinkedHashSet<>(requiredResourceTypeCapabilities); + providedResourceTypeCapabilities.forEach(providedResourceTypeCapability -> unresolvedRequiredResourceTypeCapabilities + .removeIf( + requiredResourceTypeCapability -> requiredResourceTypeCapability.isSatisfied(providedResourceTypeCapability))); + + requiredResourceTypeCapabilities.forEach(requiredResourceTypeCapability -> { + if (unresolvedRequiredResourceTypeCapabilities.contains(requiredResourceTypeCapability)) { + required.add( + RequiredResourceTypeCapability.builder().withResourceType(requiredResourceTypeCapability.getResourceType()) + .withVersionRange(requiredResourceTypeCapability.getVersionRange()).withIsOptional().build()); + } else { + required.add(requiredResourceTypeCapability); + } + }); + } else { + required.addAll(requiredResourceTypeCapabilities); + } + return new Capabilities(providedResourceTypeCapabilities, providedScriptCapabilities, required); } } diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java index 7fbab3b..b7a2df2 100644 --- a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java +++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java @@ -78,7 +78,7 @@ public class RequiredResourceTypeCapability { @Override public String toString() { - return String.format("%s{resourceType=%s, versionRange=%s, isOptonal=%s}", this.getClass().getSimpleName(), + return String.format("%s{resourceType=%s, versionRange=%s, isOptional=%s}", this.getClass().getSimpleName(), resourceType, versionRange, isOptional); } diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java index f72eaad..f577cf2 100644 --- a/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java +++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java @@ -171,6 +171,14 @@ public class MetadataMojo extends AbstractMojo { @Parameter(property = "scriptingbundle.searchPaths") private Set<String> searchPaths; + /** + * When set to "true", the requirements which are not satisfied directly by this project will be marked as optional. + * + * @since 0.5.0 + */ + @Parameter(property = "scriptingbundle.missingRequirementsOptional", defaultValue = "true") + private boolean missingRequirementsOptional = true; + private Capabilities capabilities; public void execute() { @@ -221,18 +229,15 @@ public class MetadataMojo extends AbstractMojo { scannerPaths.stream().map(workDirectory::resolve), logger, searchPaths, - scriptEngineMappings + scriptEngineMappings, + missingRequirementsOptional ); String providedCapabilitiesDefinition = capabilities.getProvidedCapabilitiesString(); String requiredCapabilitiesDefinition = capabilities.getRequiredCapabilitiesString(); - String unresolvedRequiredCapabilitiesDefinition = getUnresolvedRequiredCapabilitiesString(capabilities); project.getProperties().put("org.apache.sling.scriptingbundle.maven.plugin." + org.osgi.framework.Constants.PROVIDE_CAPABILITY, providedCapabilitiesDefinition); project.getProperties().put("org.apache.sling.scriptingbundle.maven.plugin." + org.osgi.framework.Constants.REQUIRE_CAPABILITY, requiredCapabilitiesDefinition); - project.getProperties() - .put("org.apache.sling.scriptingbundle.maven.plugin.Unresolved-" + org.osgi.framework.Constants.REQUIRE_CAPABILITY, - unresolvedRequiredCapabilitiesDefinition); } catch (IOException e) { logger.error("Unable to generate working directory.", e); } @@ -256,30 +261,6 @@ public class MetadataMojo extends AbstractMojo { return scanner; } - @NotNull - private String getUnresolvedRequiredCapabilitiesString(Capabilities capabilities) { - StringBuilder builder = new StringBuilder(); - int pcNum = capabilities.getUnresolvedRequiredResourceTypeCapabilities().size(); - int pcIndex = 0; - for (RequiredResourceTypeCapability capability : capabilities.getUnresolvedRequiredResourceTypeCapabilities()) { - builder.append(Constants.CAPABILITY_NS).append(";"); - builder.append(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES).append("=").append(capability.getResourceType()); - VersionRange versionRange = capability.getVersionRange(); - if (versionRange != null) { - Version left = versionRange.getLeft(); - if (versionRange.getLeftType() == VersionRange.LEFT_OPEN) { - left = new Version(left.getMajor(), left.getMinor(), left.getMicro() + 1); - } - builder.append(";").append(Constants.CAPABILITY_VERSION_AT).append("=").append(left); - } - if (pcIndex < pcNum - 1) { - builder.append(","); - } - pcIndex++; - } - return builder.toString(); - } - Capabilities getCapabilities() { return capabilities; } diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java index c9da665..55cbc89 100644 --- a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java +++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java @@ -52,6 +52,7 @@ public final class Constants { public static final String BND_INCLUDES = "includes"; public static final String BND_SCRIPT_ENGINE_MAPPINGS = "scriptEngineMappings"; public static final String BND_SEARCH_PATHS = "searchPaths"; + public static final String BND_MISSING_REQUIREMENTS_OPTIONAL = "missingRequirementsOptional"; public static final Map<String, String> DEFAULT_EXTENSION_TO_SCRIPT_ENGINE_MAPPING; public static final Set<String> DEFAULT_SEARCH_PATHS; diff --git a/src/site/markdown/usage.md.vm b/src/site/markdown/usage.md.vm index 626e289..b1bec3e 100644 --- a/src/site/markdown/usage.md.vm +++ b/src/site/markdown/usage.md.vm @@ -36,6 +36,11 @@ page. In addition to the normal way of structuring scripts in the file tree, the is defined as a package name, the resource type label will be the last subpackage (i.e. for `com.mydomain.components.image`, the resource type label will be `image`). +Starting with version 0.5.0, the plugin will mark the requirements which are not satisfied by the analysed project as optional; classpath +dependencies are not checked for provided capabilities. If you want to generate mandatory requirements, set the `missingRequirementsOptional` +flag to `false`. + + $h3 Defining scripts As an example, let's assume the following layout: ``` diff --git a/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java b/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java index fa35391..9a96b9b 100644 --- a/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java +++ b/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java @@ -135,7 +135,7 @@ public abstract class AbstractPluginTest { Set<RequiredResourceTypeCapability> rExpected = new HashSet<>(Arrays.asList( RequiredResourceTypeCapability.builder().withResourceType("sling/default") - .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build(), + .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).withIsOptional().build(), RequiredResourceTypeCapability.builder().withResourceType("org/apache/sling/bar").build(), RequiredResourceTypeCapability.builder().withResourceType("org/apache/sling/bar") .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).withIsOptional().build() @@ -144,11 +144,7 @@ public abstract class AbstractPluginTest { ProvidedScriptCapability.builder(scriptEngineMappings) .withPath("/org.apache.sling.wrongbar/wrongbar.has.too.many.selectors.html").build() )); - Set<RequiredResourceTypeCapability> urExpected = new HashSet<>(Arrays.asList( - RequiredResourceTypeCapability.builder().withResourceType("sling/default") - .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build() - )); - verifyCapabilities(capabilities, pExpected, rExpected, sExpected, urExpected); + verifyCapabilities(capabilities, pExpected, rExpected, sExpected); } finally { cleanUp("project-1"); } @@ -174,13 +170,9 @@ public abstract class AbstractPluginTest { )); Set<RequiredResourceTypeCapability> expectedRequired = new HashSet<>(Arrays.asList( RequiredResourceTypeCapability.builder().withResourceType("sling/scripting/warpDrive") - .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build() - )); - Set<RequiredResourceTypeCapability> expectedUnresolvedRequired = new HashSet<>(Arrays.asList( - RequiredResourceTypeCapability.builder().withResourceType("sling/scripting/warpDrive") - .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build() + .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).withIsOptional().build() )); - verifyCapabilities(capabilities, pExpected, expectedRequired, expectedScriptCapabilities, expectedUnresolvedRequired); + verifyCapabilities(capabilities, pExpected, expectedRequired, expectedScriptCapabilities); } finally { cleanUp("project-2"); } @@ -198,13 +190,31 @@ public abstract class AbstractPluginTest { .withSelectors(Arrays.asList("selector")).withScriptEngine("htl").withScriptExtension("html") .build() )); - verifyCapabilities(capabilities, pExpected, Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); + verifyCapabilities(capabilities, pExpected, Collections.emptySet(), Collections.emptySet()); } finally { cleanUp("project-3"); } } - private void verifyCapabilities(Capabilities capabilities, Set<ProvidedResourceTypeCapability> pExpected, Set<RequiredResourceTypeCapability> rExpected, Set<ProvidedScriptCapability> sExpected, Set<RequiredResourceTypeCapability> urExpected) { + @Test + public void testProject4() throws Exception { + try { + PluginExecution execution = executePluginOnProject("project-4"); + Capabilities capabilities = execution.getCapabilities(); + Set<ProvidedResourceTypeCapability> pExpected = new HashSet<>(Arrays.asList( + ProvidedResourceTypeCapability.builder().withResourceTypes("components/test", "/apps/components/test") + .withScriptEngine("htl").withScriptExtension("html").build() + )); + Set<RequiredResourceTypeCapability> rExpected = new HashSet<>(Arrays.asList( + RequiredResourceTypeCapability.builder().withResourceType("components/testhelper").build() + )); + verifyCapabilities(capabilities, pExpected, rExpected, Collections.emptySet()); + } finally { + cleanUp("project-4"); + } + } + + private void verifyCapabilities(Capabilities capabilities, Set<ProvidedResourceTypeCapability> pExpected, Set<RequiredResourceTypeCapability> rExpected, Set<ProvidedScriptCapability> sExpected) { Set<ProvidedResourceTypeCapability> provided = new HashSet<>(capabilities.getProvidedResourceTypeCapabilities()); StringBuilder missingProvided = new StringBuilder(); for (ProvidedResourceTypeCapability capability : pExpected) { @@ -263,28 +273,5 @@ public abstract class AbstractPluginTest { if (extraProvidedScripts.length() > 0) { fail(extraProvidedScripts.toString()); } - - Set<RequiredResourceTypeCapability> unresolvedRequired = - new HashSet<>(capabilities.getUnresolvedRequiredResourceTypeCapabilities()); - assertEquals(urExpected.size(), unresolvedRequired.size()); - StringBuilder missingUnresolvedRequired = new StringBuilder(); - for (RequiredResourceTypeCapability capability : urExpected) { - boolean removed = unresolvedRequired.remove(capability); - if (!removed) { - missingUnresolvedRequired.append("Missing unresolved required capability: ").append(capability.toString()) - .append(System.lineSeparator()); - } - } - if (missingUnresolvedRequired.length() > 0) { - fail(missingUnresolvedRequired.toString()); - } - StringBuilder extraUnresolvedRequired = new StringBuilder(); - for (RequiredResourceTypeCapability capability : unresolvedRequired) { - extraUnresolvedRequired.append("Extra unresolved required capability: ").append(capability.toString()) - .append(System.lineSeparator()); - } - if (extraUnresolvedRequired.length() > 0) { - fail(extraUnresolvedRequired.toString()); - } } } diff --git a/src/test/resources/project-4/bnd.bnd b/src/test/resources/project-4/bnd.bnd new file mode 100644 index 0000000..173468c --- /dev/null +++ b/src/test/resources/project-4/bnd.bnd @@ -0,0 +1,18 @@ +# 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. +-plugin: org.apache.sling.scriptingbundle.plugin.bnd.BundledScriptsScannerPlugin; \ + missingRequirementsOptional=false diff --git a/src/test/resources/project-4/pom.xml b/src/test/resources/project-4/pom.xml new file mode 100644 index 0000000..4a871d8 --- /dev/null +++ b/src/test/resources/project-4/pom.xml @@ -0,0 +1,49 @@ +<?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> + <configuration> + <missingRequirementsOptional>false</missingRequirementsOptional> + </configuration> + <executions> + <execution> + <phase>prepare-package</phase> + <goals> + <goal>metadata</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/src/test/resources/project-4/src/main/scripts/apps/components/test/requires b/src/test/resources/project-4/src/main/scripts/apps/components/test/requires new file mode 100644 index 0000000..3569a01 --- /dev/null +++ b/src/test/resources/project-4/src/main/scripts/apps/components/test/requires @@ -0,0 +1 @@ +components/testhelper diff --git a/src/test/resources/project-4/src/main/scripts/apps/components/test/test.html b/src/test/resources/project-4/src/main/scripts/apps/components/test/test.html new file mode 100644 index 0000000..2853663 --- /dev/null +++ b/src/test/resources/project-4/src/main/scripts/apps/components/test/test.html @@ -0,0 +1,18 @@ +<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ 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. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
