diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/Dockerfile b/Java-base/sling-org-apache-sling-feature-extension-apiregions/Dockerfile new file mode 100644 index 000000000..e208c4890 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:22.04 + +RUN export DEBIAN_FRONTEND=noninteractive \ + && apt-get update \ + && apt-get install -y software-properties-common \ + && add-apt-repository ppa:deadsnakes/ppa \ + && apt-get update \ + && apt-get install -y \ + build-essential \ + git \ + vim \ + jq \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/* + +RUN apt-get -y install sudo \ + openjdk-8-jdk \ + maven + +RUN bash -c "echo 2 | update-alternatives --config java" + +COPY src /workspace +WORKDIR /workspace + +RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false + +RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 + +ENV TZ=Asia/Seoul diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/CODE_OF_CONDUCT.md b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0fa18e593 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/CODE_OF_CONDUCT.md @@ -0,0 +1,22 @@ + +Apache Software Foundation Code of Conduct +==== + +Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html). diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/CONTRIBUTING.md b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/CONTRIBUTING.md new file mode 100644 index 000000000..ac82a1abe --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/CONTRIBUTING.md @@ -0,0 +1,24 @@ + +Contributing +==== + +Thanks for choosing to contribute! + +You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html. diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/Jenkinsfile b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/Jenkinsfile new file mode 100644 index 000000000..f5825190c --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/Jenkinsfile @@ -0,0 +1,20 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +slingOsgiBundleBuild() diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/LICENSE b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/README.md b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/README.md new file mode 100644 index 000000000..a33cf431a --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/README.md @@ -0,0 +1,66 @@ +[](https://sling.apache.org) + + [![Build Status](https://builds.apache.org/buildStatus/icon?job=Sling/sling-org-apache-sling-feature-extension-apiregions/master)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-feature-extension-apiregions/job/master) [![Test Status](https://img.shields.io/jenkins/t/https/builds.apache.org/job/Sling/job/sling-org-apache-sling-feature-extension-apiregions/job/master.svg)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-feature-extension-apiregions/job/master/test_results_analyzer/) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![feature](https://sling.apache.org/badges/group-feature.svg)](https://github.com/apache/sling-aggregator/blob/master/docs/groups/feature.md) + +# Apache Sling API Regions Extension for the Feature Model + +This component contains extensions relating to the API Regions component. +Read the documentation about [API Regions](docs/api-regions.md) for more information. + +## Feature Model Analysers + +This component also contains Feature Model Analysers they are contributed through the Service Loader mechanism to the set of Analysers. + +Documentation can be found here: https://github.com/apache/sling-org-apache-sling-feature-analyser . These can be run as part of the 'analyse-features' goal with the [slingfeature-maven-plugin](https://github.com/apache/sling-slingfeature-maven-plugin#analyse-features-analyse-features). + +These analysers relate to API Region definitions in Feature Models. + +* `api-regions`: This analyser ensures that packages listed as exports in API-Regions sections are actually exported by a bundle that's part of the feature. + +* `api-regions-dependencies`: This analyser checks that packages in API regions listed earlier in the API-Regions declaration have no dependency on API regions listed later in the list. This include `Import-Package` style dependencies and also uses-clause type dependencies. Later API regions also include packages from earlier declared API regions, but not the other way around. + * Configuration parameters: + * `exporting-apis`: the name of the region that provides the visible APIs. + * `hiding-apis`: the name of the region that is 'hidden' i.e. not as visible as the exporting one. The +packages in the `exporting-api` cannot depend on any packages from this region. + +* `api-regions-duplicates`: This analyser ensures that packages are only listed in one region +in a given feature. If the same package is listed in multiple regions this will be an error. + +* `api-regions-check-order`: This analyser checks that regions are defined in the specified +order and that the same region is only declared once. Later regions inherit the packages +expose in earlier regions in the list, so the order is important. + * Configuration parameters: + * `order`: A comma separated list of the region names declaring the order in which they should be found. Not all regions declared must be present, but if they are present this +order must be obeyed. + +* `api-regions-crossfeature-dups`: This analyser checks whether there are exported packages in a feature model +that does _not_ opt in to the API Regions (i.e. it does not have an API-Regions section) that overlap with exported +packages from API regions in other feature models. It can prevent against unwanted results when packages are +exported from the outside which should be exported from an API Region. +This analyser only provides a useful result when run on +an aggregate feature model, i.e. a feature model that was created by aggregating a number of other feature models. It uses the +`feature-origins` metadata to find the features that bundles were initially declared in. It then matches this with the `feature-origins` found in the `api-regions` section. Exports from bundles from features that don't +declare `api-regions` are compared to declared exports in the `api-regions` section. If there is overlap an error +is reported. + * Configuration parameters: + * `regions`: a comma separated list of regions to check. If not specified all regions found are checked. This configuration item can be used to exclude certain regions from the check. + * `warningPackages`: if packages listed here are found to overlap, a warning instead of an error is reported. Supports either literal package names (e.g. `javax.servlet`) or wildcards with an asterisk at the end (e.g. `javax.*`). + * `ignoredPackages`: packages listed here are completely ignored in the analysis. Supports literal package names or wildcards with an asterisk at the end. + +## Extensions + +The following extensions are registered via the ServiceLoader mechanism: + +## `org.apache.sling.feature.builder.MergeHandler` +Merge handlers are called when features are merged during the aggregation process. + +`APIRegionMergeHandler` - This handler knows how to merge API Regions extensions + + +# Additional Extensions + +The following extensions are also implemented by this component and made available through the Service Loader mechanism: + +* org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler +* org.apache.sling.feature.launcher.spi.Launcher +* org.apache.sling.feature.scanner.spi.ExtensionScanner diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/docs/api-regions.md b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/docs/api-regions.md new file mode 100644 index 000000000..e68d9e65a --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/docs/api-regions.md @@ -0,0 +1,61 @@ +# API Regions for the Feature Model + +If you're assembling a platform (in contrast to a final application) out of several features and provide this platform for customers to build their application on top of, additional control of the API provided by the platform is needed. The bundles within the features provide all kinds of APIs but you might not want to expose all of these as extension points. You would rather want to use some of them internally within either a single feature or share within your platform features. + +## Visibility of API + +A feature exports some API, however there are two different types of clients of the API: + +* Bundles shipped as part of the platform +* Application bundles using the platform + +We can generalize this by saying that API is either globally visible (to every client) or only visible to features within the same context. Usually this is referred to as a "region": The platform spawns its own region and a customer application has its own region, too. In theory there could be several customer applications running in the same framework on top of the platform, and each application has its own region. + +Without any further information, API is globally visible by default. However, for platform features we want the opposite as we want to ensure that newly added API is not visible to all bundles by default. + +A feature can have an additional extension of type JSON named `api-region`. The following example exposes some packages to the global region and an additional package to the platform region. Exports declared earlier in the api-regions array also apply to later elements in the array, so the `platform` region also contains all exports declared for the `global` region. + +Note that the `global` region is a predefined region that exports the listed packages to everyone. Other region names can be chosen freely. Packages listed in these other regions are only exposed to bundles in features that are in the same region. + + "api-regions:JSON|optinal" : [ + { + "name": "global", + "exports": [ + "# Export Sling's resource API in the global region", + "org.apache.sling.resource.api", + "org.apache.sling.resource.api.adapter", + "org.apache.sling.resource.api.auth", + "org.apache.sling.resource.api.request", + "org.apache.sling.resource.api.resource" + ] + },{ + "name": "platform", + "exports": [ + "# Export the scheduler API in the platform region.", + "# All exports in earlier regions defined here also apply.", + "org.apache.sling.commons.scheduler" + ] + } + ] + +Of course the above mentioned packages need to be exported by some bundle within the feature. By exporting packages to a given region, a feature automatically also sees all packages available to that region (or regions). + +A feature can also just consume packages from a region, without having to export any packages to it. This can be done by exporting an empty list of packages. For example: + + "api-regions:JSON|optional" : [ + { + "name": "platform", + "exports": [] + } + ] + +If the api-regions extension is missing or the api-regions information is missing, it is assumed that all packages are exported to the "global" region and all packages in the global region are visible to the feature. + +If a feature exports no packages and only wants to have visibility of packages from the global region, this can be specified as follows: + + "api-regions:JSON|optional" : [ + { + "name": "global", + "exports": [] + } + ] diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/pom.xml b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/pom.xml new file mode 100644 index 000000000..3920b2a14 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/pom.xml @@ -0,0 +1,118 @@ + + + 4.0.0 + + org.apache.sling + sling + 35 + + + + org.apache.sling.feature.extension.apiregions + 1.1.5-SNAPSHOT + Sling Featuremodel - API Regions Exension + + + scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-extension-apiregions.git + scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-extension-apiregions.git + https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-feature-extension-apiregions.git + HEAD + + + + 8 + + + + + + org.apache.rat + apache-rat-plugin + + + src/main/resources/META-INF/services/** + src/test/resources/** + + + + + + + + org.osgi + org.osgi.annotation.versioning + 1.0.0 + provided + + + org.apache.geronimo.specs + geronimo-json_1.0_spec + 1.0-alpha-1 + provided + + + org.apache.sling + org.apache.sling.feature + 1.1.4 + provided + + + org.apache.sling + org.apache.sling.feature.analyser + 1.2.6 + provided + + + org.osgi + osgi.core + 6.0.0 + provided + + + org.apache.sling + org.apache.sling.feature.launcher + 1.1.2 + provided + + + + + junit + junit + test + + + org.mockito + mockito-core + 2.23.4 + test + + + org.apache.johnzon + johnzon-core + 1.0.0 + test + + + org.apache.sling + org.apache.sling.feature.io + 1.3.0 + compile + + + org.apache.felix + org.apache.felix.utils + 1.11.2 + test + + + diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java new file mode 100644 index 000000000..b9975d4ef --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java @@ -0,0 +1,103 @@ +/* + * 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.feature.extension.apiregions; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import javax.json.JsonArray; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.builder.HandlerContext; +import org.apache.sling.feature.builder.MergeHandler; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +/** + * Merge to api region extensions + */ +public class APIRegionMergeHandler implements MergeHandler { + + @Override + public boolean canMerge(Extension extension) { + return ApiRegions.EXTENSION_NAME.equals(extension.getName()); + } + + @Override + public void merge(HandlerContext context, Feature target, Feature source, Extension targetEx, Extension sourceEx) { + if (!sourceEx.getName().equals(ApiRegions.EXTENSION_NAME)) + return; + if (targetEx != null && !targetEx.getName().equals(ApiRegions.EXTENSION_NAME)) + return; + + try { + final ApiRegions srcRegions = ApiRegions.parse((JsonArray) sourceEx.getJSONStructure()); + + final ApiRegions targetRegions; + if (targetEx != null) { + targetRegions = ApiRegions.parse((JsonArray) targetEx.getJSONStructure()); + } else { + targetEx = new Extension(sourceEx.getType(), sourceEx.getName(), sourceEx.getState()); + target.getExtensions().add(targetEx); + + targetRegions = new ApiRegions(); + } + + for (final ApiRegion targetRegion : targetRegions.listRegions()) { + final ApiRegion sourceRegion = srcRegions.getRegionByName(targetRegion.getName()); + if (sourceRegion != null) { + for (final ApiExport srcExp : sourceRegion.listExports()) { + if (targetRegion.getExportByName(srcExp.getName()) == null) { + targetRegion.add(srcExp); + } + } + LinkedHashSet targetOrigins = new LinkedHashSet<>(Arrays.asList(targetRegion.getFeatureOrigins())); + LinkedHashSet sourceOrigins = new LinkedHashSet<>(Arrays.asList(sourceRegion.getFeatureOrigins())); + if (sourceOrigins.isEmpty()) { + sourceOrigins.add(source.getId()); + } + targetOrigins.addAll(sourceOrigins); + targetRegion.setFeatureOrigins(targetOrigins.toArray(new ArtifactId[0])); + } + } + + // If there are any remaining regions in the src extension, process them now + for (final ApiRegion r : srcRegions.listRegions()) { + if (targetRegions.getRegionByName(r.getName()) == null) { + LinkedHashSet origins = new LinkedHashSet<>(Arrays.asList(r.getFeatureOrigins())); + if (origins.isEmpty()) + { + origins.add(source.getId()); + r.setFeatureOrigins(origins.toArray(new ArtifactId[0])); + } + if (!targetRegions.add(r)) + { + throw new IllegalStateException("Duplicate region " + r.getName()); + } + } + } + + targetEx.setJSONStructure(targetRegions.toJSONArray()); + + } catch (final IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTask.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTask.java new file mode 100644 index 000000000..adb244fd2 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTask.java @@ -0,0 +1,73 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.IOException; +import javax.json.JsonArray; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +public abstract class AbstractApiRegionsAnalyserTask implements AnalyserTask { + + @Override + public final void execute(AnalyserTaskContext ctx) throws Exception { + Feature feature = ctx.getFeature(); + + // extract and check the api-regions + + Extensions extensions = feature.getExtensions(); + Extension apiRegionsExtension = extensions.getByName(ApiRegions.EXTENSION_NAME); + if (apiRegionsExtension == null) { + // no need to be analyzed + return; + } + + if (apiRegionsExtension.getJSON() == null || apiRegionsExtension.getJSON().isEmpty()) { + // no need to be analyzed + return; + } + + if (apiRegionsExtension.getJSONStructure() == null) { + ctx.reportError("API Regions '" + apiRegionsExtension.getJSON() + + "' does not represent a valid JSON 'api-regions'"); + return; + } + + // read the api-regions and create a Sieve data structure for checks + + ApiRegions apiRegions; + try { + apiRegions = ApiRegions.parse((JsonArray) apiRegionsExtension.getJSONStructure()); + } catch (IOException e) { + ctx.reportError("API Regions '" + + apiRegionsExtension.getJSON() + + "' does not represent a valid JSON 'api-regions': " + + e.getMessage()); + return; + } + + execute(apiRegions, ctx); + } + + protected abstract void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception; + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegions.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegions.java new file mode 100644 index 000000000..088e10531 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegions.java @@ -0,0 +1,94 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.util.Collection; +import java.util.Formatter; + +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; + +public class CheckApiRegions extends AbstractApiRegionsAnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME; + } + + @Override + public String getName() { + return "Api Regions analyser task"; + } + + @Override + protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception { + // for each bundle, get the Export-Package and process the packages + + FeatureDescriptor featureDescriptor = ctx.getFeatureDescriptor(); + for (BundleDescriptor bundleDescriptor : featureDescriptor.getBundleDescriptors()) { + for (PackageInfo packageInfo : bundleDescriptor.getExportedPackages()) { + String exportedPackage = packageInfo.getName(); + // use the Sieve technique: remove bundle exported packages from the api-regions + for (final ApiRegion region : apiRegions.listRegions()) { + ApiExport export = region.getExportByName(exportedPackage); + if (export != null) { + region.remove(export); + } + } + } + } + + boolean isEmpty = true; + for (final ApiRegion region : apiRegions.listRegions()) { + if (!region.listExports().isEmpty()) { + isEmpty = false; + break; + } + } + // final evaluation: if the Sieve is not empty, not all declared packages are exported by bundles of the same feature + if (!isEmpty) { + // track a single error for each region + for (ApiRegion region : apiRegions.listRegions()) { + if (!region.listExports().isEmpty()) { + Formatter formatter = new Formatter(); + formatter.format("Region '%s' defined in feature '%s' declares %s package%s which %s not exported by any bundle:%n", + region.getName(), + ctx.getFeature().getId(), + region.listExports().size(), getExtension(region.listExports(), "", "s"), + getExtension(region.listExports(), "is", "are")); + region.listExports().forEach(api -> formatter.format(" * %s%n", api.getName())); + + ctx.reportError(formatter.toString()); + + formatter.close(); + } + } + } + } + + // utility methods + + private static String getExtension(Collection collection, String singular, String plural) { + return collection.size() > 1 ? plural : singular; + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java new file mode 100644 index 000000000..e3ca7cbaa --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java @@ -0,0 +1,365 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.json.JsonArray; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; +import org.osgi.framework.Version; + +public class CheckApiRegionsBundleExportsImports implements AnalyserTask { + + private static final String IGNORE_API_REGIONS_CONFIG_KEY = "ignoreAPIRegions"; + private static final String GLOBAL_REGION = "global"; + private static final String NO_REGION = " __NO_REGION__ "; + private static final String OWN_FEATURE = " __OWN_FEATURE__ "; + + @Override + public String getName() { + return "Bundle Import/Export Check"; + } + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-exportsimports"; + } + + public static final class Report { + + public List exportWithoutVersion = new ArrayList<>(); + + public List exportMatchingSeveral = new ArrayList<>(); + + public List importWithoutVersion = new ArrayList<>(); + + public List missingExports = new ArrayList<>(); + + public List missingExportsWithVersion = new ArrayList<>(); + + public List missingExportsForOptional = new ArrayList<>(); + + public Map, Set>> regionInfo = new HashMap<>(); + } + + private Report getReport(final Map reports, final BundleDescriptor info) { + Report report = reports.get(info); + if ( report == null ) { + report = new Report(); + reports.put(info, report); + } + return report; + } + + private void checkForVersionOnExportedPackages(final AnalyserTaskContext ctx, final Map reports) { + for(final BundleDescriptor info : ctx.getFeatureDescriptor().getBundleDescriptors()) { + if ( info.getExportedPackages() != null ) { + for(final PackageInfo i : info.getExportedPackages()) { + if ( i.getPackageVersion().compareTo(Version.emptyVersion) == 0 ) { + getReport(reports, info).exportWithoutVersion.add(i); + } + } + } + } + } + + private void checkForVersionOnImportingPackages(final AnalyserTaskContext ctx, final Map reports) { + for(final BundleDescriptor info : ctx.getFeatureDescriptor().getBundleDescriptors()) { + if ( info.getImportedPackages() != null ) { + for(final PackageInfo i : info.getImportedPackages()) { + if ( i.getVersion() == null ) { + // don't report for javax, org.xml. and org.w3c. packages (TODO) + if ( !i.getName().startsWith("javax.") + && !i.getName().startsWith("org.w3c.") && !i.getName().startsWith("org.xml.")) { + getReport(reports, info).importWithoutVersion.add(i); + } + } + } + } + } + } + + @Override + public void execute(final AnalyserTaskContext ctx) throws Exception { + boolean ignoreAPIRegions = ctx.getConfiguration().getOrDefault( + IGNORE_API_REGIONS_CONFIG_KEY, "false").equalsIgnoreCase("true"); + + // basic checks + final Map reports = new HashMap<>(); + checkForVersionOnExportedPackages(ctx, reports); + checkForVersionOnImportingPackages(ctx, reports); + + final SortedMap> bundlesMap = new TreeMap<>(); + for(final BundleDescriptor bi : ctx.getFeatureDescriptor().getBundleDescriptors()) { + List list = bundlesMap.get(bi.getArtifact().getStartOrder()); + if ( list == null ) { + list = new ArrayList<>(); + bundlesMap.put(bi.getArtifact().getStartOrder(), list); + } + list.add(bi); + } + + // add all system packages + final List exportingBundles = new ArrayList<>(); + if ( ctx.getFrameworkDescriptor() != null ) { + exportingBundles.add(ctx.getFrameworkDescriptor()); + } + ApiRegions apiRegions = new ApiRegions(); // Empty API Regions; + + Feature feature = ctx.getFeature(); + + // extract and check the api-regions + + Extensions extensions = feature.getExtensions(); + Extension apiRegionsExtension = extensions.getByName(ApiRegions.EXTENSION_NAME); + if (apiRegionsExtension != null && apiRegionsExtension.getJSONStructure() != null) { + try { + apiRegions = ApiRegions.parse((JsonArray) apiRegionsExtension.getJSONStructure()); + } catch (IOException e) { + ctx.reportError("API Regions '" + apiRegionsExtension.getJSON() + + "' does not represent a valid JSON 'api-regions': " + e.getMessage()); + return; + } + } + + for(final Map.Entry> entry : bundlesMap.entrySet()) { + // first add all exporting bundles + for(final BundleDescriptor info : entry.getValue()) { + if ( info.getExportedPackages() != null ) { + exportingBundles.add(info); + } + } + // check importing bundles + for(final BundleDescriptor info : entry.getValue()) { + if ( info.getImportedPackages() != null ) { + for(final PackageInfo pck : info.getImportedPackages() ) { + final Map> candidates = + getCandidates(exportingBundles, pck, info, apiRegions, ignoreAPIRegions); + if ( candidates.isEmpty() ) { + if ( pck.isOptional() ) { + getReport(reports, info).missingExportsForOptional.add(pck); + } else { + getReport(reports, info).missingExports.add(pck); + } + } else { + final List matchingCandidates = new ArrayList<>(); + + Set exportingRegions = new HashSet<>(); + Set importingRegions = new HashSet<>(); + for(final Map.Entry> candidate : candidates.entrySet()) { + BundleDescriptor bd = candidate.getKey(); + if (bd.isExportingPackage(pck)) { + Set exRegions = candidate.getValue(); + if (exRegions.contains(NO_REGION)) { + // If an export is defined outside of a region, it always matches + matchingCandidates.add(bd); + continue; + } + if (exRegions.contains(GLOBAL_REGION)) { + // Everyone can import from the global regin + matchingCandidates.add(bd); + continue; + } + if (exRegions.contains(OWN_FEATURE)) { + // A feature can always import packages from bundles in itself + matchingCandidates.add(bd); + continue; + } + + // Find out what regions the importing bundle is in + Set imRegions = + getBundleRegions(info, apiRegions, ignoreAPIRegions); + + // Record the exporting and importing regions for diagnostics + exportingRegions.addAll(exRegions); + + Set regions = new HashSet<>(); + for (String region : imRegions) { + for (ApiRegion r = apiRegions.getRegionByName(region); r != null; r = r.getParent()) { + regions.add(r.getName()); + } + } + + importingRegions.addAll(regions); + + // Only keep the regions that also export the package + regions.retainAll(exRegions); + + if (!regions.isEmpty()) { + // there is an overlapping region + matchingCandidates.add(bd); + } + } + } + + if ( matchingCandidates.isEmpty() ) { + if ( pck.isOptional() ) { + getReport(reports, info).missingExportsForOptional.add(pck); + } else { + getReport(reports, info).missingExportsWithVersion.add(pck); + getReport(reports, info).regionInfo.put(pck, new AbstractMap.SimpleEntry<>(exportingRegions, importingRegions)); + } + } else if ( matchingCandidates.size() > 1 ) { + getReport(reports, info).exportMatchingSeveral.add(pck); + } + } + } + } + } + } + + boolean errorReported = false; + for(final Map.Entry entry : reports.entrySet()) { + final String key = "Bundle " + entry.getKey().getArtifact().getId().getArtifactId() + ":" + entry.getKey().getArtifact().getId().getVersion(); + + if ( !entry.getValue().importWithoutVersion.isEmpty() ) { + ctx.reportWarning(key + " is importing package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without specifying a version range."); + } + if ( !entry.getValue().exportWithoutVersion.isEmpty() ) { + ctx.reportWarning(key + " is exporting package(s) " + getPackageInfo(entry.getValue().importWithoutVersion, false) + " without a version."); + } + + if ( !entry.getValue().missingExports.isEmpty() ) { + ctx.reportError(key + " is importing package(s) " + getPackageInfo(entry.getValue().missingExports, false) + " in start level " + + String.valueOf(entry.getKey().getArtifact().getStartOrder()) + + " but no bundle is exporting these for that start level."); + errorReported = true; + } + if ( !entry.getValue().missingExportsWithVersion.isEmpty() ) { + StringBuilder message = new StringBuilder(key + " is importing package(s) " + getPackageInfo(entry.getValue().missingExportsWithVersion, true) + " in start level " + + String.valueOf(entry.getKey().getArtifact().getStartOrder()) + + " but no visible bundle is exporting these for that start level in the required version range."); + + for (Map.Entry, Set>> regionInfoEntry : entry.getValue().regionInfo.entrySet()) { + PackageInfo pkg = regionInfoEntry.getKey(); + Map.Entry, Set> regions = regionInfoEntry.getValue(); + if (regions.getKey().size() > 0) { + message.append("\n" + pkg.getName() + " is exported in regions " + regions.getKey() + " but it is imported in regions " + regions.getValue()); + } + } + ctx.reportError(message.toString()); + errorReported = true; + } + } + if (errorReported && ctx.getFeature().isComplete()) { + ctx.reportError(ctx.getFeature().getId().toMvnId() + " is marked as 'complete' but has missing imports."); + } + } + + private Set getBundleRegions(BundleDescriptor info, ApiRegions regions, boolean ignoreAPIRegions) { + Set result = ignoreAPIRegions ? Collections.emptySet() : Stream.of(info.getArtifact().getFeatureOrigins()) + .map(regions::getRegionsByFeature).flatMap(Stream::of).map(ApiRegion::getName).collect(Collectors.toSet()); + + if (result.isEmpty()) { + result = new HashSet<>(); + result.add(NO_REGION); + } + return result; + } + + + private String getPackageInfo(final List pcks, final boolean includeVersion) { + if ( pcks.size() == 1 ) { + if (includeVersion) { + return pcks.get(0).toString(); + } else { + return pcks.get(0).getName(); + } + } + final StringBuilder sb = new StringBuilder(); + boolean first = true; + sb.append('['); + for(final PackageInfo info : pcks) { + if ( first ) { + first = false; + } else { + sb.append(", "); + } + if (includeVersion) { + sb.append(info.toString()); + } else { + sb.append(info.getName()); + } + } + sb.append(']'); + return sb.toString(); + } + + private Map> getCandidates( + final List exportingBundles, + final PackageInfo pck, + final BundleDescriptor requestingBundle, + final ApiRegions apiRegions, boolean ignoreAPIRegions) { + Set rf = ignoreAPIRegions ? Collections.emptySet() : Stream.of(requestingBundle.getArtifact().getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.toSet()); + + final Set requestingFeatures = rf; + + final Map> candidates = new HashMap<>(); + for(final BundleDescriptor info : exportingBundles) { + if ( info.isExportingPackage(pck.getName()) ) { + Set providingFeatures = ignoreAPIRegions ? Collections.emptySet() : Stream.of(info.getArtifact().getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.toSet()); + + // Compute the intersection without modifying the sets + Set intersection = providingFeatures.stream().filter( + s -> requestingFeatures.contains(s)).collect(Collectors.toSet()); + if (!intersection.isEmpty()) { + // A requesting bundle can see all exported packages inside its own feature + candidates.put(info, Collections.singleton(OWN_FEATURE)); + continue; + } + + for (String region : getBundleRegions(info, apiRegions, ignoreAPIRegions)) { + if (!NO_REGION.equals(region) && + (apiRegions.getRegionByName(region) == null + || apiRegions.getRegionByName(region).getAllExportByName(pck.getName()) == null)) + continue; + + Set regions = candidates.get(info); + if (regions == null) { + regions = new HashSet<>(); + candidates.put(info, regions); + } + regions.add(region); + } + } + } + return candidates; + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDups.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDups.java new file mode 100644 index 000000000..7c9c8716d --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDups.java @@ -0,0 +1,130 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class CheckApiRegionsCrossFeatureDups extends AbstractApiRegionsAnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-crossfeature-dups"; + } + + @Override + public String getName() { + return "Api Regions cross-feature duplicate export task"; + } + + @Override + protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception { + Set checkedRegions = splitListConfig(ctx.getConfiguration().get("regions")); + Set ignoredPackages = splitListConfig(ctx.getConfiguration().get("ignoredPackages")); + Set warningPackages = splitListConfig(ctx.getConfiguration().get("warningPackages")); + + Map> regionExports = new HashMap<>(); + + List apiRegionsFeatures = new ArrayList<>(); + for (ApiRegion r : apiRegions.listRegions()) { + apiRegionsFeatures.addAll(Arrays.asList(r.getFeatureOrigins())); + if (checkedRegions.isEmpty() || checkedRegions.contains(r.getName())) { + Set exports = regionExports.get(r.getName()); + if (exports == null) { + exports = new HashSet<>(); + regionExports.put(r.getName(), exports); + } + exports.addAll(r.listExports().stream().map(ApiExport::getName).collect(Collectors.toSet())); + } + } + + FeatureDescriptor f = ctx.getFeatureDescriptor(); + for (BundleDescriptor bd : f.getBundleDescriptors()) { + List borgs = new ArrayList<>(Arrays.asList(bd.getArtifact().getFeatureOrigins())); + borgs.removeAll(apiRegionsFeatures); + + if (!borgs.isEmpty()) { + // This bundle was contributed by a feature that did not opt-in to the API Regions + Set reportedPackages = new HashSet<>(); + for (PackageInfo pi : bd.getExportedPackages()) { + String pkgName = pi.getName(); + for (Map.Entry> entry : regionExports.entrySet()) { + if (entry.getValue().contains(pkgName) && !reportedPackages.contains(pkgName)) { + if (matchesSet(pkgName, ignoredPackages)) { + continue; + } + + reportedPackages.add(pi.getName()); + + String msg = "Package overlap found between region " + entry.getKey() + + " and bundle " + bd.getBundleSymbolicName() + " " + bd.getBundleVersion() + + " which comes from a feature without API Regions: " + borgs + + ". Both export package: " + pi.getName(); + if (matchesSet(pkgName, warningPackages)) { + ctx.reportWarning(msg); + } else { + ctx.reportError(msg); + } + } + } + } + } + } + } + + private boolean matchesSet(String pkg, Set set) { + for (String e : set) { + if (e.endsWith("*")) { + if (pkg.startsWith(e.substring(0, e.length() - 1)) ) { + return true; + } + } else { + if (pkg.equals(e)) { + return true; + } + } + } + return false; + } + + private Set splitListConfig(String value) { + if (value == null) { + return Collections.emptySet(); + } else { + return Arrays.asList(value.split(",")) + .stream() + .map(String::trim) + .collect(Collectors.toSet()); + } + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDependencies.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDependencies.java new file mode 100644 index 000000000..fe6554086 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDependencies.java @@ -0,0 +1,87 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; +import org.osgi.framework.Constants; + +public class CheckApiRegionsDependencies extends AbstractApiRegionsAnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-dependencies"; + } + + @Override + public String getName() { + return "Api Regions dependecies analyser task"; + } + + @Override + protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception { + for (int i = 0; i < apiRegions.listRegions().size(); i++) { + for (int j = i + 1; j < apiRegions.listRegions().size(); j++) { + execute(ctx, apiRegions, apiRegions.listRegions().get(i), apiRegions.listRegions().get(j)); + } + } + } + + private void execute(AnalyserTaskContext ctx, ApiRegions apiRegions, ApiRegion exportingApisName, + ApiRegion hidingApisName) { + FeatureDescriptor featureDescriptor = ctx.getFeatureDescriptor(); + for (BundleDescriptor bundleDescriptor : featureDescriptor.getBundleDescriptors()) { + for (PackageInfo packageInfo : bundleDescriptor.getExportedPackages()) { + String exportedPackage = packageInfo.getName(); + + if (exportingApisName.getExportByName(exportedPackage) != null) { + if (hidingApisName.getExportByName(exportedPackage) != null) { + String errorMessage = String.format( + "Bundle '%s' (defined in feature '%s') declares '%s' in the '%s' header that is enlisted in both exporting '%s' and hiding '%s' APIs regions, please adjust Feature settings", + bundleDescriptor.getArtifact().getId(), + ctx.getFeature().getId(), + exportedPackage, + Constants.EXPORT_PACKAGE, + exportingApisName.getName(), + hidingApisName.getName()); + ctx.reportError(errorMessage); + } else { + for (String uses : packageInfo.getUses()) { + if (hidingApisName.getExportByName(uses) != null) { + String errorMessage = String.format( + "Bundle '%s' (defined in feature '%s') declares '%s' in the '%s' header, enlisted in the '%s' region, which uses '%s' package that is in the '%s' region", + bundleDescriptor.getArtifact().getId(), + ctx.getFeature().getId(), + exportedPackage, + Constants.EXPORT_PACKAGE, + exportingApisName.getName(), + uses, + hidingApisName.getName()); + ctx.reportError(errorMessage); + } + } + } + } + } + } + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicates.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicates.java new file mode 100644 index 000000000..25d12b55f --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicates.java @@ -0,0 +1,81 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.util.Formatter; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +public class CheckApiRegionsDuplicates extends AbstractApiRegionsAnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-duplicates"; + } + + @Override + public String getName() { + return "Api Regions duplicates analyser task"; + } + + @Override + protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception { + for (int i = 0; i < apiRegions.listRegions().size(); i++) { + ApiRegion sourceRegion = apiRegions.listRegions().get(i); + List targetRegions = apiRegions.listRegions().subList(i + 1, apiRegions.listRegions().size()); + + for (ApiRegion targetRegion : targetRegions) { + if (sourceRegion.equals(targetRegion)) { + continue; + } + + Set intersection = calculateIntersection(sourceRegion, targetRegion); + if (!intersection.isEmpty()) { + Formatter formatter = new Formatter(); + formatter.format("Regions '%s' and '%s' defined in feature '%s' declare both %s package(s):%n", + sourceRegion.getName(), targetRegion.getName(), + ctx.getFeature().getId(), + intersection.size()); + intersection.forEach(api -> formatter.format(" * %s%n", api)); + + ctx.reportError(formatter.toString()); + + formatter.close(); + } + } + } + } + + private static Set calculateIntersection(ApiRegion source, ApiRegion target) { + final Set intersection = new HashSet<>(); + + for (ApiExport packageName : source.listExports()) { + if (target.getExportByName(packageName.getName()) != null) { + intersection.add(packageName.getName()); + } + } + + return intersection; + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsOrder.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsOrder.java new file mode 100644 index 000000000..bbfff4337 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsOrder.java @@ -0,0 +1,123 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.json.JsonArray; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +public class CheckApiRegionsOrder implements AnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-check-order"; + } + + @Override + public String getName() { + return "Api Regions check order analyser task"; + } + + @Override + public final void execute(AnalyserTaskContext ctx) throws Exception { + String order = ctx.getConfiguration().get("order"); + Feature feature = ctx.getFeature(); + if (feature == null) { + reportError(ctx, "No feature found. Illegal Analyser State."); + return; + } + + if (order == null) { + reportError(ctx, "This analyser task must be configured: " + getId() + " for feature " + feature.getId()); + reportError(ctx, "Must specify configuration key 'order'."); + return; + } + + String[] sl = order.split("[,]"); + List prescribedOrder = new ArrayList<>(); + for (String s : sl) { + s = s.trim(); + if (s.length() > 0) + prescribedOrder.add(s); + } + if (prescribedOrder.size() == 0) { + reportError(ctx, "No regions declared in the 'order' configuration"); + return; + } + + // extract and check the api-regions + Extensions extensions = feature.getExtensions(); + Extension apiRegionsExtension = extensions.getByName(ApiRegions.EXTENSION_NAME); + if (apiRegionsExtension == null) { + // no need to be analyzed + return; + } + + String jsonRepresentation = apiRegionsExtension.getJSON(); + if (jsonRepresentation == null || jsonRepresentation.isEmpty()) { + // no need to be analyzed + return; + } + + try { + int regionIdx = 0; + ApiRegions apiRegions = ApiRegions.parse((JsonArray) apiRegionsExtension.getJSONStructure()); + for (final ApiRegion region : apiRegions.listRegions()) { + String name = region.getName(); + if (!prescribedOrder.contains(name)) { + reportError(ctx, "Region found with undeclared name: " + name); + return; + } + int prevIdx = regionIdx; + regionIdx = validateRegion(regionIdx, prescribedOrder, name); + if (regionIdx < 0) { + reportError(ctx, "Region '" + name + "' appears in the wrong order. It appears after '" + + prescribedOrder.get(prevIdx) + "'. Order of regions should be " + prescribedOrder); + return; + } + } + } catch (final IOException e) { + ctx.reportError("Invalid api regions"); + } + } + + private int validateRegion(int regionIdx, List order, String name) { + for (int i=regionIdx; i { + + private final String name; + + private volatile String toggle; + + private volatile ArtifactId previous; + + private final Map properties = new HashMap<>(); + + /** + * Create a new export + * + * @param name Package name for the export + */ + public ApiExport(final String name) { + this.name = name; + } + + /** + * Get the package name + * + * @return The package name + */ + public String getName() { + return name; + } + + /** + * Get the optional toggle information + * + * @return The toggle info or {@code null} + */ + public String getToggle() { + return toggle; + } + + /** + * Set the toggle info. + * + * @param toggle The toggle info + */ + public void setToggle(String toggle) { + this.toggle = toggle; + } + + /** + * Get the previous version of this api + * + * @return The previous version or {@code null} + */ + public ArtifactId getPrevious() { + return previous; + } + + /** + * Set the previous version + * + * @param previous Previus version + */ + public void setPrevious(ArtifactId previous) { + this.previous = previous; + } + + /** + * Get additional properties + * + * @return Modifiable map of properties + */ + public Map getProperties() { + return this.properties; + } + + @Override + public int compareTo(final ApiExport o) { + return this.name.compareTo(o.name); + } + + @Override + public String toString() { + return "ApiExport [name=" + name + ", toggle=" + toggle + ", previous=" + previous + ", properties=" + + properties + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((previous == null) ? 0 : previous.hashCode()); + result = prime * result + ((properties == null) ? 0 : properties.hashCode()); + result = prime * result + ((toggle == null) ? 0 : toggle.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ApiExport other = (ApiExport) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (previous == null) { + if (other.previous != null) + return false; + } else if (!previous.equals(other.previous)) + return false; + if (properties == null) { + if (other.properties != null) + return false; + } else if (!properties.equals(other.properties)) + return false; + if (toggle == null) { + if (other.toggle != null) + return false; + } else if (!toggle.equals(other.toggle)) + return false; + return true; + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java new file mode 100644 index 000000000..dd9f4b21c --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java @@ -0,0 +1,228 @@ +/* + * 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.feature.extension.apiregions.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.sling.feature.ArtifactId; + +/** + * Describes an api region + */ +public class ApiRegion { + + /** Name of the global region. */ + public static final String GLOBAL = "global"; + + private final List exports = new ArrayList<>(); + + private final List origins = new ArrayList<>(); + + private final Map properties = new HashMap<>(); + + private final String name; + + private volatile ApiRegion parent; + + /** + * Create a new named region + * + * @param name The name + */ + public ApiRegion(final String name) { + this.name = name; + } + + /** + * Get the name of the region + * + * @return The region name + */ + public String getName() { + return name; + } + + public ArtifactId[] getFeatureOrigins() { + return origins.toArray(new ArtifactId[0]); + } + + public void setFeatureOrigins(ArtifactId... featureOrigins) { + origins.clear(); + if (featureOrigins != null) { + origins.addAll(Stream.of(featureOrigins).filter(Objects::nonNull).distinct().collect(Collectors.toList())); + } + } + + /** + * Add the export. The export is only added if there isn't already a export with + * the same name + * + * @param export The export to add + * @return {@code true} if the export could be added, {@code false} otherwise + */ + public boolean add(final ApiExport export) { + boolean found = false; + for (final ApiExport c : this.exports) { + if (c.getName().equals(export.getName())) { + found = true; + break; + } + } + if (!found) { + this.exports.add(export); + } + return !found; + } + + /** + * Remove the export + * + * @param export export to remove + * @return {@code true} if the export got removed. + */ + public boolean remove(final ApiExport export) { + return this.exports.remove(export); + } + + /** + * Check if the region has exports + * + * @return {@code true} if it has any export + */ + public boolean isEmpty() { + return this.exports.isEmpty(); + } + + /** + * Unmodifiable collection of exports for this region + * + * @return The collection of exports + */ + public Collection listExports() { + return Collections.unmodifiableCollection(this.exports); + } + + /** + * Unmodifiable collection of exports for this region and all parents. + * + * @return The collection of exports + */ + public Collection listAllExports() { + final List list = new ArrayList<>(); + if (parent != null) { + list.addAll(parent.listAllExports()); + } + list.addAll(this.exports); + return Collections.unmodifiableCollection(list); + } + + /** + * Get an export by name + * + * @param name package name + * @return The export or {@code null} + */ + public ApiExport getExportByName(final String name) { + for (final ApiExport e : this.exports) { + if (e.getName().equals(name)) { + return e; + } + } + return null; + } + + /** + * Get an export by name + * + * @param name package name + * @return The export or {@code null} + */ + public ApiExport getAllExportByName(final String name) { + for (final ApiExport e : listAllExports()) { + if (e.getName().equals(name)) { + return e; + } + } + return null; + } + + /** + * Get additional properties + * + * @return Modifiable map of properties + */ + public Map getProperties() { + return this.properties; + } + + /** + * Get the parent region + * + * @return The parent region or {@code null} + */ + public ApiRegion getParent() { + return this.parent; + } + + void setParent(final ApiRegion region) { + this.parent = region; + } + + + @Override + public String toString() { + return "ApiRegion [exports=" + exports + ", properties=" + properties + ", name=" + name + ", feature-origins=" + origins + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((exports == null) ? 0 : exports.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + Arrays.hashCode(getFeatureOrigins()); + result = prime * result + ((properties == null) ? 0 : properties.hashCode()); + return result; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + ApiRegion region = (ApiRegion) o; + return exports.equals(region.exports) && + origins.equals(region.origins) && + properties.equals(region.properties) && + Objects.equals(name, region.name); + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegions.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegions.java new file mode 100644 index 000000000..e92908f44 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegions.java @@ -0,0 +1,345 @@ +/* + * 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.feature.extension.apiregions.api; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonException; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonReader; +import javax.json.JsonString; +import javax.json.JsonValue; +import javax.json.JsonValue.ValueType; +import javax.json.JsonWriter; + +import org.apache.sling.feature.Artifact; +import org.apache.sling.feature.ArtifactId; + +/** + * An api regions configuration + */ +public class ApiRegions { + + /** The name of the api regions extension. */ + public static final String EXTENSION_NAME = "api-regions"; + + private static final String NAME_KEY = "name"; + + private static final String EXPORTS_KEY = "exports"; + + private static final String TOGGLE_KEY = "toggle"; + + private static final String PREVIOUS_KEY = "previous"; + + private final List regions = new ArrayList<>(); + + /** + * Return the list of regions + * + * @return Unmodifiable list of regions, might be empty + */ + public List listRegions() { + return Collections.unmodifiableList(this.regions); + } + + /** + * Get the root regions. The root is the region which does not have a parent + * + * @return The root region or {@code null} + */ + public ApiRegion getRoot() { + if (this.regions.isEmpty()) { + return null; + } + return this.regions.get(0); + } + + /** + * Check if any region exists + * + * @return {@code true} if it has any region + */ + public boolean isEmpty() { + return this.regions.isEmpty(); + } + + /** + * Add the region. The region is only added if there isn't already a region with + * the same name + * + * @param region The region to add + * @return {@code true} if the region could be added, {@code false} otherwise + */ + public boolean add(final ApiRegion region) { + for (final ApiRegion c : this.regions) { + if (c.getName().equals(region.getName())) { + return false; + } + } + Set origins = new LinkedHashSet<>(Arrays.asList(region.getFeatureOrigins())); + + this.regions.stream() + .filter( + existingRegion -> + { + ArtifactId[] targetOrigins = existingRegion.getFeatureOrigins(); + return (targetOrigins.length == 0 && origins.isEmpty()) + || Stream.of(targetOrigins).anyMatch(origins::contains); + } + ).reduce((a,b) -> b).ifPresent(region::setParent); + + this.regions.add(region); + return true; + } + + /** + * Get a named region + * + * @param name The name + * @return The region or {@code null} + */ + public ApiRegion getRegionByName(final String name) { + ApiRegion found = null; + + for (final ApiRegion c : this.regions) { + if (c.getName().equals(name)) { + found = c; + break; + } + } + + return found; + } + + public ApiRegion[] getRegionsByFeature(final ArtifactId featureId) { + return this.regions.stream().filter( + region -> Stream.of(region.getFeatureOrigins()).anyMatch(featureId::equals) + ).toArray(ApiRegion[]::new); + } + + /** + * Get the names of the regions + * + * @return The list of regions, might be empty + */ + public List getRegionNames() { + final List names = new ArrayList<>(); + for (final ApiRegion c : this.regions) { + names.add(c.getName()); + } + return Collections.unmodifiableList(names); + } + + /** + * Convert regions into json + * + * @return The json array + * @throws IOException If generating the JSON fails + */ + public JsonArray toJSONArray() throws IOException { + final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + + for (final ApiRegion region : this.regions) { + final JsonObjectBuilder regionBuilder = Json.createObjectBuilder(); + regionBuilder.add(NAME_KEY, region.getName()); + + if (!region.listExports().isEmpty()) { + final JsonArrayBuilder expArrayBuilder = Json.createArrayBuilder(); + for (final ApiExport exp : region.listExports()) { + if (exp.getToggle() == null && exp.getPrevious() == null && exp.getProperties().isEmpty()) { + expArrayBuilder.add(exp.getName()); + } else { + final JsonObjectBuilder expBuilder = Json.createObjectBuilder(); + expBuilder.add(NAME_KEY, exp.getName()); + if (exp.getToggle() != null) { + expBuilder.add(TOGGLE_KEY, exp.getToggle()); + } + if (exp.getPrevious() != null) { + expBuilder.add(PREVIOUS_KEY, exp.getPrevious().toMvnId()); + } + for (final Map.Entry entry : exp.getProperties().entrySet()) { + expBuilder.add(entry.getKey(), entry.getValue()); + } + expArrayBuilder.add(expBuilder); + } + } + + regionBuilder.add(EXPORTS_KEY, expArrayBuilder); + } + ArtifactId[] origins = region.getFeatureOrigins(); + if (origins.length > 0) { + final JsonArrayBuilder originBuilder = Json.createArrayBuilder(); + for (ArtifactId origin : origins) { + originBuilder.add(origin.toMvnId()); + } + regionBuilder.add(Artifact.KEY_FEATURE_ORIGINS, originBuilder); + } + for (final Map.Entry entry : region.getProperties().entrySet()) { + regionBuilder.add(entry.getKey(), entry.getValue()); + } + + arrayBuilder.add(regionBuilder); + } + + return arrayBuilder.build(); + } + + /** + * Convert regions into json + * + * @return The json array as a string + * @throws IOException If generating the JSON fails + */ + public String toJSON() throws IOException { + final JsonArray array = this.toJSONArray(); + try (final StringWriter stringWriter = new StringWriter(); + final JsonWriter writer = Json.createWriter(stringWriter)) { + writer.writeArray(array); + return stringWriter.toString(); + } + } + + /** + * Parse a JSON array into an api regions object + * + * @param json The json as a string + * @return The api regions + * @throws IOException If the json could not be parsed + */ + public static ApiRegions parse(final String json) throws IOException { + try (final JsonReader reader = Json.createReader(new StringReader(json))) { + return parse(reader.readArray()); + } + } + + /** + * Parse a JSON array into an api regions object + * + * @param json The json + * @return The api regions + * @throws IOException If the json could not be parsed + */ + public static ApiRegions parse(final JsonArray json) throws IOException { + try { + final ApiRegions regions = new ApiRegions(); + + for (final JsonValue value : json) { + if (value.getValueType() != ValueType.OBJECT) { + throw new IOException("Illegal api regions json " + json); + } + final JsonObject obj = (JsonObject) value; + + final ApiRegion region = new ApiRegion(obj.getString(NAME_KEY)); + + for (final Map.Entry entry : obj.entrySet()) { + if (NAME_KEY.equals(entry.getKey())) { + continue; // already set + } else if (entry.getKey().equals(EXPORTS_KEY)) { + for (final JsonValue e : (JsonArray) entry.getValue()) { + if (e.getValueType() == ValueType.STRING) { + final String name = ((JsonString) e).getString(); + if (!name.startsWith("#")) { + final ApiExport export = new ApiExport(name); + if (!region.add(export)) { + throw new IOException("Export " + export.getName() + + " is defined twice in region " + region.getName()); + } + } + } else if (e.getValueType() == ValueType.OBJECT) { + final JsonObject expObj = (JsonObject) e; + final ApiExport export = new ApiExport(expObj.getString(NAME_KEY)); + if (!region.add(export)) { + throw new IOException("Export " + export.getName() + " is defined twice in region " + + region.getName()); + } + + for (final String key : expObj.keySet()) { + if (NAME_KEY.equals(key)) { + continue; // already set + } else if (TOGGLE_KEY.equals(key)) { + export.setToggle(expObj.getString(key)); + } else if (PREVIOUS_KEY.equals(key)) { + export.setPrevious(ArtifactId.parse(expObj.getString(key))); + } else { + export.getProperties().put(key, expObj.getString(key)); + } + } + } + } + } else if (entry.getKey().equals(Artifact.KEY_FEATURE_ORIGINS)) { + Set origins = new LinkedHashSet<>(); + for (final JsonValue origin : (JsonArray) entry.getValue()) { + origins.add(ArtifactId.fromMvnId(((JsonString) origin).getString())); + } + region.setFeatureOrigins(origins.toArray(new ArtifactId[0])); + } else { + region.getProperties().put(entry.getKey(), ((JsonString) entry.getValue()).getString()); + } + } + if (!regions.add(region)) { + throw new IOException("Region " + region.getName() + " is defined twice"); + } + } + return regions; + } catch (final JsonException | IllegalArgumentException e) { + throw new IOException(e); + } + } + + @Override + public String toString() { + return "ApiRegions [regions=" + regions + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((regions == null) ? 0 : regions.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ApiRegions other = (ApiRegions) obj; + if (regions == null) { + if (other.regions != null) + return false; + } else if (!regions.equals(other.regions)) + return false; + return true; + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/package-info.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/package-info.java new file mode 100644 index 000000000..72e56f8ba --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/api/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +@org.osgi.annotation.versioning.Version("1.0.0") +package org.apache.sling.feature.extension.apiregions.api; + + diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/LauncherProperties.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/LauncherProperties.java new file mode 100644 index 000000000..955db852f --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/LauncherProperties.java @@ -0,0 +1,163 @@ +/* + * 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.feature.extension.apiregions.launcher; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +import org.apache.sling.feature.Artifact; +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.builder.ArtifactProvider; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.io.IOUtils; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; + +public class LauncherProperties +{ + public static Properties getBundleIDtoBSNandVersionMap(Feature app, ArtifactProvider artifactProvider) { + Map map = new HashMap<>(); + + for (Artifact bundle : app.getBundles()) + { + map.computeIfAbsent(bundle.getId(), id -> + { + try(JarFile jarFile = IOUtils.getJarFileFromURL(artifactProvider.provide(id), true, null)) { + Attributes manifest = jarFile.getManifest().getMainAttributes(); + String bsn = manifest.getValue(Constants.BUNDLE_SYMBOLICNAME); + if (bsn != null && !bsn.trim().isEmpty()) + { + return bsn.trim() + "~" + Version.parseVersion(manifest.getValue(Constants.BUNDLE_VERSION)); + } + else { + return null; + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + } + + Properties result = new Properties(); + + for (Map.Entry entry : map.entrySet()) { + result.setProperty(entry.getKey().toMvnId(), entry.getValue()); + } + + return result; + } + + public static Properties getBundleIDtoFeaturesMap(Feature app) { + Map> map = new HashMap<>(); + + for (Artifact bundle : app.getBundles()) + { + map.compute(bundle.getId(), (id, features) -> + { + if (features == null) + { + features = new HashSet<>(); + } + features.addAll(Arrays.asList(bundle.getFeatureOrigins())); + return features; + }); + } + + Properties result = new Properties(); + + for (Map.Entry> entry : map.entrySet()) { + result.setProperty(entry.getKey().toMvnId(), entry.getValue().stream().map(ArtifactId::toMvnId).collect(Collectors.joining(","))); + } + + return result; + } + + public static Properties getFeatureIDtoRegionsMap(ApiRegions regions) { + Map> map = new HashMap<>(); + + for (ApiRegion region : regions.listRegions()) + { + for (ArtifactId featureId : region.getFeatureOrigins()) { + map.compute(featureId, (id, regionNames) -> { + if (regionNames == null) { + regionNames = new HashSet<>(); + } + regionNames.add(region.getName()); + for (ApiRegion parent = region.getParent(); parent != null; parent = parent.getParent()) { + regionNames.add(parent.getName()); + } + return regionNames; + }); + } + } + + Properties result = new Properties(); + + for (Map.Entry> entry : map.entrySet()) { + result.setProperty(entry.getKey().toMvnId(), String.join(",", entry.getValue())); + } + + return result; + } + + public static Properties getRegionNametoPackagesMap(ApiRegions regions) { + Map> map = new HashMap<>(); + + for (ApiRegion region : regions.listRegions()) + { + for (ApiExport export : region.listExports()) { + map.compute(region.getName(), (name, exports) -> { + if (exports == null) { + exports = new HashSet<>(); + } + exports.add(export.getName()); + return exports; + }); + } + } + + Properties result = new Properties(); + + for (Map.Entry> entry : map.entrySet()) { + result.setProperty(entry.getKey(), String.join(",", entry.getValue())); + } + + return result; + } + + + public static void save(Properties properties, File file) throws IOException { + try (FileOutputStream output = new FileOutputStream(file)) + { + properties.store(output, ""); + } + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/RegionLauncher.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/RegionLauncher.java new file mode 100644 index 000000000..850581d4a --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/RegionLauncher.java @@ -0,0 +1,71 @@ +/* + * 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.feature.extension.apiregions.launcher; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.builder.ArtifactProvider; +import org.apache.sling.feature.launcher.impl.launchers.FrameworkLauncher; +import org.apache.sling.feature.launcher.spi.LauncherPrepareContext; +import org.apache.sling.feature.launcher.spi.LauncherRunContext; + +public class RegionLauncher extends FrameworkLauncher +{ + public static final String IDBSNVER_FILENAME = "idbsnver.properties"; + public static final String BUNDLE_FEATURE_FILENAME = "bundles.properties"; + + @Override + public void prepare(LauncherPrepareContext context, ArtifactId frameworkId, Feature app) throws Exception + { + super.prepare(context, frameworkId, app); + + ArtifactProvider artifactProvider = id -> + { + try + { + return context.getArtifactFile(id); + } + catch (IOException e) + { + throw new UncheckedIOException(e); + } + }; + + + final File base = File.createTempFile("apiregions", ".properties"); + base.delete(); + base.mkdirs(); + File idbsnverFile = new File(base, IDBSNVER_FILENAME); + File bundlesFile = new File(base, BUNDLE_FEATURE_FILENAME); + + LauncherProperties.save(LauncherProperties.getBundleIDtoBSNandVersionMap(app, artifactProvider), idbsnverFile); + LauncherProperties.save(LauncherProperties.getBundleIDtoFeaturesMap(app), bundlesFile); + + app.getFrameworkProperties().put("sling.feature.apiregions.resource." + IDBSNVER_FILENAME, idbsnverFile.toURI().toURL().toString()); + app.getFrameworkProperties().put("sling.feature.apiregions.resource." + BUNDLE_FEATURE_FILENAME, bundlesFile.toURI().toURL().toString()); + } + + @Override + public int run(LauncherRunContext context, ClassLoader cl) throws Exception + { + return super.run(context, cl); + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/RegionLauncherExtension.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/RegionLauncherExtension.java new file mode 100644 index 000000000..7a180a7fe --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/launcher/RegionLauncherExtension.java @@ -0,0 +1,54 @@ +/* + * 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.feature.extension.apiregions.launcher; + +import java.io.File; +import javax.json.JsonArray; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.launcher.spi.extensions.ExtensionContext; +import org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler; + +public class RegionLauncherExtension implements ExtensionHandler +{ + public static final String FEATURE_REGION_FILENAME = "features.properties"; + public static final String REGION_PACKAGE_FILENAME = "regions.properties"; + + @Override + public boolean handle(ExtensionContext extensionContext, Extension extension) throws Exception + { + if (!extension.getName().equals(ApiRegions.EXTENSION_NAME)) + return false; + + final File base = File.createTempFile("apiregions", ".properties"); + base.delete(); + base.mkdirs(); + File featuresFile = new File(base, FEATURE_REGION_FILENAME); + File regionsFile = new File(base, REGION_PACKAGE_FILENAME); + + final ApiRegions apiRegions = ApiRegions.parse((JsonArray) extension.getJSONStructure()); + + LauncherProperties.save(LauncherProperties.getFeatureIDtoRegionsMap(apiRegions), featuresFile); + LauncherProperties.save(LauncherProperties.getRegionNametoPackagesMap(apiRegions), regionsFile); + + extensionContext.addFrameworkProperty("sling.feature.apiregions.resource." + FEATURE_REGION_FILENAME, featuresFile.toURI().toURL().toString()); + extensionContext.addFrameworkProperty("sling.feature.apiregions.resource." + REGION_PACKAGE_FILENAME, regionsFile.toURI().toURL().toString()); + + return true; + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/scanner/ApiRegionsExtensionScanner.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/scanner/ApiRegionsExtensionScanner.java new file mode 100644 index 000000000..088baff82 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/java/org/apache/sling/feature/extension/apiregions/scanner/ApiRegionsExtensionScanner.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.feature.extension.apiregions.scanner; + +import java.io.IOException; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.builder.ArtifactProvider; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.ContainerDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl; +import org.apache.sling.feature.scanner.spi.ExtensionScanner; + +public class ApiRegionsExtensionScanner implements ExtensionScanner { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME; + } + + @Override + public String getName() { + return "Api Regions extention scanner"; + } + + @Override + public ContainerDescriptor scan(Feature feature, Extension extension, ArtifactProvider provider) throws IOException { + FeatureDescriptor featureDescriptor = new FeatureDescriptorImpl(feature); + /* + * for (Artifact artifact : feature.getBundles()) { URL file = + * provider.provide(artifact.getId()); BundleDescriptor bundleDescriptor = new + * BundleDescriptorImpl(artifact, file, artifact.getStartOrder()); + * featureDescriptor.getBundleDescriptors().add(bundleDescriptor); } + */ + featureDescriptor.lock(); + + return featureDescriptor; + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask new file mode 100644 index 000000000..0744c725b --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask @@ -0,0 +1,6 @@ +org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegions +org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsDependencies +org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsDuplicates +org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsOrder +org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsBundleExportsImports +org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsCrossFeatureDups diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.builder.MergeHandler b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.builder.MergeHandler new file mode 100644 index 000000000..ec6db934a --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.builder.MergeHandler @@ -0,0 +1 @@ +org.apache.sling.feature.extension.apiregions.APIRegionMergeHandler diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.Launcher b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.Launcher new file mode 100644 index 000000000..887412dc5 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.Launcher @@ -0,0 +1 @@ +org.apache.sling.feature.extension.apiregions.launcher.RegionLauncher \ No newline at end of file diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler new file mode 100644 index 000000000..89e2efc4c --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.launcher.spi.extensions.ExtensionHandler @@ -0,0 +1 @@ +org.apache.sling.feature.extension.apiregions.launcher.RegionLauncherExtension \ No newline at end of file diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.ExtensionScanner b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.ExtensionScanner new file mode 100644 index 000000000..34846b842 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.ExtensionScanner @@ -0,0 +1 @@ +org.apache.sling.feature.extension.apiregions.scanner.ApiRegionsExtensionScanner diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandlerTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandlerTest.java new file mode 100644 index 000000000..15c0bc691 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandlerTest.java @@ -0,0 +1,154 @@ +/* + * 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.feature.extension.apiregions; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonReader; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.ExtensionState; +import org.apache.sling.feature.ExtensionType; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.builder.HandlerContext; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class APIRegionMergeHandlerTest { + private Path tempDir; + + @Before + public void setUp() throws IOException { + tempDir = Files.createTempDirectory(getClass().getSimpleName()); + } + + @After + public void tearDown() throws IOException { + // Delete the temp dir again + Files.walk(tempDir) + .sorted(Comparator.reverseOrder()) + .map(Path::toFile) + .forEach(File::delete); + } + + @Test + public void testCanMerge() { + APIRegionMergeHandler armh = new APIRegionMergeHandler(); + + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + assertTrue(armh.canMerge(ex)); + assertFalse(armh.canMerge(new Extension(ExtensionType.JSON, "foo", ExtensionState.OPTIONAL))); + } + + @Test + public void testAPIRegionMerging() throws Exception { + APIRegionMergeHandler armh = new APIRegionMergeHandler(); + + Feature tf = new Feature(ArtifactId.fromMvnId("x:t:1")); + Feature sf = new Feature(ArtifactId.fromMvnId("y:s:2")); + + Extension tgEx = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + tgEx.setJSON("[{\"name\":\"global\"," + + "\"exports\": [\"a.b.c\",\"d.e.f\"]}," + + "{\"name\":\"internal\"," + + "\"exports\":[\"xyz\"]," + + "\"some-key\":\"some-val\"}]"); + + Extension srEx = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + srEx.setJSON("[{\"name\":\"global\"," + + "\"exports\": [\"test\"]}," + + "{\"name\":\"something\"," + + "\"exports\": [\"a.ha\"]," + + "\"my-key\": \"my-val\"}]"); + + HandlerContext hc = Mockito.mock(HandlerContext.class); + armh.merge(hc, tf, sf, tgEx, srEx); + + ApiRegions expected = new ApiRegions(); + ApiRegion global = new ApiRegion("global"); + global.add(new ApiExport("a.b.c")); + global.add(new ApiExport("d.e.f")); + global.add(new ApiExport("test")); + global.setFeatureOrigins(sf.getId()); + expected.add(global); + + ApiRegion internal = new ApiRegion("internal"); + internal.add(new ApiExport("xyz")); + internal.getProperties().put("some-key", "some-val"); + expected.add(internal); + + ApiRegion something = new ApiRegion("something"); + something.add(new ApiExport("a.ha")); + something.getProperties().put("my-key", "my-val"); + something.setFeatureOrigins(sf.getId()); + expected.add(something); + + ApiRegions created = ApiRegions.parse((JsonArray) tgEx.getJSONStructure()); + + assertEquals(expected, created); + } + + + @Test + public void testRegionExportsNoInheritance() throws Exception { + APIRegionMergeHandler armh = new APIRegionMergeHandler(); + + Feature tf = new Feature(ArtifactId.fromMvnId("x:t:1")); + Feature sf = new Feature(ArtifactId.fromMvnId("y:s:2")); + + Extension srEx = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + srEx.setJSON("[{\"name\":\"global\"," + + "\"exports\": [\"a.b.c\",\"d.e.f\"]}," + + "{\"name\":\"deprecated\"," + + "\"exports\":[\"klm\",\"#ignored\",\"qrs\"]}," + + "{\"name\":\"internal\"," + + "\"exports\":[\"xyz\"]}," + + "{\"name\":\"forbidden\"," + + "\"exports\":[\"abc\",\"klm\"]}]"); + + HandlerContext hc = Mockito.mock(HandlerContext.class); + armh.merge(hc, tf, sf, null, srEx); + + Extension tgEx = tf.getExtensions().iterator().next(); + + String expectedJSON = "[{\"name\":\"global\",\"exports\":[\"a.b.c\",\"d.e.f\"],\"feature-origins\":[\"y:s:2\"]}," + + "{\"name\":\"deprecated\",\"exports\":[\"klm\",\"qrs\"],\"feature-origins\":[\"y:s:2\"]}," + + "{\"name\":\"internal\",\"exports\":[\"xyz\"],\"feature-origins\":[\"y:s:2\"]}," + + "{\"name\":\"forbidden\",\"exports\":[\"abc\",\"klm\"],\"feature-origins\":[\"y:s:2\"]}]"; + JsonReader er = Json.createReader(new StringReader(expectedJSON)); + JsonReader ar = Json.createReader(new StringReader(tgEx.getJSON())); + JsonArray ea = er.readArray(); + JsonArray aa = ar.readArray(); + + assertEquals(ea, aa); + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java new file mode 100644 index 000000000..e8decf5b4 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java @@ -0,0 +1,204 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.StringReader; +import java.net.URL; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.jar.Manifest; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonReader; +import javax.json.stream.JsonParsingException; + +import org.apache.sling.feature.Artifact; +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; +import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class AbstractApiRegionsAnalyserTaskTest { + + protected abstract T newTask(); + + private AnalyserTask analyserTask; + + @Before + public void setUp() { + analyserTask = newTask(); + } + + @After + public void tearDown() { + analyserTask = null; + } + + @Test + public void testNoApiRegionsExtension() throws Exception { + List errors = execute((Extension) null); + assertTrue(errors.isEmpty()); + } + + @Test + public void testNullApiRegionsJSON() throws Exception { + List errors = execute((String) null); + assertTrue(errors.isEmpty()); + } + + @Test + public void testEmptyApiRegionsJSON() throws Exception { + List errors = execute(""); + assertTrue(errors.isEmpty()); + } + + @Test + public void testApiRegionsIsNotJSON() throws Exception { + List errors = execute("this is not a JSON string"); + + assertFalse(errors.isEmpty()); + assertTrue(errors.iterator().next().contains("does not represent a valid JSON 'api-regions'")); + } + + protected final List execute(String apiRegionJSON) throws Exception { + Extension extension = mock(Extension.class); + when(extension.getJSON()).thenReturn(apiRegionJSON); + if (apiRegionJSON != null) { + JsonReader reader = Json.createReader(new StringReader(apiRegionJSON)); + JsonArray array = null; + try { + array = reader.readArray(); + } catch (final JsonParsingException ignore) { + + } + when(extension.getJSONStructure()).thenReturn(array); + } + return execute(extension); + } + + protected final List execute(Extension extension) throws Exception { + Extensions extensions = mock(Extensions.class); + when(extensions.getByName("api-regions")).thenReturn(extension); + + Feature feature = mock(Feature.class); + when(feature.getId()).thenReturn(new ArtifactId("org.apache.sling.testing", + "org.apache.sling.testing.apiregions", + "1.0.0", + null, + null)); + when(feature.getExtensions()).thenReturn(extensions); + + AnalyserTaskContext ctx = mock(AnalyserTaskContext.class); + when(ctx.getFeature()).thenReturn(feature); + @SuppressWarnings("unchecked") + Map cfg = Mockito.mock(Map.class); + when(ctx.getConfiguration()).thenReturn(cfg); + when(cfg.getOrDefault(anyString(), anyString())).thenAnswer(new Answer() { + + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + return (String) args[1]; + } + + }); + + PackageInfo packageInfo = new PackageInfo("org.osgi.util.function", "1.0", false, Collections.singleton("org.objectweb.asm")); + + BundleDescriptor bundleDescriptor = new TestBundleDescriptor(); + bundleDescriptor.getExportedPackages().add(packageInfo); + + FeatureDescriptor featureDescriptor = new FeatureDescriptorImpl(feature); + featureDescriptor.getBundleDescriptors().add(bundleDescriptor); + + when(ctx.getFeatureDescriptor()).thenReturn(featureDescriptor); + + List errors = new LinkedList<>(); + doAnswer(invocation -> { + String error = invocation.getArgument(0); + errors.add(error); + return null; + }).when(ctx).reportError(anyString()); + + analyserTask.execute(ctx); + + return errors; + } + + private static final class TestBundleDescriptor extends BundleDescriptor { + TestBundleDescriptor() { + super("org.osgi:org.osgi.util.function:1.0.0"); + } + + @Override + public URL getArtifactFile() { + // do nothing + return null; + } + + @Override + public Artifact getArtifact() { + return new Artifact(ArtifactId.fromMvnId("org.osgi:org.osgi.util.function:1.0.0")); + } + + @Override + public Manifest getManifest() { + // do nothing + return null; + } + + @Override + public String getBundleVersion() { + // do nothing + return null; + } + + @Override + public String getBundleSymbolicName() { + // do nothing + return null; + } + + @Override + public int getBundleStartLevel() { + // do nothing + return 0; + } + + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java new file mode 100644 index 000000000..3801cc7ae --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImportsTest.java @@ -0,0 +1,381 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.feature.Artifact; +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.ExtensionState; +import org.apache.sling.feature.ExtensionType; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.impl.BundleDescriptorImpl; +import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mockito; +import static org.junit.Assert.assertEquals; + +public class CheckApiRegionsBundleExportsImportsTest { + private static File resourceRoot; + + @BeforeClass + public static void setupClass() { + resourceRoot = + new File(CheckApiRegionsBundleExportsImportsTest.class. + getResource("/test-framework.jar").getFile()).getParentFile(); + } + + @Test + public void testId() { + assertEquals(ApiRegions.EXTENSION_NAME + "-exportsimports", new CheckApiRegionsBundleExportsImports().getId()); + } + + @Test + /* + * Bundle b3 imports org.foo.e, but no bundle exports it. The feature is marked + * as complete which it isn't + */ + public void testImportExportNoRegionsMarkedAsComplete() throws Exception { + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + f.setComplete(true); + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar"); + fdAddBundle(fd, "g:b3:1", "test-bundle3.jar"); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + t.execute(ctx); + + Mockito.verify(ctx, Mockito.times(2)).reportError(Mockito.anyString()); + Mockito.verify(ctx).reportError(Mockito.contains("org.foo.e")); + Mockito.verify(ctx).reportError(Mockito.contains("marked as 'complete'")); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + public void testImportExportNoRegionsAllOk() throws Exception { + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar"); + fdAddBundle(fd, "g:b2:1", "test-bundle2.jar"); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + t.execute(ctx); + + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle b3 imports org.foo.e, but no bundle exports it + */ + public void testImportExportNoRegionsMissing() throws Exception { + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar"); + fdAddBundle(fd, "g:b3:1", "test-bundle3.jar"); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + t.execute(ctx); + + Mockito.verify(ctx).reportError(Mockito.contains("org.foo.e")); + Mockito.verify(ctx, Mockito.times(1)).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle 2 imports org.foo.b from bundle 1, but bundle 1 exports it in a + * different region, bundle 2 is in no region. + */ + public void testImportExportWithRegionsMissing() throws Exception { + String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f:1\"]}]"; + + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + ex.setJSON(exJson); + f.getExtensions().add(ex); + + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", f.getId()); + fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1")); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + t.execute(ctx); + + Mockito.verify(ctx).reportError(Mockito.contains("org.foo.b")); + Mockito.verify(ctx, Mockito.times(1)).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle 2 imports org.foo.b from bundle 1, but bundle 1 exports it in a different + * region, bundle 1 is in something region, and bundle 2 is in somethingelse region. + */ + public void testImportExportWithRegionMismatch() throws Exception { + String exJson = + "[" + + "{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f:1\"]}," + + "{\"name\": \"someotherthing\", \"exports\": [],\"feature-origins\":[\"f:f:1\"]}," + + "{\"name\": \"somethingelse\", \"exports\": [],\"feature-origins\":[\"f:f2:1\"]}" + + "]"; + + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + ex.setJSON(exJson); + f.getExtensions().add(ex); + + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", fd.getFeature().getId()); + fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1")); + + Map cfgMap = new HashMap(); + cfgMap.put("ignoreAPIRegions", "false"); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + Mockito.when(ctx.getConfiguration()).thenReturn(cfgMap); + t.execute(ctx); + + Mockito.verify(ctx).reportError(Mockito.contains("org.foo.b")); + Mockito.verify(ctx).reportError(Mockito.contains("something")); + Mockito.verify(ctx).reportError(Mockito.contains("somethingelse")); + Mockito.verify(ctx, Mockito.times(1)).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle 2 imports org.foo.b from bundle 1, but bundle 1 exports it in a different + * region, bundle 1 is in something region, and bundle 2 is in somethingelse region. + * However this should still pass as the analyzer is configured to ignore regions. + */ + public void testImportExportWithRegionMismatchIgnoreRegions() throws Exception { + String exJson = + "[" + + "{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f:1\"]}," + + "{\"name\": \"someotherthing\", \"exports\": [],\"feature-origins\":[\"f:f:1\"]}," + + "{\"name\": \"somethingelse\", \"exports\": [],\"feature-origins\":[\"f:f2:1\"]}" + + "]"; + + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + ex.setJSON(exJson); + f.getExtensions().add(ex); + + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", fd.getFeature().getId()); + fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1")); + + Map cfgMap = new HashMap(); + cfgMap.put("ignoreAPIRegions", "true"); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + Mockito.when(ctx.getConfiguration()).thenReturn(cfgMap); + t.execute(ctx); + + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle 3 imports org.foo.a from Bundle 1 and org.foo.e from Bundle 4. + * The Feature is in a region called 'blah' which exports nothing, but because + * all these bundles are in the same feature they can all see each other. + */ + public void testImportFromOtherBundleInSameFeature() throws Exception { + String exJson = "[{\"name\": \"blah\",\"feature-origins\":[\"f:f:2:1\",\"f:f3:1\"]}" + + ",{\"name\": \"something\",\"feature-origins\":[\"f:f:1\"]}" + + ",{\"name\": \"someotherthing\",\"feature-origins\":[\"f:f:1\"]}" + + ",{\"name\": \"abc\",\"feature-origins\":[\"f:2:1\"]}" + + ",{\"name\": \"xyz\",\"feature-origins\":[\"f:f2:1\"]}" + + "]"; // no exports + + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:2")); + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + ex.setJSON(exJson); + f.getExtensions().add(ex); + + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", ArtifactId.fromMvnId("f:f3:1")); + fdAddBundle(fd, "g:b3:1", "test-bundle3.jar", ArtifactId.fromMvnId("f:f3:1")); + fdAddBundle(fd, "g:b4:1", "test-bundle4.jar", ArtifactId.fromMvnId("f:f3:1")); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + t.execute(ctx); + + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle 2 imports org.foo.b from bundle 1. Bundle 1 exports it in the something region + * and bundle 2 imports it in the something region, so this succeeds. + */ + public void testImportExportWithMatchingRegion() throws Exception { + String exJson = "[{\"name\": \"something\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f:1\",\"f:f2:1\"]}" + + ",{\"name\": \"someotherthing\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f:1\"]}]"; + + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + ex.setJSON(exJson); + f.getExtensions().add(ex); + + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", f.getId()); + fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1")); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + t.execute(ctx); + + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle 2 imports org.foo.b from bundle 1. Bundle 1 exports it in the global region. + * Bundle 2 is not explicitly part of the global region, but can still see it + */ + public void testImportFromGlobalAlwaysSucceeds() throws Exception { + String exJson = "[{\"name\": \"global\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f:1\"]}" + + ",{\"name\": \"something\", \"exports\": [],\"feature-origins\":[\"f:f2:1\"]}" + + ",{\"name\": \"lalala\", \"exports\": [],\"feature-origins\":[\"f:f:1\"]}" + + ",{\"name\": \"someotherthing\", \"exports\": [],\"feature-origins\":[\"f:f:1\"]}]" + ; + + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + ex.setJSON(exJson); + f.getExtensions().add(ex); + + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar",f.getId()); + fdAddBundle(fd, "g:b2:1", "test-bundle2.jar",ArtifactId.fromMvnId("f:f2:1")); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + Mockito.when(ctx.getConfiguration()).thenReturn( + Collections.singletonMap("fileStorage", + resourceRoot + "/origins/testImportFromGlobalAlwaysSucceeds")); + t.execute(ctx); + + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + } + + @Test + /* + * Bundle 2 imports org.foo.b from bundle 1. Bundle 1 exports it in region1. + * Regions + */ + public void testImportFromInheritedRegionSucceeds() throws Exception { + String exJson = "[" + + "{\"name\": \"region1\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f1:1\"]}," + + "{\"name\": \"region2\", \"exports\": [\"org.foo.c\"],\"feature-origins\":[\"f:f1:1\",\"f:f2:1\"]}," + + "{\"name\": \"region3\", \"exports\": [],\"feature-origins\":[\"f:f2:1\"]}" + + "]"; + + CheckApiRegionsBundleExportsImports t = new CheckApiRegionsBundleExportsImports(); + + Feature f = new Feature(ArtifactId.fromMvnId("f:f:1")); + Extension ex = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + ex.setJSON(exJson); + f.getExtensions().add(ex); + + + FeatureDescriptor fd = new FeatureDescriptorImpl(f); + + fdAddBundle(fd, "g:b1:1", "test-bundle1.jar", f.getId()); + fdAddBundle(fd, "g:b2:1", "test-bundle2.jar", ArtifactId.fromMvnId("f:f2:1")); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getFeature()).thenReturn(f); + Mockito.when(ctx.getFeatureDescriptor()).thenReturn(fd); + t.execute(ctx); + + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + Mockito.verify(ctx, Mockito.never()).reportWarning(Mockito.anyString()); + + } + + private void fdAddBundle(FeatureDescriptor fd, String id, String file, ArtifactId... origins) throws IOException { + Artifact artifact = new Artifact(ArtifactId.fromMvnId(id)); + artifact.setFeatureOrigins(origins); + BundleDescriptor bd1 = new BundleDescriptorImpl( + artifact, new File(resourceRoot, file).toURI().toURL(), 0); + fd.getBundleDescriptors().add(bd1); + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDupsTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDupsTest.java new file mode 100644 index 000000000..3765fe353 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDupsTest.java @@ -0,0 +1,216 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.Analyser; +import org.apache.sling.feature.analyser.AnalyserResult; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.builder.ArtifactProvider; +import org.apache.sling.feature.extension.apiregions.scanner.ApiRegionsExtensionScanner; +import org.apache.sling.feature.io.json.FeatureJSONReader; +import org.apache.sling.feature.scanner.Scanner; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class CheckApiRegionsCrossFeatureDupsTest { + @Test + public void testOverlapError() throws Exception { + Path fp = new File(getClass().getResource("/crossfeatdups/fm1.json").getFile()).toPath(); + String fm = new String(Files.readAllBytes(fp)); + + Feature f = FeatureJSONReader.read(new StringReader(fm), null); + + Scanner scanner = getScanner(); + AnalyserTask at = new CheckApiRegionsCrossFeatureDups(); + Map> configs = + Collections.singletonMap("api-regions-crossfeature-dups", + Collections.singletonMap("warningPackages", "x.y.z")); + Analyser a = new Analyser(scanner, configs, at); + AnalyserResult res = a.analyse(f); + assertEquals(1, res.getErrors().size()); + assertEquals(0, res.getWarnings().size()); + + String err = res.getErrors().get(0); + assertTrue(err.contains("a.b.c")); + assertTrue(err.contains("g:f3:1")); + assertTrue(err.contains("feature-export2")); + } + + @Test + public void testOverlapWarning() throws Exception { + Path fp = new File(getClass().getResource("/crossfeatdups/fm1.json").getFile()).toPath(); + String fm = new String(Files.readAllBytes(fp)); + + Feature f = FeatureJSONReader.read(new StringReader(fm), null); + + Scanner scanner = getScanner(); + CheckApiRegionsCrossFeatureDups at = new CheckApiRegionsCrossFeatureDups(); + Map cfg = new HashMap<>(); + cfg.put("warningPackages", "a.b.c"); + cfg.put("ignoredPackages", "x.y.z"); + Map> configs = + Collections.singletonMap("api-regions-crossfeature-dups", cfg); + Analyser a = new Analyser(scanner, configs, at); + AnalyserResult res = a.analyse(f); + assertEquals(0, res.getErrors().size()); + assertEquals(1, res.getWarnings().size()); + + String err = res.getWarnings().get(0); + assertTrue(err.contains("a.b.c")); + assertTrue(err.contains("g:f3:1")); + assertTrue(err.contains("feature-export2")); + } + + @Test + public void testOverlapWarning2() throws Exception { + Path fp = new File(getClass().getResource("/crossfeatdups/fm1.json").getFile()).toPath(); + String fm = new String(Files.readAllBytes(fp)); + + Feature f = FeatureJSONReader.read(new StringReader(fm), null); + + Scanner scanner = getScanner(); + CheckApiRegionsCrossFeatureDups at = new CheckApiRegionsCrossFeatureDups(); + Map cfg = new HashMap<>(); + cfg.put("warningPackages", "x.*, a.*"); + Map> configs = + Collections.singletonMap("api-regions-crossfeature-dups", cfg); + Analyser a = new Analyser(scanner, configs, at); + AnalyserResult res = a.analyse(f); + assertEquals(0, res.getErrors().size()); + assertEquals(1, res.getWarnings().size()); + + String err = res.getWarnings().get(0); + assertTrue(err.contains("a.b.c")); + assertTrue(err.contains("g:f3:1")); + assertTrue(err.contains("feature-export2")); + } + + @Test + public void testOverlapIgnore() throws Exception { + Path fp = new File(getClass().getResource("/crossfeatdups/fm1.json").getFile()).toPath(); + String fm = new String(Files.readAllBytes(fp)); + + Feature f = FeatureJSONReader.read(new StringReader(fm), null); + + Scanner scanner = getScanner(); + CheckApiRegionsCrossFeatureDups at = new CheckApiRegionsCrossFeatureDups(); + Map cfg = new HashMap<>(); + cfg.put("ignoredPackages", "a.b.c"); + Map> configs = + Collections.singletonMap("api-regions-crossfeature-dups", cfg); + Analyser a = new Analyser(scanner, configs, at); + AnalyserResult res = a.analyse(f); + assertEquals(0, res.getErrors().size()); + assertEquals(0, res.getWarnings().size()); + } + + @Test + public void testOverlapError2() throws Exception { + Path fp = new File(getClass().getResource("/crossfeatdups/fm1.json").getFile()).toPath(); + String fm = new String(Files.readAllBytes(fp)); + + Feature f = FeatureJSONReader.read(new StringReader(fm), null); + + Scanner scanner = getScanner2(); + AnalyserTask at = new CheckApiRegionsCrossFeatureDups(); + Analyser a = new Analyser(scanner, at); + AnalyserResult res = a.analyse(f); + assertEquals(2, res.getErrors().size()); + assertEquals(0, res.getWarnings().size()); + + String allErrs = res.getErrors().get(0) + res.getErrors().get(1); + assertTrue(allErrs.contains("a.b.c")); + assertTrue(allErrs.contains("zzz.zzz")); + } + + @Test + public void testOverlapErrorOnlyGlobal() throws Exception { + Path fp = new File(getClass().getResource("/crossfeatdups/fm1.json").getFile()).toPath(); + String fm = new String(Files.readAllBytes(fp)); + + Feature f = FeatureJSONReader.read(new StringReader(fm), null); + + Scanner scanner = getScanner2(); + AnalyserTask at = new CheckApiRegionsCrossFeatureDups(); + Map> configs = + Collections.singletonMap("api-regions-crossfeature-dups", + Collections.singletonMap("regions", "something,global")); + Analyser a = new Analyser(scanner, configs, at); + AnalyserResult res = a.analyse(f); + assertEquals(1, res.getErrors().size()); + assertEquals(0, res.getWarnings().size()); + + String err = res.getErrors().get(0); + assertTrue(err.contains("zzz.zzz")); + } + + private Scanner getScanner() throws IOException { + ArtifactProvider ap = new ArtifactProvider() { + @Override + public URL provide(ArtifactId id) { + switch (id.toMvnId()) { + case "g:exp1:1": + return getClass().getResource("/crossfeatdups/test-bundles/feature-export.jar"); + case "g:exp2:1": + return getClass().getResource("/crossfeatdups/test-bundles/feature-export2.jar"); + case "g:noexp:1": + return getClass().getResource("/crossfeatdups/test-bundles/no-exports.jar"); + case "g:extra:1": + return getClass().getResource("/crossfeatdups/test-bundles/no-exports.jar"); + } + return null; + } + }; + Scanner scanner = new Scanner(ap, Collections.singletonList(new ApiRegionsExtensionScanner()), Collections.emptyList()); + return scanner; + } + + private Scanner getScanner2() throws IOException { + ArtifactProvider ap = new ArtifactProvider() { + @Override + public URL provide(ArtifactId id) { + switch (id.toMvnId()) { + case "g:exp1:1": + return getClass().getResource("/crossfeatdups/test-bundles/feature-export.jar"); + case "g:exp2:1": + return getClass().getResource("/crossfeatdups/test-bundles/feature-export2.jar"); + case "g:noexp:1": + return getClass().getResource("/crossfeatdups/test-bundles/no-exports.jar"); + case "g:extra:1": + return getClass().getResource("/crossfeatdups/test-bundles/feature-export3.jar"); + } + return null; + } + }; + Scanner scanner = new Scanner(ap, Collections.singletonList(new ApiRegionsExtensionScanner()), Collections.emptyList()); + return scanner; + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDependenciesTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDependenciesTest.java new file mode 100644 index 000000000..bde744d83 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDependenciesTest.java @@ -0,0 +1,60 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.util.List; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CheckApiRegionsDependenciesTest extends AbstractApiRegionsAnalyserTaskTest { + + @Override + protected CheckApiRegionsDependencies newTask() { + return new CheckApiRegionsDependencies(); + } + + @Test + public void testNotValidApiRegionJson() throws Exception { + List errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]},{\"name\": \"deprecated\",\"exports\": [\"org.objectweb.asm\"]}]"); + + assertFalse(errors.isEmpty()); + assertEquals( + "Bundle 'org.osgi:org.osgi.util.function:1.0.0' (defined in feature 'org.apache.sling.testing:org.apache.sling.testing.apiregions:1.0.0') declares 'org.osgi.util.function' in the 'Export-Package' header, enlisted in the 'global' region, which uses 'org.objectweb.asm' package that is in the 'deprecated' region", + errors.iterator().next() + ); + } + + @Test + public void testPackageEnlistedInBothRegions() throws Exception { + List errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]},{\"name\": \"deprecated\",\"exports\": [\"org.osgi.util.function\"]}]"); + + assertFalse(errors.isEmpty()); + assertEquals( + "Bundle 'org.osgi:org.osgi.util.function:1.0.0' (defined in feature 'org.apache.sling.testing:org.apache.sling.testing.apiregions:1.0.0') declares 'org.osgi.util.function' in the 'Export-Package' header that is enlisted in both exporting 'global' and hiding 'deprecated' APIs regions, please adjust Feature settings", + errors.iterator().next()); + } + + @Test + public void testValidApiRegionJson() throws Exception { + List errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]}]"); + assertTrue(errors.isEmpty()); + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicatesTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicatesTest.java new file mode 100644 index 000000000..66cc33b96 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsDuplicatesTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.feature.extension.apiregions.analyser; + +import java.util.List; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CheckApiRegionsDuplicatesTest extends AbstractApiRegionsAnalyserTaskTest { + + @Override + protected CheckApiRegionsDuplicates newTask() { + return new CheckApiRegionsDuplicates(); + } + + @Test + public void testDetectDuplicates() throws Exception { + List errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]},{\"name\": \"deprecated\",\"exports\": [\"org.osgi.util.function\"]}]"); + + assertFalse(errors.isEmpty()); + assertEquals(1, errors.size()); + System.out.println(errors); + assertTrue(errors.iterator() + .next() + .startsWith("Regions 'global' and 'deprecated' defined in feature 'org.apache.sling.testing:org.apache.sling.testing.apiregions:1.0.0' declare both 1 package(s)")); + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsOrderTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsOrderTest.java new file mode 100644 index 000000000..394812947 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsOrderTest.java @@ -0,0 +1,156 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.ExtensionState; +import org.apache.sling.feature.ExtensionType; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.junit.Test; +import org.mockito.Mockito; + +public class CheckApiRegionsOrderTest { + @Test + public void testValidOrder() throws Exception { + CheckApiRegionsOrder caro = new CheckApiRegionsOrder(); + + Extension e = new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME, ExtensionState.OPTIONAL); + e.setJSON("[{\"name\":\"deprecated\"," + + "\"exports\": [\"a.b.c\"]}," + + "{\"name\":\"internal\"," + + "\"exports\": [\"g.h.i\"]}]"); + + Feature f = new Feature(ArtifactId.fromMvnId("a:b:1")); + f.getExtensions().add(e); + + Map cfgMap = new HashMap<>(); + cfgMap.put("order", "deprecated, internal "); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getConfiguration()).thenReturn(cfgMap); + Mockito.when(ctx.getFeature()).thenReturn(f); + + caro.execute(ctx); + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + } + + @Test + public void testInvalidOrder() throws Exception { + CheckApiRegionsOrder caro = new CheckApiRegionsOrder(); + + Extension e = new Extension(ExtensionType.JSON, ApiRegions.EXTENSION_NAME, ExtensionState.OPTIONAL); + e.setJSON("[{\"name\":\"deprecated\"," + + "\"exports\": [\"a.b.c\"]}," + + "{\"name\":\"internal\"," + + "\"exports\": [\"d.e.f\"]}," + + "{\"name\":\"deprecated\"," + + "\"exports\": [\"g.h.i\"]}]"); + + Feature f = new Feature(ArtifactId.fromMvnId("a:b:1")); + f.getExtensions().add(e); + + Map cfgMap = new HashMap<>(); + cfgMap.put("order", "deprecated, internal "); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getConfiguration()).thenReturn(cfgMap); + Mockito.when(ctx.getFeature()).thenReturn(f); + + caro.execute(ctx); + Mockito.verify(ctx).reportError(Mockito.contains("Invalid api regions")); + } + + @Test + public void testInvalidRegion() throws Exception { + CheckApiRegionsOrder caro = new CheckApiRegionsOrder(); + + Extension e = new Extension(ExtensionType.JSON, "api-regions", ExtensionState.OPTIONAL); + e.setJSON("[{\"name\":\"foo\"}]"); + + Feature f = new Feature(ArtifactId.fromMvnId("a:b:1")); + f.getExtensions().add(e); + + Map cfgMap = new HashMap<>(); + cfgMap.put("order", "bar"); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getConfiguration()).thenReturn(cfgMap); + Mockito.when(ctx.getFeature()).thenReturn(f); + + caro.execute(ctx); + Mockito.verify(ctx).reportError(Mockito.contains("undeclared")); + } + + @Test + public void testNoExtensionsIsValid() throws Exception { + CheckApiRegionsOrder caro = new CheckApiRegionsOrder(); + + Map cfgMap = new HashMap<>(); + cfgMap.put("order", "deprecated, internal "); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getConfiguration()).thenReturn(cfgMap); + Mockito.when(ctx.getFeature()).thenReturn(new Feature(ArtifactId.fromMvnId("a:b:1"))); + + caro.execute(ctx); + Mockito.verify(ctx, Mockito.never()).reportError(Mockito.anyString()); + } + + @Test + public void testEmptyOrder() throws Exception { + CheckApiRegionsOrder caro = new CheckApiRegionsOrder(); + + Map cfgMap = new HashMap<>(); + cfgMap.put("order", " "); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getConfiguration()).thenReturn(cfgMap); + Mockito.when(ctx.getFeature()).thenReturn(new Feature(ArtifactId.fromMvnId("a:b:1"))); + + caro.execute(ctx); + Mockito.verify(ctx).reportError(Mockito.contains("No regions")); + } + + @Test + public void testNoFeature() throws Exception { + CheckApiRegionsOrder caro = new CheckApiRegionsOrder(); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getConfiguration()).thenReturn(new HashMap<>()); + + caro.execute(ctx); + Mockito.verify(ctx).reportError(Mockito.contains("No feature")); + } + + @Test + public void testNoOrderConfig() throws Exception { + CheckApiRegionsOrder caro = new CheckApiRegionsOrder(); + + AnalyserTaskContext ctx = Mockito.mock(AnalyserTaskContext.class); + Mockito.when(ctx.getConfiguration()).thenReturn(new HashMap<>()); + Mockito.when(ctx.getFeature()).thenReturn(new Feature(ArtifactId.fromMvnId("a:b:1"))); + + caro.execute(ctx); + Mockito.verify(ctx).reportError(Mockito.contains("'order'")); + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsTest.java new file mode 100644 index 000000000..ba2a26a1f --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsTest.java @@ -0,0 +1,48 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.util.List; + +import org.junit.Test; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CheckApiRegionsTest extends AbstractApiRegionsAnalyserTaskTest { + + @Override + protected CheckApiRegions newTask() { + return new CheckApiRegions(); + } + + @Test + public void testNotValidApiRegionJson() throws Exception { + List errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function.doesnotexist\"]}]"); + + assertFalse(errors.isEmpty()); + assertTrue(errors.iterator() + .next() + .startsWith("Region 'global' defined in feature 'org.apache.sling.testing:org.apache.sling.testing.apiregions:1.0.0' declares 1 package which is not exported by any bundle")); + } + + @Test + public void testValidApiRegionJson() throws Exception { + List errors = execute("[{\"name\": \"global\",\"exports\": [\"org.osgi.util.function\"]}]"); + assertTrue(errors.toString(), errors.isEmpty()); + } + +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/api/TestApiRegions.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/api/TestApiRegions.java new file mode 100644 index 000000000..0ad697cd6 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/api/TestApiRegions.java @@ -0,0 +1,135 @@ +/* + * 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.feature.extension.apiregions.api; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; + +import org.apache.sling.feature.ArtifactId; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class TestApiRegions { + + private String readJSON(final String name) throws IOException { + try (final Reader reader = new InputStreamReader( + TestApiRegions.class.getResourceAsStream("/json/" + name + ".json"), + "UTF-8"); final Writer writer = new StringWriter()) { + int l; + char[] buf = new char[2048]; + while ((l = reader.read(buf)) > -1) { + writer.write(buf, 0, l); + } + + return writer.toString(); + } + } + + @Test + public void testParsing() throws Exception { + final String json = readJSON("apis"); + + final ApiRegions regions = ApiRegions.parse(json); + assertNotNull(regions); + + assertEquals(2, regions.listRegions().size()); + + final ApiRegion global = regions.listRegions().get(0); + assertEquals("global", global.getName()); + + assertEquals(2, global.listExports().size()); + + final ApiRegion internal = regions.listRegions().get(1); + assertEquals("internal", internal.getName()); + + assertEquals(1, internal.listExports().size()); + } + + @Test + public void testOrdering() throws Exception { + final ApiRegions regions = new ApiRegions(); + final ApiRegion one = new ApiRegion("one"); + one.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + final ApiRegion two = new ApiRegion("two"); + two.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + final ApiRegion three = new ApiRegion("three"); + three.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + + final ApiRegion duplicate = new ApiRegion("two"); + duplicate.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + final ApiRegion other = new ApiRegion("other"); + other.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + + assertTrue(regions.add(one)); + assertTrue(regions.add(two)); + assertTrue(regions.add(three)); + + assertFalse(regions.add(duplicate)); + + assertEquals(3, regions.listRegions().size()); + + assertNull(one.getParent()); + assertEquals(one, two.getParent()); + assertEquals(two, three.getParent()); + } + + @Test + public void testExports() throws Exception { + final ApiRegions regions = new ApiRegions(); + + final ApiRegion one = new ApiRegion("one"); + one.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + one.add(new ApiExport("a")); + + final ApiRegion two = new ApiRegion("two"); + two.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + two.add(new ApiExport("b")); + + final ApiRegion three = new ApiRegion("three"); + three.setFeatureOrigins(ArtifactId.fromMvnId("f:f1:1")); + three.add(new ApiExport("c")); + + assertTrue(regions.add(one)); + assertTrue(regions.add(two)); + assertTrue(regions.add(three)); + + assertEquals(1, one.listAllExports().size()); + assertTrue(one.listAllExports().contains(new ApiExport("a"))); + assertEquals(1, one.listExports().size()); + assertTrue(one.listExports().contains(new ApiExport("a"))); + + assertEquals(2, two.listAllExports().size()); + assertTrue(two.listAllExports().contains(new ApiExport("a"))); + assertTrue(two.listAllExports().contains(new ApiExport("b"))); + assertEquals(1, two.listExports().size()); + assertTrue(two.listExports().contains(new ApiExport("b"))); + + assertEquals(3, three.listAllExports().size()); + assertTrue(three.listAllExports().contains(new ApiExport("a"))); + assertTrue(three.listAllExports().contains(new ApiExport("b"))); + assertTrue(three.listAllExports().contains(new ApiExport("c"))); + assertEquals(1, three.listExports().size()); + assertTrue(three.listExports().contains(new ApiExport("c"))); + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/launcher/LauncherPropertiesTest.java b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/launcher/LauncherPropertiesTest.java new file mode 100644 index 000000000..6fe8b2866 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/java/org/apache/sling/feature/extension/apiregions/launcher/LauncherPropertiesTest.java @@ -0,0 +1,63 @@ +/* + * 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.feature.extension.apiregions.launcher; + +import java.io.IOException; +import java.util.Properties; + +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.junit.Assert; +import org.junit.Test; + +public class LauncherPropertiesTest +{ + @Test + public void testInheritedFeature() throws IOException + { + ApiRegions apiRegions = ApiRegions.parse("[" + + "{\"name\": \"region1\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f1:1\"]}," + + "{\"name\": \"region2\", \"exports\": [\"org.foo.c\"],\"feature-origins\":[\"f:f1:1\",\"f:f2:1\"]}," + + "{\"name\": \"region3\", \"exports\": [],\"feature-origins\":[\"f:f2:1\"]}," + + "{\"name\": \"region4\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f1:1\"]}" + + "]"); + + Properties properties = LauncherProperties.getFeatureIDtoRegionsMap(apiRegions); + + Assert.assertNotNull(properties); + Assert.assertEquals("region1,region2,region4", properties.getProperty("f:f1:1")); + + Assert.assertEquals("region1,region2,region3", properties.getProperty("f:f2:1")); + } + + @Test + public void testNotInheritedFeature() throws IOException + { + ApiRegions apiRegions = ApiRegions.parse("[" + + "{\"name\": \"region1\", \"exports\": [\"org.foo.b\"],\"feature-origins\":[\"f:f1:1\"]}," + + "{\"name\": \"region2\", \"exports\": [\"org.foo.c\"],\"feature-origins\":[\"f:f1:1\"]}," + + "{\"name\": \"region3\", \"exports\": [],\"feature-origins\":[\"f:f2:1\"]}," + + "{\"name\": \"region4\", \"exports\": [],\"feature-origins\":[\"f:f2:1\"]}" + + "]"); + + Properties properties = LauncherProperties.getFeatureIDtoRegionsMap(apiRegions); + + Assert.assertNotNull(properties); + Assert.assertEquals("region1,region2", properties.getProperty("f:f1:1")); + + Assert.assertEquals("region3,region4", properties.getProperty("f:f2:1")); + } +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b1/MANIFEST.MF b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b1/MANIFEST.MF new file mode 100644 index 000000000..28bba7a93 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b1/MANIFEST.MF @@ -0,0 +1,3 @@ +Bundle-SymbolicName: b1 +Bundle-Version: 1.0.0 + diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b1/b1.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b1/b1.jar new file mode 100644 index 000000000..6798c5e01 Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b1/b1.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b2/MANIFEST.MF b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b2/MANIFEST.MF new file mode 100644 index 000000000..653d2b0cd --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b2/MANIFEST.MF @@ -0,0 +1,3 @@ +Bundle-SymbolicName: b2 +Bundle-Version: 1.2.3 + diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b2/b2.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b2/b2.jar new file mode 100644 index 000000000..c9cfb6b83 Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b2/b2.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b3/MANIFEST.MF b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b3/MANIFEST.MF new file mode 100644 index 000000000..bf9143715 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b3/MANIFEST.MF @@ -0,0 +1,2 @@ +NotABundle: me + diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b3/b3.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b3/b3.jar new file mode 100644 index 000000000..bca1724d6 Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/b3/b3.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/fm1.json b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/fm1.json new file mode 100644 index 000000000..b0a037b58 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/fm1.json @@ -0,0 +1,35 @@ +{ + "id":"g:fm1:1.2.3", + "bundles":[ + { + "id":"g:exp1:1", + "feature-origins":"g:f1:1" + }, + { + "id":"g:exp2:1", + "feature-origins":"g:f2:1,g:f3:1" + }, + "g:noexp:1", + { + "id": "g:extra:1", + "feature-origins":"g:f3:1" + } + ], + "api-regions:JSON|false":[ + { + "name":"foo", + "exports":[ + "a.b.c", + "d.e.f" + ], + "feature-origins":[ + "g:f1:1","g:f2:1" + ] + },{ + "name":"global", + "exports":[ + "zzz.zzz" + ] + } + ] +} diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export.jar new file mode 100644 index 000000000..fa3c6ede7 Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export2.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export2.jar new file mode 100644 index 000000000..28de57d0f Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export2.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export3.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export3.jar new file mode 100644 index 000000000..81861eac5 Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/feature-export3.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/no-exports.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/no-exports.jar new file mode 100644 index 000000000..070fc16af Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/crossfeatdups/test-bundles/no-exports.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/json/apis.json b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/json/apis.json new file mode 100644 index 000000000..eaa81a519 --- /dev/null +++ b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/json/apis.json @@ -0,0 +1,15 @@ +[ + { + "name" : "global", + "exports" : [ + "org.apache.sling.global", + "org.apache.felix.global" + ] + }, + { + "name" : "internal", + "exports" : [ + "org.apache.sling.internal" + ] + } +] diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle1.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle1.jar new file mode 100644 index 000000000..394c883ad Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle1.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle2.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle2.jar new file mode 100644 index 000000000..8fb2589c5 Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle2.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle3.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle3.jar new file mode 100644 index 000000000..dcc08877e Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle3.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle4.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle4.jar new file mode 100644 index 000000000..27d4f74c2 Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-bundle4.jar differ diff --git a/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-framework.jar b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-framework.jar new file mode 100644 index 000000000..9ce022dcf Binary files /dev/null and b/Java-base/sling-org-apache-sling-feature-extension-apiregions/src/src/test/resources/test-framework.jar differ diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/buggy.java new file mode 100644 index 000000000..d6dd0086c --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/buggy.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.feature.extension.apiregions; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import javax.json.JsonArray; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.builder.HandlerContext; +import org.apache.sling.feature.builder.MergeHandler; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +/** + * Merge to api region extensions + */ +public class APIRegionMergeHandler implements MergeHandler { + + @Override + public boolean canMerge(Extension extension) { + return ApiRegions.EXTENSION_NAME.equals(extension.getName()); + } + + @Override +public void merge(org.apache.sling.feature.builder.HandlerContext context, org.apache.sling.feature.Feature target, org.apache.sling.feature.Feature source, org.apache.sling.feature.Extension targetEx, org.apache.sling.feature.Extension sourceEx) { + if (!sourceEx.getName().equals(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME)) { + return; + } + if ((targetEx != null) && (!targetEx.getName().equals(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME))) { + return; + } + try { + final org.apache.sling.feature.extension.apiregions.api.ApiRegions srcRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (sourceEx.getJSONStructure()))); + final org.apache.sling.feature.extension.apiregions.api.ApiRegions targetRegions; + { + targetRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (/* NPEX_NULL_EXP */ + targetEx.getJSONStructure()))); + } + for (final org.apache.sling.feature.extension.apiregions.api.ApiRegion targetRegion : targetRegions.listRegions()) { + final org.apache.sling.feature.extension.apiregions.api.ApiRegion sourceRegion = srcRegions.getRegionByName(targetRegion.getName()); + if (sourceRegion != null) { + for (final org.apache.sling.feature.extension.apiregions.api.ApiExport srcExp : sourceRegion.listExports()) { + if (targetRegion.getExportByName(srcExp.getName()) == null) { + targetRegion.add(srcExp); + } + } + java.util.LinkedHashSet targetOrigins = new java.util.LinkedHashSet<>(java.util.Arrays.asList(targetRegion.getFeatureOrigins())); + java.util.LinkedHashSet sourceOrigins = new java.util.LinkedHashSet<>(java.util.Arrays.asList(sourceRegion.getFeatureOrigins())); + if (sourceOrigins.isEmpty()) { + sourceOrigins.add(source.getId()); + } + targetOrigins.addAll(sourceOrigins); + targetRegion.setFeatureOrigins(targetOrigins.toArray(new org.apache.sling.feature.ArtifactId[0])); + } + } + // If there are any remaining regions in the src extension, process them now + for (final org.apache.sling.feature.extension.apiregions.api.ApiRegion r : srcRegions.listRegions()) { + if (targetRegions.getRegionByName(r.getName()) == null) { + java.util.LinkedHashSet origins = new java.util.LinkedHashSet<>(java.util.Arrays.asList(r.getFeatureOrigins())); + if (origins.isEmpty()) { + origins.add(source.getId()); + r.setFeatureOrigins(origins.toArray(new org.apache.sling.feature.ArtifactId[0])); + } + if (!targetRegions.add(r)) { + throw new java.lang.IllegalStateException("Duplicate region " + r.getName()); + } + } + } + targetEx.setJSONStructure(targetRegions.toJSONArray()); + } catch (final java.io.IOException e) { + throw new java.lang.RuntimeException(e); + } +} +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/metadata.json new file mode 100644 index 000000000..efa501df3 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java", + "line": 56, + "npe_method": "merge", + "deref_field": "targetEx", + "npe_class": "APIRegionMergeHandler", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "APIRegionMergeHandler_54" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/npe.json new file mode 100644 index 000000000..131862bda --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_54/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java", + "line": 56, + "npe_method": "merge", + "deref_field": "targetEx", + "npe_class": "APIRegionMergeHandler" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/buggy.java new file mode 100644 index 000000000..cb859d024 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/buggy.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.feature.extension.apiregions; + +import java.io.IOException; +import java.util.Arrays; +import java.util.LinkedHashSet; +import javax.json.JsonArray; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.builder.HandlerContext; +import org.apache.sling.feature.builder.MergeHandler; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +/** + * Merge to api region extensions + */ +public class APIRegionMergeHandler implements MergeHandler { + + @Override + public boolean canMerge(Extension extension) { + return ApiRegions.EXTENSION_NAME.equals(extension.getName()); + } + + @Override +public void merge(org.apache.sling.feature.builder.HandlerContext context, org.apache.sling.feature.Feature target, org.apache.sling.feature.Feature source, org.apache.sling.feature.Extension targetEx, org.apache.sling.feature.Extension sourceEx) { + if (!sourceEx.getName().equals(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME)) { + return; + } + if ((targetEx != null) && (!targetEx.getName().equals(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME))) { + return; + } + try { + final org.apache.sling.feature.extension.apiregions.api.ApiRegions srcRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (sourceEx.getJSONStructure()))); + final org.apache.sling.feature.extension.apiregions.api.ApiRegions targetRegions; + if (targetEx != null) { + targetRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (targetEx.getJSONStructure()))); + } else { + targetEx = new org.apache.sling.feature.Extension(sourceEx.getType(), sourceEx.getName(), sourceEx.getState()); + target.getExtensions().add(targetEx); + targetRegions = new org.apache.sling.feature.extension.apiregions.api.ApiRegions(); + } + for (final org.apache.sling.feature.extension.apiregions.api.ApiRegion targetRegion : targetRegions.listRegions()) { + final org.apache.sling.feature.extension.apiregions.api.ApiRegion sourceRegion = srcRegions.getRegionByName(targetRegion.getName()); + { + for (final org.apache.sling.feature.extension.apiregions.api.ApiExport srcExp : /* NPEX_NULL_EXP */ + sourceRegion.listExports()) { + if (targetRegion.getExportByName(srcExp.getName()) == null) { + targetRegion.add(srcExp); + } + } + java.util.LinkedHashSet targetOrigins = new java.util.LinkedHashSet<>(java.util.Arrays.asList(targetRegion.getFeatureOrigins())); + java.util.LinkedHashSet sourceOrigins = new java.util.LinkedHashSet<>(java.util.Arrays.asList(sourceRegion.getFeatureOrigins())); + if (sourceOrigins.isEmpty()) { + sourceOrigins.add(source.getId()); + } + targetOrigins.addAll(sourceOrigins); + targetRegion.setFeatureOrigins(targetOrigins.toArray(new org.apache.sling.feature.ArtifactId[0])); + } + } + // If there are any remaining regions in the src extension, process them now + for (final org.apache.sling.feature.extension.apiregions.api.ApiRegion r : srcRegions.listRegions()) { + if (targetRegions.getRegionByName(r.getName()) == null) { + java.util.LinkedHashSet origins = new java.util.LinkedHashSet<>(java.util.Arrays.asList(r.getFeatureOrigins())); + if (origins.isEmpty()) { + origins.add(source.getId()); + r.setFeatureOrigins(origins.toArray(new org.apache.sling.feature.ArtifactId[0])); + } + if (!targetRegions.add(r)) { + throw new java.lang.IllegalStateException("Duplicate region " + r.getName()); + } + } + } + targetEx.setJSONStructure(targetRegions.toJSONArray()); + } catch (final java.io.IOException e) { + throw new java.lang.RuntimeException(e); + } +} +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/metadata.json new file mode 100644 index 000000000..75e83ffd5 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java", + "line": 65, + "npe_method": "merge", + "deref_field": "sourceRegion", + "npe_class": "APIRegionMergeHandler", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "APIRegionMergeHandler_65" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/npe.json new file mode 100644 index 000000000..85e816fae --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-APIRegionMergeHandler_65/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/APIRegionMergeHandler.java", + "line": 65, + "npe_method": "merge", + "deref_field": "sourceRegion", + "npe_class": "APIRegionMergeHandler" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/buggy.java new file mode 100644 index 000000000..2cdf784dc --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/buggy.java @@ -0,0 +1,204 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.StringReader; +import java.net.URL; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.jar.Manifest; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonReader; +import javax.json.stream.JsonParsingException; + +import org.apache.sling.feature.Artifact; +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; +import org.apache.sling.feature.scanner.impl.FeatureDescriptorImpl; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public abstract class AbstractApiRegionsAnalyserTaskTest { + + protected abstract T newTask(); + + private AnalyserTask analyserTask; + + @Before + public void setUp() { + analyserTask = newTask(); + } + + @After + public void tearDown() { + analyserTask = null; + } + + @Test + public void testNoApiRegionsExtension() throws Exception { + List errors = execute((Extension) null); + assertTrue(errors.isEmpty()); + } + + @Test + public void testNullApiRegionsJSON() throws Exception { + List errors = execute((String) null); + assertTrue(errors.isEmpty()); + } + + @Test + public void testEmptyApiRegionsJSON() throws Exception { + List errors = execute(""); + assertTrue(errors.isEmpty()); + } + + @Test + public void testApiRegionsIsNotJSON() throws Exception { + List errors = execute("this is not a JSON string"); + + assertFalse(errors.isEmpty()); + assertTrue(errors.iterator().next().contains("does not represent a valid JSON 'api-regions'")); + } + +protected final java.util.List execute(java.lang.String apiRegionJSON) throws java.lang.Exception { + org.apache.sling.feature.Extension extension = org.mockito.Mockito.mock(org.apache.sling.feature.Extension.class); + org.mockito.Mockito.when(extension.getJSON()).thenReturn(apiRegionJSON); + { + javax.json.JsonReader reader = javax.json.Json.createReader(new java.io.StringReader(/* NPEX_NULL_EXP */ + apiRegionJSON)); + javax.json.JsonArray array = null; + try { + array = reader.readArray(); + } catch (final javax.json.stream.JsonParsingException ignore) { + } + org.mockito.Mockito.when(extension.getJSONStructure()).thenReturn(array); + } + return execute(extension); +} + + protected final List execute(Extension extension) throws Exception { + Extensions extensions = mock(Extensions.class); + when(extensions.getByName("api-regions")).thenReturn(extension); + + Feature feature = mock(Feature.class); + when(feature.getId()).thenReturn(new ArtifactId("org.apache.sling.testing", + "org.apache.sling.testing.apiregions", + "1.0.0", + null, + null)); + when(feature.getExtensions()).thenReturn(extensions); + + AnalyserTaskContext ctx = mock(AnalyserTaskContext.class); + when(ctx.getFeature()).thenReturn(feature); + @SuppressWarnings("unchecked") + Map cfg = Mockito.mock(Map.class); + when(ctx.getConfiguration()).thenReturn(cfg); + when(cfg.getOrDefault(anyString(), anyString())).thenAnswer(new Answer() { + + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + return (String) args[1]; + } + + }); + + PackageInfo packageInfo = new PackageInfo("org.osgi.util.function", "1.0", false, Collections.singleton("org.objectweb.asm")); + + BundleDescriptor bundleDescriptor = new TestBundleDescriptor(); + bundleDescriptor.getExportedPackages().add(packageInfo); + + FeatureDescriptor featureDescriptor = new FeatureDescriptorImpl(feature); + featureDescriptor.getBundleDescriptors().add(bundleDescriptor); + + when(ctx.getFeatureDescriptor()).thenReturn(featureDescriptor); + + List errors = new LinkedList<>(); + doAnswer(invocation -> { + String error = invocation.getArgument(0); + errors.add(error); + return null; + }).when(ctx).reportError(anyString()); + + analyserTask.execute(ctx); + + return errors; + } + + private static final class TestBundleDescriptor extends BundleDescriptor { + TestBundleDescriptor() { + super("org.osgi:org.osgi.util.function:1.0.0"); + } + + @Override + public URL getArtifactFile() { + // do nothing + return null; + } + + @Override + public Artifact getArtifact() { + return new Artifact(ArtifactId.fromMvnId("org.osgi:org.osgi.util.function:1.0.0")); + } + + @Override + public Manifest getManifest() { + // do nothing + return null; + } + + @Override + public String getBundleVersion() { + // do nothing + return null; + } + + @Override + public String getBundleSymbolicName() { + // do nothing + return null; + } + + @Override + public int getBundleStartLevel() { + // do nothing + return 0; + } + + } + +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/metadata.json new file mode 100644 index 000000000..c16c68773 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java", + "line": 102, + "npe_method": "execute", + "deref_field": "apiRegionJSON", + "npe_class": "AbstractApiRegionsAnalyserTaskTest", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "AbstractApiRegionsAnalyserTaskTest_100" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/npe.json new file mode 100644 index 000000000..342c30915 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTaskTest_100/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/test/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTaskTest.java", + "line": 102, + "npe_method": "execute", + "deref_field": "apiRegionJSON", + "npe_class": "AbstractApiRegionsAnalyserTaskTest" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/buggy.java new file mode 100644 index 000000000..1badb63d5 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/buggy.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.feature.extension.apiregions.analyser; + +import java.io.IOException; +import javax.json.JsonArray; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +public abstract class AbstractApiRegionsAnalyserTask implements AnalyserTask { + + @Override +public final void execute(org.apache.sling.feature.analyser.task.AnalyserTaskContext ctx) throws java.lang.Exception { + org.apache.sling.feature.Feature feature = ctx.getFeature(); + // extract and check the api-regions + org.apache.sling.feature.Extensions extensions = feature.getExtensions(); + org.apache.sling.feature.Extension apiRegionsExtension = extensions.getByName(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME); + { + if ((/* NPEX_NULL_EXP */ + apiRegionsExtension.getJSON() == null) || apiRegionsExtension.getJSON().isEmpty()) { + // no need to be analyzed + return; + } + if (apiRegionsExtension.getJSONStructure() == null) { + ctx.reportError(("API Regions '" + apiRegionsExtension.getJSON()) + "' does not represent a valid JSON 'api-regions'"); + return; + } + // read the api-regions and create a Sieve data structure for checks + org.apache.sling.feature.extension.apiregions.api.ApiRegions apiRegions; + try { + apiRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (apiRegionsExtension.getJSONStructure()))); + } catch (java.io.IOException e) { + ctx.reportError((("API Regions '" + apiRegionsExtension.getJSON()) + "' does not represent a valid JSON 'api-regions': ") + e.getMessage()); + return; + } + execute(apiRegions, ctx); + } +} + + protected abstract void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception; + +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/metadata.json new file mode 100644 index 000000000..9858223ae --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTask.java", + "line": 39, + "npe_method": "execute", + "deref_field": "apiRegionsExtension", + "npe_class": "AbstractApiRegionsAnalyserTask", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "AbstractApiRegionsAnalyserTask_39" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/npe.json new file mode 100644 index 000000000..416048cae --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-AbstractApiRegionsAnalyserTask_39/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/analyser/AbstractApiRegionsAnalyserTask.java", + "line": 39, + "npe_method": "execute", + "deref_field": "apiRegionsExtension", + "npe_class": "AbstractApiRegionsAnalyserTask" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/buggy.java new file mode 100644 index 000000000..d24c6bddf --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/buggy.java @@ -0,0 +1,163 @@ +/* + * 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.feature.extension.apiregions.api; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.feature.ArtifactId; + +/** + * Describes an exported package. + */ +public class ApiExport implements Comparable { + + private final String name; + + private volatile String toggle; + + private volatile ArtifactId previous; + + private final Map properties = new HashMap<>(); + + /** + * Create a new export + * + * @param name Package name for the export + */ + public ApiExport(final String name) { + this.name = name; + } + + /** + * Get the package name + * + * @return The package name + */ + public String getName() { + return name; + } + + /** + * Get the optional toggle information + * + * @return The toggle info or {@code null} + */ + public String getToggle() { + return toggle; + } + + /** + * Set the toggle info. + * + * @param toggle The toggle info + */ + public void setToggle(String toggle) { + this.toggle = toggle; + } + + /** + * Get the previous version of this api + * + * @return The previous version or {@code null} + */ + public ArtifactId getPrevious() { + return previous; + } + + /** + * Set the previous version + * + * @param previous Previus version + */ + public void setPrevious(ArtifactId previous) { + this.previous = previous; + } + + /** + * Get additional properties + * + * @return Modifiable map of properties + */ + public Map getProperties() { + return this.properties; + } + + @Override + public int compareTo(final ApiExport o) { + return this.name.compareTo(o.name); + } + + @Override + public String toString() { + return "ApiExport [name=" + name + ", toggle=" + toggle + ", previous=" + previous + ", properties=" + + properties + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((previous == null) ? 0 : previous.hashCode()); + result = prime * result + ((properties == null) ? 0 : properties.hashCode()); + result = prime * result + ((toggle == null) ? 0 : toggle.hashCode()); + return result; + } + + @Override +public boolean equals(java.lang.Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + org.apache.sling.feature.extension.apiregions.api.ApiExport other = ((org.apache.sling.feature.extension.apiregions.api.ApiExport) (obj)); + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + { + if (!/* NPEX_NULL_EXP */ + previous.equals(other.previous)) { + return false; + } + } + if (properties == null) { + if (other.properties != null) { + return false; + } + } else if (!properties.equals(other.properties)) { + return false; + } + if (toggle == null) { + if (other.toggle != null) { + return false; + } + } else if (!toggle.equals(other.toggle)) { + return false; + } + return true; +} +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/metadata.json new file mode 100644 index 000000000..1a4a26596 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-ApiExport_136", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java", + "line": 143, + "npe_method": "equals", + "deref_field": "previous", + "npe_class": "ApiExport", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "ApiExport_136" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/npe.json new file mode 100644 index 000000000..48b257d2d --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_136/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java", + "line": 143, + "npe_method": "equals", + "deref_field": "previous", + "npe_class": "ApiExport" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/buggy.java new file mode 100644 index 000000000..17f259e1b --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/buggy.java @@ -0,0 +1,163 @@ +/* + * 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.feature.extension.apiregions.api; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.feature.ArtifactId; + +/** + * Describes an exported package. + */ +public class ApiExport implements Comparable { + + private final String name; + + private volatile String toggle; + + private volatile ArtifactId previous; + + private final Map properties = new HashMap<>(); + + /** + * Create a new export + * + * @param name Package name for the export + */ + public ApiExport(final String name) { + this.name = name; + } + + /** + * Get the package name + * + * @return The package name + */ + public String getName() { + return name; + } + + /** + * Get the optional toggle information + * + * @return The toggle info or {@code null} + */ + public String getToggle() { + return toggle; + } + + /** + * Set the toggle info. + * + * @param toggle The toggle info + */ + public void setToggle(String toggle) { + this.toggle = toggle; + } + + /** + * Get the previous version of this api + * + * @return The previous version or {@code null} + */ + public ArtifactId getPrevious() { + return previous; + } + + /** + * Set the previous version + * + * @param previous Previus version + */ + public void setPrevious(ArtifactId previous) { + this.previous = previous; + } + + /** + * Get additional properties + * + * @return Modifiable map of properties + */ + public Map getProperties() { + return this.properties; + } + + @Override + public int compareTo(final ApiExport o) { + return this.name.compareTo(o.name); + } + + @Override + public String toString() { + return "ApiExport [name=" + name + ", toggle=" + toggle + ", previous=" + previous + ", properties=" + + properties + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((previous == null) ? 0 : previous.hashCode()); + result = prime * result + ((properties == null) ? 0 : properties.hashCode()); + result = prime * result + ((toggle == null) ? 0 : toggle.hashCode()); + return result; + } + + @Override +public boolean equals(java.lang.Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + org.apache.sling.feature.extension.apiregions.api.ApiExport other = ((org.apache.sling.feature.extension.apiregions.api.ApiExport) (obj)); + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (previous == null) { + if (other.previous != null) { + return false; + } + } else if (!previous.equals(other.previous)) { + return false; + } + if (properties == null) { + if (other.properties != null) { + return false; + } + } else if (!properties.equals(other.properties)) { + return false; + } + { + if (!/* NPEX_NULL_EXP */ + toggle.equals(other.toggle)) { + return false; + } + } + return true; +} +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/metadata.json new file mode 100644 index 000000000..65760ce1c --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-ApiExport_146", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java", + "line": 157, + "npe_method": "equals", + "deref_field": "toggle", + "npe_class": "ApiExport", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "ApiExport_146" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/npe.json new file mode 100644 index 000000000..3f8cba91d --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiExport_146/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiExport.java", + "line": 157, + "npe_method": "equals", + "deref_field": "toggle", + "npe_class": "ApiExport" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/buggy.java new file mode 100644 index 000000000..039009ca0 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/buggy.java @@ -0,0 +1,234 @@ +/* + * 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.feature.extension.apiregions.api; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.sling.feature.ArtifactId; + +/** + * Describes an api region + */ +public class ApiRegion { + + /** Name of the global region. */ + public static final String GLOBAL = "global"; + + private final List exports = new ArrayList<>(); + + private final List origins = new ArrayList<>(); + + private final Map properties = new HashMap<>(); + + private final String name; + + private volatile ApiRegion parent; + + /** + * Create a new named region + * + * @param name The name + */ + public ApiRegion(final String name) { + this.name = name; + } + + /** + * Get the name of the region + * + * @return The region name + */ + public String getName() { + return name; + } + + public ArtifactId[] getFeatureOrigins() { + return origins.toArray(new ArtifactId[0]); + } + + public void setFeatureOrigins(ArtifactId... featureOrigins) { + origins.clear(); + if (featureOrigins != null) { + origins.addAll(Stream.of(featureOrigins).filter(Objects::nonNull).distinct().collect(Collectors.toList())); + } + } + + /** + * Add the export. The export is only added if there isn't already a export with + * the same name + * + * @param export The export to add + * @return {@code true} if the export could be added, {@code false} otherwise + */ + public boolean add(final ApiExport export) { + boolean found = false; + for (final ApiExport c : this.exports) { + if (c.getName().equals(export.getName())) { + found = true; + break; + } + } + if (!found) { + this.exports.add(export); + } + return !found; + } + + /** + * Remove the export + * + * @param export export to remove + * @return {@code true} if the export got removed. + */ + public boolean remove(final ApiExport export) { + return this.exports.remove(export); + } + + /** + * Check if the region has exports + * + * @return {@code true} if it has any export + */ + public boolean isEmpty() { + return this.exports.isEmpty(); + } + + /** + * Unmodifiable collection of exports for this region + * + * @return The collection of exports + */ + public Collection listExports() { + return Collections.unmodifiableCollection(this.exports); + } + + /** + * Unmodifiable collection of exports for this region and all parents. + * + * @return The collection of exports + */ +/** + * Unmodifiable collection of exports for this region and all parents. + * + * @return The collection of exports + */ +public java.util.Collection listAllExports() { + final java.util.List list = new java.util.ArrayList<>(); + { + list.addAll(/* NPEX_NULL_EXP */ + parent.listAllExports()); + } + list.addAll(this.exports); + return java.util.Collections.unmodifiableCollection(list); +} + + /** + * Get an export by name + * + * @param name package name + * @return The export or {@code null} + */ + public ApiExport getExportByName(final String name) { + for (final ApiExport e : this.exports) { + if (e.getName().equals(name)) { + return e; + } + } + return null; + } + + /** + * Get an export by name + * + * @param name package name + * @return The export or {@code null} + */ + public ApiExport getAllExportByName(final String name) { + for (final ApiExport e : listAllExports()) { + if (e.getName().equals(name)) { + return e; + } + } + return null; + } + + /** + * Get additional properties + * + * @return Modifiable map of properties + */ + public Map getProperties() { + return this.properties; + } + + /** + * Get the parent region + * + * @return The parent region or {@code null} + */ + public ApiRegion getParent() { + return this.parent; + } + + void setParent(final ApiRegion region) { + this.parent = region; + } + + + @Override + public String toString() { + return "ApiRegion [exports=" + exports + ", properties=" + properties + ", name=" + name + ", feature-origins=" + origins + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((exports == null) ? 0 : exports.hashCode()); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + Arrays.hashCode(getFeatureOrigins()); + result = prime * result + ((properties == null) ? 0 : properties.hashCode()); + return result; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + ApiRegion region = (ApiRegion) o; + return exports.equals(region.exports) && + origins.equals(region.origins) && + properties.equals(region.properties) && + Objects.equals(name, region.name); + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/metadata.json new file mode 100644 index 000000000..633aae454 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java", + "line": 142, + "npe_method": "listAllExports", + "deref_field": "parent", + "npe_class": "ApiRegion", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "ApiRegion_135" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/npe.json new file mode 100644 index 000000000..dd45f6abd --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-ApiRegion_135/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/api/ApiRegion.java", + "line": 142, + "npe_method": "listAllExports", + "deref_field": "parent", + "npe_class": "ApiRegion" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/buggy.java new file mode 100644 index 000000000..7a9db9b27 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/buggy.java @@ -0,0 +1,340 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.IOException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.json.JsonArray; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; +import org.osgi.framework.Version; + +public class CheckApiRegionsBundleExportsImports implements AnalyserTask { + + private static final String IGNORE_API_REGIONS_CONFIG_KEY = "ignoreAPIRegions"; + private static final String GLOBAL_REGION = "global"; + private static final String NO_REGION = " __NO_REGION__ "; + private static final String OWN_FEATURE = " __OWN_FEATURE__ "; + + @Override + public String getName() { + return "Bundle Import/Export Check"; + } + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-exportsimports"; + } + + public static final class Report { + + public List exportWithoutVersion = new ArrayList<>(); + + public List exportMatchingSeveral = new ArrayList<>(); + + public List importWithoutVersion = new ArrayList<>(); + + public List missingExports = new ArrayList<>(); + + public List missingExportsWithVersion = new ArrayList<>(); + + public List missingExportsForOptional = new ArrayList<>(); + + public Map, Set>> regionInfo = new HashMap<>(); + } + + private Report getReport(final Map reports, final BundleDescriptor info) { + Report report = reports.get(info); + if ( report == null ) { + report = new Report(); + reports.put(info, report); + } + return report; + } + + private void checkForVersionOnExportedPackages(final AnalyserTaskContext ctx, final Map reports) { + for(final BundleDescriptor info : ctx.getFeatureDescriptor().getBundleDescriptors()) { + if ( info.getExportedPackages() != null ) { + for(final PackageInfo i : info.getExportedPackages()) { + if ( i.getPackageVersion().compareTo(Version.emptyVersion) == 0 ) { + getReport(reports, info).exportWithoutVersion.add(i); + } + } + } + } + } + + private void checkForVersionOnImportingPackages(final AnalyserTaskContext ctx, final Map reports) { + for(final BundleDescriptor info : ctx.getFeatureDescriptor().getBundleDescriptors()) { + if ( info.getImportedPackages() != null ) { + for(final PackageInfo i : info.getImportedPackages()) { + if ( i.getVersion() == null ) { + // don't report for javax, org.xml. and org.w3c. packages (TODO) + if ( !i.getName().startsWith("javax.") + && !i.getName().startsWith("org.w3c.") && !i.getName().startsWith("org.xml.")) { + getReport(reports, info).importWithoutVersion.add(i); + } + } + } + } + } + } + + @Override +public void execute(final org.apache.sling.feature.analyser.task.AnalyserTaskContext ctx) throws java.lang.Exception { + boolean ignoreAPIRegions = ctx.getConfiguration().getOrDefault(org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsBundleExportsImports.IGNORE_API_REGIONS_CONFIG_KEY, "false").equalsIgnoreCase("true"); + // basic checks + final java.util.Map reports = new java.util.HashMap<>(); + checkForVersionOnExportedPackages(ctx, reports); + checkForVersionOnImportingPackages(ctx, reports); + final java.util.SortedMap> bundlesMap = new java.util.TreeMap<>(); + for (final org.apache.sling.feature.scanner.BundleDescriptor bi : ctx.getFeatureDescriptor().getBundleDescriptors()) { + java.util.List list = bundlesMap.get(bi.getArtifact().getStartOrder()); + if (list == null) { + list = new java.util.ArrayList<>(); + bundlesMap.put(bi.getArtifact().getStartOrder(), list); + } + list.add(bi); + } + // add all system packages + final java.util.List exportingBundles = new java.util.ArrayList<>(); + { + exportingBundles.add(/* NPEX_NULL_EXP */ + ctx.getFrameworkDescriptor()); + } + org.apache.sling.feature.extension.apiregions.api.ApiRegions apiRegions = new org.apache.sling.feature.extension.apiregions.api.ApiRegions();// Empty API Regions; + + org.apache.sling.feature.Feature feature = ctx.getFeature(); + // extract and check the api-regions + org.apache.sling.feature.Extensions extensions = feature.getExtensions(); + org.apache.sling.feature.Extension apiRegionsExtension = extensions.getByName(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME); + if ((apiRegionsExtension != null) && (apiRegionsExtension.getJSONStructure() != null)) { + try { + apiRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (apiRegionsExtension.getJSONStructure()))); + } catch (java.io.IOException e) { + ctx.reportError((("API Regions '" + apiRegionsExtension.getJSON()) + "' does not represent a valid JSON 'api-regions': ") + e.getMessage()); + return; + } + } + for (final java.util.Map.Entry> entry : bundlesMap.entrySet()) { + // first add all exporting bundles + for (final org.apache.sling.feature.scanner.BundleDescriptor info : entry.getValue()) { + if (info.getExportedPackages() != null) { + exportingBundles.add(info); + } + } + // check importing bundles + for (final org.apache.sling.feature.scanner.BundleDescriptor info : entry.getValue()) { + if (info.getImportedPackages() != null) { + for (final org.apache.sling.feature.scanner.PackageInfo pck : info.getImportedPackages()) { + final java.util.Map> candidates = getCandidates(exportingBundles, pck, info, apiRegions, ignoreAPIRegions); + if (candidates.isEmpty()) { + if (pck.isOptional()) { + getReport(reports, info).missingExportsForOptional.add(pck); + } else { + getReport(reports, info).missingExports.add(pck); + } + } else { + final java.util.List matchingCandidates = new java.util.ArrayList<>(); + java.util.Set exportingRegions = new java.util.HashSet<>(); + java.util.Set importingRegions = new java.util.HashSet<>(); + for (final java.util.Map.Entry> candidate : candidates.entrySet()) { + org.apache.sling.feature.scanner.BundleDescriptor bd = candidate.getKey(); + if (bd.isExportingPackage(pck)) { + java.util.Set exRegions = candidate.getValue(); + if (exRegions.contains(org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsBundleExportsImports.NO_REGION)) { + // If an export is defined outside of a region, it always matches + matchingCandidates.add(bd); + continue; + } + if (exRegions.contains(org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsBundleExportsImports.GLOBAL_REGION)) { + // Everyone can import from the global regin + matchingCandidates.add(bd); + continue; + } + if (exRegions.contains(org.apache.sling.feature.extension.apiregions.analyser.CheckApiRegionsBundleExportsImports.OWN_FEATURE)) { + // A feature can always import packages from bundles in itself + matchingCandidates.add(bd); + continue; + } + // Find out what regions the importing bundle is in + java.util.Set imRegions = getBundleRegions(info, apiRegions, ignoreAPIRegions); + // Record the exporting and importing regions for diagnostics + exportingRegions.addAll(exRegions); + java.util.Set regions = new java.util.HashSet<>(); + for (java.lang.String region : imRegions) { + for (org.apache.sling.feature.extension.apiregions.api.ApiRegion r = apiRegions.getRegionByName(region); r != null; r = r.getParent()) { + regions.add(r.getName()); + } + } + importingRegions.addAll(regions); + // Only keep the regions that also export the package + regions.retainAll(exRegions); + if (!regions.isEmpty()) { + // there is an overlapping region + matchingCandidates.add(bd); + } + } + } + if (matchingCandidates.isEmpty()) { + if (pck.isOptional()) { + getReport(reports, info).missingExportsForOptional.add(pck); + } else { + getReport(reports, info).missingExportsWithVersion.add(pck); + getReport(reports, info).regionInfo.put(pck, new java.util.AbstractMap.SimpleEntry<>(exportingRegions, importingRegions)); + } + } else if (matchingCandidates.size() > 1) { + getReport(reports, info).exportMatchingSeveral.add(pck); + } + } + } + } + } + } + boolean errorReported = false; + for (final java.util.Map.Entry entry : reports.entrySet()) { + final java.lang.String key = (("Bundle " + entry.getKey().getArtifact().getId().getArtifactId()) + ":") + entry.getKey().getArtifact().getId().getVersion(); + if (!entry.getValue().importWithoutVersion.isEmpty()) { + ctx.reportWarning(((key + " is importing package(s) ") + getPackageInfo(entry.getValue().importWithoutVersion, false)) + " without specifying a version range."); + } + if (!entry.getValue().exportWithoutVersion.isEmpty()) { + ctx.reportWarning(((key + " is exporting package(s) ") + getPackageInfo(entry.getValue().importWithoutVersion, false)) + " without a version."); + } + if (!entry.getValue().missingExports.isEmpty()) { + ctx.reportError(((((key + " is importing package(s) ") + getPackageInfo(entry.getValue().missingExports, false)) + " in start level ") + java.lang.String.valueOf(entry.getKey().getArtifact().getStartOrder())) + " but no bundle is exporting these for that start level."); + errorReported = true; + } + if (!entry.getValue().missingExportsWithVersion.isEmpty()) { + java.lang.StringBuilder message = new java.lang.StringBuilder(((((key + " is importing package(s) ") + getPackageInfo(entry.getValue().missingExportsWithVersion, true)) + " in start level ") + java.lang.String.valueOf(entry.getKey().getArtifact().getStartOrder())) + " but no visible bundle is exporting these for that start level in the required version range."); + for (java.util.Map.Entry, java.util.Set>> regionInfoEntry : entry.getValue().regionInfo.entrySet()) { + org.apache.sling.feature.scanner.PackageInfo pkg = regionInfoEntry.getKey(); + java.util.Map.Entry, java.util.Set> regions = regionInfoEntry.getValue(); + if (regions.getKey().size() > 0) { + message.append((((("\n" + pkg.getName()) + " is exported in regions ") + regions.getKey()) + " but it is imported in regions ") + regions.getValue()); + } + } + ctx.reportError(message.toString()); + errorReported = true; + } + } + if (errorReported && ctx.getFeature().isComplete()) { + ctx.reportError(ctx.getFeature().getId().toMvnId() + " is marked as 'complete' but has missing imports."); + } +} + + private Set getBundleRegions(BundleDescriptor info, ApiRegions regions, boolean ignoreAPIRegions) { + Set result = ignoreAPIRegions ? Collections.emptySet() : Stream.of(info.getArtifact().getFeatureOrigins()) + .map(regions::getRegionsByFeature).flatMap(Stream::of).map(ApiRegion::getName).collect(Collectors.toSet()); + + if (result.isEmpty()) { + result = new HashSet<>(); + result.add(NO_REGION); + } + return result; + } + + + private String getPackageInfo(final List pcks, final boolean includeVersion) { + if ( pcks.size() == 1 ) { + if (includeVersion) { + return pcks.get(0).toString(); + } else { + return pcks.get(0).getName(); + } + } + final StringBuilder sb = new StringBuilder(); + boolean first = true; + sb.append('['); + for(final PackageInfo info : pcks) { + if ( first ) { + first = false; + } else { + sb.append(", "); + } + if (includeVersion) { + sb.append(info.toString()); + } else { + sb.append(info.getName()); + } + } + sb.append(']'); + return sb.toString(); + } + + private Map> getCandidates( + final List exportingBundles, + final PackageInfo pck, + final BundleDescriptor requestingBundle, + final ApiRegions apiRegions, boolean ignoreAPIRegions) { + Set rf = ignoreAPIRegions ? Collections.emptySet() : Stream.of(requestingBundle.getArtifact().getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.toSet()); + + final Set requestingFeatures = rf; + + final Map> candidates = new HashMap<>(); + for(final BundleDescriptor info : exportingBundles) { + if ( info.isExportingPackage(pck.getName()) ) { + Set providingFeatures = ignoreAPIRegions ? Collections.emptySet() : Stream.of(info.getArtifact().getFeatureOrigins()).map(ArtifactId::toMvnId).collect(Collectors.toSet()); + + // Compute the intersection without modifying the sets + Set intersection = providingFeatures.stream().filter( + s -> requestingFeatures.contains(s)).collect(Collectors.toSet()); + if (!intersection.isEmpty()) { + // A requesting bundle can see all exported packages inside its own feature + candidates.put(info, Collections.singleton(OWN_FEATURE)); + continue; + } + + for (String region : getBundleRegions(info, apiRegions, ignoreAPIRegions)) { + if (!NO_REGION.equals(region) && + (apiRegions.getRegionByName(region) == null + || apiRegions.getRegionByName(region).getAllExportByName(pck.getName()) == null)) + continue; + + Set regions = candidates.get(info); + if (regions == null) { + regions = new HashSet<>(); + candidates.put(info, regions); + } + regions.add(region); + } + } + } + return candidates; + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/metadata.json new file mode 100644 index 000000000..871929309 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java", + "line": 139, + "npe_method": "execute", + "deref_field": "getFrameworkDescriptor", + "npe_class": "CheckApiRegionsBundleExportsImports", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "CheckApiRegionsBundleExportsImports_141" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/npe.json new file mode 100644 index 000000000..48a02eb6c --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsBundleExportsImports_141/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsBundleExportsImports.java", + "line": 139, + "npe_method": "execute", + "deref_field": "getFrameworkDescriptor", + "npe_class": "CheckApiRegionsBundleExportsImports" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/buggy.java new file mode 100644 index 000000000..18c049ec6 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/buggy.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.sling.feature.extension.apiregions.analyser; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiExport; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.apache.sling.feature.scanner.FeatureDescriptor; +import org.apache.sling.feature.scanner.PackageInfo; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class CheckApiRegionsCrossFeatureDups extends AbstractApiRegionsAnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-crossfeature-dups"; + } + + @Override + public String getName() { + return "Api Regions cross-feature duplicate export task"; + } + + @Override + protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception { + Set checkedRegions = splitListConfig(ctx.getConfiguration().get("regions")); + Set ignoredPackages = splitListConfig(ctx.getConfiguration().get("ignoredPackages")); + Set warningPackages = splitListConfig(ctx.getConfiguration().get("warningPackages")); + + Map> regionExports = new HashMap<>(); + + List apiRegionsFeatures = new ArrayList<>(); + for (ApiRegion r : apiRegions.listRegions()) { + apiRegionsFeatures.addAll(Arrays.asList(r.getFeatureOrigins())); + if (checkedRegions.isEmpty() || checkedRegions.contains(r.getName())) { + Set exports = regionExports.get(r.getName()); + if (exports == null) { + exports = new HashSet<>(); + regionExports.put(r.getName(), exports); + } + exports.addAll(r.listExports().stream().map(ApiExport::getName).collect(Collectors.toSet())); + } + } + + FeatureDescriptor f = ctx.getFeatureDescriptor(); + for (BundleDescriptor bd : f.getBundleDescriptors()) { + List borgs = new ArrayList<>(Arrays.asList(bd.getArtifact().getFeatureOrigins())); + borgs.removeAll(apiRegionsFeatures); + + if (!borgs.isEmpty()) { + // This bundle was contributed by a feature that did not opt-in to the API Regions + Set reportedPackages = new HashSet<>(); + for (PackageInfo pi : bd.getExportedPackages()) { + String pkgName = pi.getName(); + for (Map.Entry> entry : regionExports.entrySet()) { + if (entry.getValue().contains(pkgName) && !reportedPackages.contains(pkgName)) { + if (matchesSet(pkgName, ignoredPackages)) { + continue; + } + + reportedPackages.add(pi.getName()); + + String msg = "Package overlap found between region " + entry.getKey() + + " and bundle " + bd.getBundleSymbolicName() + " " + bd.getBundleVersion() + + " which comes from a feature without API Regions: " + borgs + + ". Both export package: " + pi.getName(); + if (matchesSet(pkgName, warningPackages)) { + ctx.reportWarning(msg); + } else { + ctx.reportError(msg); + } + } + } + } + } + } + } + + private boolean matchesSet(String pkg, Set set) { + for (String e : set) { + if (e.endsWith("*")) { + if (pkg.startsWith(e.substring(0, e.length() - 1)) ) { + return true; + } + } else { + if (pkg.equals(e)) { + return true; + } + } + } + return false; + } + +private java.util.Set splitListConfig(java.lang.String value) { + { + return java.util.Arrays.asList(/* NPEX_NULL_EXP */ + value.split(",")).stream().map(java.lang.String::trim).collect(java.util.stream.Collectors.toSet()); + } +} +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/metadata.json b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/metadata.json new file mode 100644 index 000000000..426fb1a90 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121", + "buggyPath": ".", + "referencePath": null, + "buildCommand": "mvn package -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false", + "testCommand": "mvn clean test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100", + "categories": [ + "safety", + "npe" + ], + "npe": { + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDups.java", + "line": 123, + "npe_method": "splitListConfig", + "deref_field": "value", + "npe_class": "CheckApiRegionsCrossFeatureDups", + "repo": "sling-org-apache-sling-feature-extension-apiregions", + "bug_id": "CheckApiRegionsCrossFeatureDups_121" + } +} diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/npe.json b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/npe.json new file mode 100644 index 000000000..db582f121 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsCrossFeatureDups_121/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckApiRegionsCrossFeatureDups.java", + "line": 123, + "npe_method": "splitListConfig", + "deref_field": "value", + "npe_class": "CheckApiRegionsCrossFeatureDups" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_48/Dockerfile b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_48/Dockerfile new file mode 100644 index 000000000..553e8419e --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_48/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-feature-extension-apiregions + +ENV TZ=Asia/Seoul + +COPY ./metadata.json . +COPY ./npe.json . +COPY ./buggy.java /tmp/buggy.java +RUN export BUGGY_PATH=$(cat metadata.json | jq -r ".npe.filepath") \ + && export BUGGY_LINE=$(cat metadata.json | jq -r ".npe.line") \ + && export BUGGY_MTHD=$(cat metadata.json | jq -r ".npe.npe_method") \ + && mv /tmp/buggy.java $BUGGY_PATH \ + && echo "[{\"filepath\": \"$BUGGY_PATH\", \"line\": $BUGGY_LINE, \"method_name\": \"$BUGGY_MTHD\"}]" | jq . > traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_48/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_48/buggy.java new file mode 100644 index 000000000..909161dcb --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_48/buggy.java @@ -0,0 +1,117 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.json.JsonArray; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +public class CheckApiRegionsOrder implements AnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-check-order"; + } + + @Override + public String getName() { + return "Api Regions check order analyser task"; + } + + @Override +public final void execute(org.apache.sling.feature.analyser.task.AnalyserTaskContext ctx) throws java.lang.Exception { + java.lang.String order = ctx.getConfiguration().get("order"); + org.apache.sling.feature.Feature feature = ctx.getFeature(); + { + if (order == null) { + reportError(ctx, (("This analyser task must be configured: " + getId()) + " for feature ") + /* NPEX_NULL_EXP */ + feature.getId()); + reportError(ctx, "Must specify configuration key 'order'."); + return; + } + java.lang.String[] sl = order.split("[,]"); + java.util.List prescribedOrder = new java.util.ArrayList<>(); + for (java.lang.String s : sl) { + s = s.trim(); + if (s.length() > 0) { + prescribedOrder.add(s); + } + } + if (prescribedOrder.size() == 0) { + reportError(ctx, "No regions declared in the 'order' configuration"); + return; + } + // extract and check the api-regions + org.apache.sling.feature.Extensions extensions = feature.getExtensions(); + org.apache.sling.feature.Extension apiRegionsExtension = extensions.getByName(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME); + if (apiRegionsExtension == null) { + // no need to be analyzed + return; + } + java.lang.String jsonRepresentation = apiRegionsExtension.getJSON(); + if ((jsonRepresentation == null) || jsonRepresentation.isEmpty()) { + // no need to be analyzed + return; + } + try { + int regionIdx = 0; + org.apache.sling.feature.extension.apiregions.api.ApiRegions apiRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (apiRegionsExtension.getJSONStructure()))); + for (final org.apache.sling.feature.extension.apiregions.api.ApiRegion region : apiRegions.listRegions()) { + java.lang.String name = region.getName(); + if (!prescribedOrder.contains(name)) { + reportError(ctx, "Region found with undeclared name: " + name); + return; + } + int prevIdx = regionIdx; + regionIdx = validateRegion(regionIdx, prescribedOrder, name); + if (regionIdx < 0) { + reportError(ctx, (((("Region '" + name) + "' appears in the wrong order. It appears after '") + prescribedOrder.get(prevIdx)) + "'. Order of regions should be ") + prescribedOrder); + return; + } + } + } catch (final java.io.IOException e) { + ctx.reportError("Invalid api regions"); + } + } +} + + private int validateRegion(int regionIdx, List order, String name) { + for (int i=regionIdx; i traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_53/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_53/buggy.java new file mode 100644 index 000000000..6be27f349 --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_53/buggy.java @@ -0,0 +1,116 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.json.JsonArray; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +public class CheckApiRegionsOrder implements AnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-check-order"; + } + + @Override + public String getName() { + return "Api Regions check order analyser task"; + } + + @Override +public final void execute(org.apache.sling.feature.analyser.task.AnalyserTaskContext ctx) throws java.lang.Exception { + java.lang.String order = ctx.getConfiguration().get("order"); + org.apache.sling.feature.Feature feature = ctx.getFeature(); + if (feature == null) { + reportError(ctx, "No feature found. Illegal Analyser State."); + return; + } + { + java.lang.String[] sl = /* NPEX_NULL_EXP */ + order.split("[,]"); + java.util.List prescribedOrder = new java.util.ArrayList<>(); + for (java.lang.String s : sl) { + s = s.trim(); + if (s.length() > 0) { + prescribedOrder.add(s); + } + } + if (prescribedOrder.size() == 0) { + reportError(ctx, "No regions declared in the 'order' configuration"); + return; + } + // extract and check the api-regions + org.apache.sling.feature.Extensions extensions = feature.getExtensions(); + org.apache.sling.feature.Extension apiRegionsExtension = extensions.getByName(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME); + if (apiRegionsExtension == null) { + // no need to be analyzed + return; + } + java.lang.String jsonRepresentation = apiRegionsExtension.getJSON(); + if ((jsonRepresentation == null) || jsonRepresentation.isEmpty()) { + // no need to be analyzed + return; + } + try { + int regionIdx = 0; + org.apache.sling.feature.extension.apiregions.api.ApiRegions apiRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (apiRegionsExtension.getJSONStructure()))); + for (final org.apache.sling.feature.extension.apiregions.api.ApiRegion region : apiRegions.listRegions()) { + java.lang.String name = region.getName(); + if (!prescribedOrder.contains(name)) { + reportError(ctx, "Region found with undeclared name: " + name); + return; + } + int prevIdx = regionIdx; + regionIdx = validateRegion(regionIdx, prescribedOrder, name); + if (regionIdx < 0) { + reportError(ctx, (((("Region '" + name) + "' appears in the wrong order. It appears after '") + prescribedOrder.get(prevIdx)) + "'. Order of regions should be ") + prescribedOrder); + return; + } + } + } catch (final java.io.IOException e) { + ctx.reportError("Invalid api regions"); + } + } +} + + private int validateRegion(int regionIdx, List order, String name) { + for (int i=regionIdx; i traces.json + +RUN git init . && git add -A + +RUN $(cat metadata.json | jq -r ".buildCommand") + +RUN $(cat metadata.json | jq -r ".testCommand"); if [ $? -eq 0 ]; then exit 1; fi diff --git a/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_74/buggy.java b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_74/buggy.java new file mode 100644 index 000000000..43ec636bf --- /dev/null +++ b/Java/sling-org-apache-sling-feature-extension-apiregions-CheckApiRegionsOrder_74/buggy.java @@ -0,0 +1,117 @@ +/* + * 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.feature.extension.apiregions.analyser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.json.JsonArray; + +import org.apache.sling.feature.Extension; +import org.apache.sling.feature.Extensions; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.extension.apiregions.api.ApiRegion; +import org.apache.sling.feature.extension.apiregions.api.ApiRegions; + +public class CheckApiRegionsOrder implements AnalyserTask { + + @Override + public String getId() { + return ApiRegions.EXTENSION_NAME + "-check-order"; + } + + @Override + public String getName() { + return "Api Regions check order analyser task"; + } + + @Override +public final void execute(org.apache.sling.feature.analyser.task.AnalyserTaskContext ctx) throws java.lang.Exception { + java.lang.String order = ctx.getConfiguration().get("order"); + org.apache.sling.feature.Feature feature = ctx.getFeature(); + if (feature == null) { + reportError(ctx, "No feature found. Illegal Analyser State."); + return; + } + if (order == null) { + reportError(ctx, (("This analyser task must be configured: " + getId()) + " for feature ") + feature.getId()); + reportError(ctx, "Must specify configuration key 'order'."); + return; + } + java.lang.String[] sl = order.split("[,]"); + java.util.List prescribedOrder = new java.util.ArrayList<>(); + for (java.lang.String s : sl) { + s = s.trim(); + if (s.length() > 0) { + prescribedOrder.add(s); + } + } + if (prescribedOrder.size() == 0) { + reportError(ctx, "No regions declared in the 'order' configuration"); + return; + } + // extract and check the api-regions + org.apache.sling.feature.Extensions extensions = feature.getExtensions(); + org.apache.sling.feature.Extension apiRegionsExtension = extensions.getByName(org.apache.sling.feature.extension.apiregions.api.ApiRegions.EXTENSION_NAME); + { + java.lang.String jsonRepresentation = /* NPEX_NULL_EXP */ + apiRegionsExtension.getJSON(); + if ((jsonRepresentation == null) || jsonRepresentation.isEmpty()) { + // no need to be analyzed + return; + } + try { + int regionIdx = 0; + org.apache.sling.feature.extension.apiregions.api.ApiRegions apiRegions = org.apache.sling.feature.extension.apiregions.api.ApiRegions.parse(((javax.json.JsonArray) (apiRegionsExtension.getJSONStructure()))); + for (final org.apache.sling.feature.extension.apiregions.api.ApiRegion region : apiRegions.listRegions()) { + java.lang.String name = region.getName(); + if (!prescribedOrder.contains(name)) { + reportError(ctx, "Region found with undeclared name: " + name); + return; + } + int prevIdx = regionIdx; + regionIdx = validateRegion(regionIdx, prescribedOrder, name); + if (regionIdx < 0) { + reportError(ctx, (((("Region '" + name) + "' appears in the wrong order. It appears after '") + prescribedOrder.get(prevIdx)) + "'. Order of regions should be ") + prescribedOrder); + return; + } + } + } catch (final java.io.IOException e) { + ctx.reportError("Invalid api regions"); + } + } +} + + private int validateRegion(int regionIdx, List order, String name) { + for (int i=regionIdx; i