diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/Dockerfile b/Java-base/sling-org-apache-sling-caconfig-impl/Dockerfile new file mode 100644 index 000000000..e208c4890 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/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-caconfig-impl/src/CODE_OF_CONDUCT.md b/Java-base/sling-org-apache-sling-caconfig-impl/src/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0fa18e593 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/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-caconfig-impl/src/CONTRIBUTING.md b/Java-base/sling-org-apache-sling-caconfig-impl/src/CONTRIBUTING.md new file mode 100644 index 000000000..ac82a1abe --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/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-caconfig-impl/src/Jenkinsfile b/Java-base/sling-org-apache-sling-caconfig-impl/src/Jenkinsfile new file mode 100644 index 000000000..f5825190c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/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-caconfig-impl/src/LICENSE b/Java-base/sling-org-apache-sling-caconfig-impl/src/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/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-caconfig-impl/src/README.md b/Java-base/sling-org-apache-sling-caconfig-impl/src/README.md new file mode 100644 index 000000000..c23a6762c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/README.md @@ -0,0 +1,9 @@ +[](https://sling.apache.org) + + [![Build Status](https://builds.apache.org/buildStatus/icon?job=Sling/sling-org-apache-sling-caconfig-impl/master)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-caconfig-impl/job/master) [![Test Status](https://img.shields.io/jenkins/t/https/builds.apache.org/job/Sling/job/sling-org-apache-sling-caconfig-impl/job/master.svg)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-caconfig-impl/job/master/test_results_analyzer/) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.sling/org.apache.sling.caconfig.impl/badge.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.caconfig.impl%22) [![JavaDocs](https://www.javadoc.io/badge/org.apache.sling/org.apache.sling.caconfig.impl.svg)](https://www.javadoc.io/doc/org.apache.sling/org.apache.sling.caconfig.impl) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![caconfig](https://sling.apache.org/badges/group-caconfig.svg)](https://github.com/apache/sling-aggregator/blob/master/docs/groups/caconfig.md) + +# Apache Sling Context-Aware Configuration Implementation + +This module is part of the [Apache Sling](https://sling.apache.org) project. + +Documentation: [Apache Sling Context-Aware Configuration](https://sling.apache.org/documentation/bundles/context-aware-configuration/context-aware-configuration.html) diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/pom.xml b/Java-base/sling-org-apache-sling-caconfig-impl/src/pom.xml new file mode 100644 index 000000000..e1c869156 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/pom.xml @@ -0,0 +1,256 @@ + + + + 4.0.0 + + + org.apache.sling + sling-bundle-parent + 36 + + + + org.apache.sling.caconfig.impl + jar + 1.5.1-SNAPSHOT + Apache Sling Context-Aware Configuration Implementation + Apache Sling Context-Aware Configuration Implementation + + + scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-caconfig-impl.git + scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-caconfig-impl.git + https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-caconfig-impl.git + HEAD + + + + + + biz.aQute.bnd + bnd-maven-plugin + + + bnd-process + + bnd-process + + + + Import-Package: \ + \ + org.apache.felix.webconsole;resolution:=optional,\ + \ + org.apache.sling.xss;version="[1.0.0,3)",\ + \ + !org.apache.sling.commons.osgi,\ + * + + + + + + + biz.aQute.bnd + bnd-baseline-maven-plugin + + + org.apache.maven.plugins + maven-shade-plugin + 3.0.0 + + + package + + shade + + + true + true + + + org.apache.sling:org.apache.sling.commons.osgi + + + + + org.apache.sling.commons.osgi + caconfigimpl.org.apache.sling.commons.osgi + + + + + org.apache.sling:org.apache.sling.commons.osgi + + org/apache/sling/commons/osgi/** + + + + + + + + + org.apache.rat + apache-rat-plugin + + + dependency-reduced-pom.xml + + + + + + + + + org.osgi + org.osgi.annotation.versioning + provided + + + + org.apache.sling + org.apache.sling.commons.osgi + 2.4.0 + compile + + + org.apache.sling + org.apache.sling.caconfig.api + 1.2.0 + compile + + + org.apache.sling + org.apache.sling.caconfig.spi + 1.3.2 + compile + + + org.jetbrains + annotations + provided + + + org.slf4j + slf4j-api + provided + + + org.osgi + osgi.core + provided + + + org.apache.sling + org.apache.sling.api + 2.9.0 + provided + + + org.apache.sling + org.apache.sling.scripting.api + 2.1.6 + provided + + + org.apache.commons + commons-lang3 + 3.3.2 + compile + + + org.apache.commons + commons-collections4 + 4.1 + compile + + + org.apache.geronimo.specs + geronimo-json_1.0_spec + 1.0-alpha-1 + provided + + + + + org.apache.felix + org.apache.felix.webconsole + 4.2.0 + provided + + + org.apache.felix + org.apache.felix.inventory + 1.0.4 + provided + + + org.apache.sling + org.apache.sling.xss + 1.0.0 + provided + + + + + org.apache.sling + org.apache.sling.testing.osgi-mock + 2.2.4 + test + + + org.apache.sling + org.apache.sling.testing.sling-mock + 1.9.6 + test + + + org.apache.sling + org.apache.sling.testing.hamcrest + 1.0.2 + test + + + junit + junit + test + + + org.slf4j + slf4j-simple + test + + + org.mockito + mockito-core + 2.0.87-beta + test + + + org.apache.johnzon + johnzon-core + 1.0.0 + test + + + + + diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBindingsValueProvider.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBindingsValueProvider.java new file mode 100644 index 000000000..37e87be98 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBindingsValueProvider.java @@ -0,0 +1,208 @@ +/* + * 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.caconfig.impl; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.script.Bindings; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.scripting.api.BindingsValuesProvider; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Binds a script variable "caconfig" to the current configuration value map to be + * used in HTL (Sightly). + */ +@Component(immediate = true, service = BindingsValuesProvider.class, property = { + "javax.script.name=sightly", + Constants.SERVICE_RANKING + "=100" }) +@Designate(ocd = ConfigurationBindingsValueProvider.Config.class) +public class ConfigurationBindingsValueProvider implements BindingsValuesProvider { + + /** + * Name of the variable to which the config value map is bound to in script + * configuration. + */ + public static final String BINDING_VARIABLE = "caconfig"; + + @ObjectClassDefinition(name = "Apache Sling Context-Aware Configuration HTL Binding Values Provider", + description = "Binds a script variable '" + BINDING_VARIABLE + "' to the HTL/Sightly scripting context.") + static @interface Config { + + @AttributeDefinition(name = "Enabled", description = "Enable provider.") + boolean enabled() default true; + + } + + @Reference + private ConfigurationMetadataProviderMultiplexer configMetadataProvider; + + private boolean enabled; + + @Override + @SuppressWarnings("unused") + public void addBindings(Bindings bindings) { + if (!enabled || !bindings.containsKey(SlingBindings.REQUEST)) { + return; + } + SlingHttpServletRequest request = (SlingHttpServletRequest)bindings.get(SlingBindings.REQUEST); + Resource resource = request.getResource(); + if (resource == null) { + return; + } + Map configMap = new ConfigMap(resource, configMetadataProvider); + bindings.put(BINDING_VARIABLE, configMap); + } + + @Activate + void activate(Config config) { + this.enabled = config.enabled(); + } + + + /** + * This is a "virtual" containing configuration names as keys, and the underlying value maps/value map collections as values. + * The map accesses only the data that is really required in a lazy fashion. + */ + private static class ConfigMap implements Map { + + private final Resource resource; + private final ConfigurationMetadataProvider configMetadataProvider; + private Set configNamesCache; + private Map valuesCache = new HashMap<>(); + + ConfigMap(Resource resource, ConfigurationMetadataProvider configMetadataProvider) { + this.resource = resource; + this.configMetadataProvider = configMetadataProvider; + } + + private Set getConfigNames() { + if (configNamesCache == null) { + configNamesCache = configMetadataProvider.getConfigurationNames(); + } + return configNamesCache; + } + + @Override + public int size() { + return getConfigNames().size(); + } + + @Override + public boolean isEmpty() { + return getConfigNames().isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return getConfigNames().contains(key); + } + + @Override + public Object get(Object key) { + Object value = valuesCache.get(key); + if (value == null) { + value = getConfigValue((String)key); + if (value != null) { + valuesCache.put((String)key, value); + } + } + return value; + } + + private Object getConfigValue(String configName) { + @SuppressWarnings("null") + ConfigurationBuilder configBuilder = resource.adaptTo(ConfigurationBuilder.class).name(configName); + if (isCollection(configName)) { + return configBuilder.asValueMapCollection(); + } + else { + return configBuilder.asValueMap(); + } + } + + private boolean isCollection(String configName) { + ConfigurationMetadata configMetadata = configMetadataProvider.getConfigurationMetadata(configName); + if (configMetadata != null) { + return configMetadata.isCollection(); + } + else { + return false; + } + } + + @Override + public Set keySet() { + return getConfigNames(); + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderAdapterFactory.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderAdapterFactory.java new file mode 100644 index 000000000..464b574db --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderAdapterFactory.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl; + +import org.apache.sling.api.adapter.AdapterFactory; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +@Component(service=AdapterFactory.class, + property={ + AdapterFactory.ADAPTER_CLASSES + "=org.apache.sling.caconfig.ConfigurationBuilder", + AdapterFactory.ADAPTABLE_CLASSES + "=org.apache.sling.api.resource.Resource" + }) +public class ConfigurationBuilderAdapterFactory implements AdapterFactory { + + @Reference(policyOption = ReferencePolicyOption.GREEDY) + private ConfigurationResolver resolver; + + @Override + @SuppressWarnings("unchecked") + public AdapterType getAdapter(@NotNull final Object adaptable, @NotNull final Class type) { + if (adaptable instanceof Resource && type == ConfigurationBuilder.class) { + return (AdapterType) resolver.get((Resource) adaptable); + } + return null; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderImpl.java new file mode 100644 index 000000000..307de4e23 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationBuilderImpl.java @@ -0,0 +1,429 @@ +/* + * 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.caconfig.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.ResettableListIterator; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.iterators.ListIteratorWrapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.impl.ConfigurationProxy.ChildResolver; +import org.apache.sling.caconfig.impl.metadata.AnnotationClassParser; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil; +import org.apache.sling.caconfig.resource.impl.util.MapUtil; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class ConfigurationBuilderImpl implements ConfigurationBuilder { + + private final Resource contentResource; + private final ConfigurationResolver configurationResolver; + private final ConfigurationResourceResolvingStrategy configurationResourceResolvingStrategy; + private final ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy; + private final ConfigurationInheritanceStrategy configurationInheritanceStrategy; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationMetadataProvider configurationMetadataProvider; + private final Collection configBucketNames; + private final String configName; + + private static final Logger log = LoggerFactory.getLogger(ConfigurationBuilderImpl.class); + + public ConfigurationBuilderImpl(final Resource resource, + final ConfigurationResolver configurationResolver, + final ConfigurationResourceResolvingStrategy configurationResourceResolvingStrategy, + final ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy, + final ConfigurationInheritanceStrategy configurationInheritanceStrategy, + final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + final ConfigurationMetadataProvider configurationMetadataProvider, + final Collection configBucketNames) { + this(resource, configurationResolver, configurationResourceResolvingStrategy, configurationPersistenceStrategy, + configurationInheritanceStrategy, configurationOverrideMultiplexer, configurationMetadataProvider, configBucketNames, null); + } + + private ConfigurationBuilderImpl(final Resource resource, + final ConfigurationResolver configurationResolver, + final ConfigurationResourceResolvingStrategy configurationResourceResolvingStrategy, + final ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy, + final ConfigurationInheritanceStrategy configurationInheritanceStrategy, + final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + final ConfigurationMetadataProvider configurationMetadataProvider, + final Collection configBucketNames, + final String configName) { + this.contentResource = resource; + this.configurationResolver = configurationResolver; + this.configurationResourceResolvingStrategy = configurationResourceResolvingStrategy; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configurationInheritanceStrategy = configurationInheritanceStrategy; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationMetadataProvider = configurationMetadataProvider; + this.configBucketNames = configBucketNames; + this.configName = configName; + } + + @Override + public @NotNull ConfigurationBuilder name(@NotNull final String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + return new ConfigurationBuilderImpl(contentResource, + configurationResolver, + configurationResourceResolvingStrategy, + configurationPersistenceStrategy, + configurationInheritanceStrategy, + configurationOverrideMultiplexer, + configurationMetadataProvider, + configBucketNames, + configName); + } + + /** + * Validate the configuration name. + * @param name Configuration name or relative path + */ + private void validateConfigurationName(String name) { + if (name == null) { + throw new ConfigurationResolveException("Configuration name is required."); + } + } + + /** + * Converts configuration resource into given class. + * @param Target class + */ + private interface Converter { + T convert(Resource resource, Class clazz, String configName, boolean isCollection); + } + + /** + * Get singleton configuration resource and convert it to the desired target class. + * @param configName Configuration name + * @param clazz Target class + * @param converter Conversion method + * @return Converted singleton configuration + */ + private T getConfigResource(String configName, Class clazz, Converter converter) { + Iterator resourceInheritanceChain = null; + if (this.contentResource != null) { + validateConfigurationName(configName); + resourceInheritanceChain = this.configurationResourceResolvingStrategy + .getResourceInheritanceChain(this.contentResource, configBucketNames, configName); + } + return convert(resourceInheritanceChain, clazz, converter, configName, false); + } + + /** + * Get configuration resource collection and convert it to the desired target class. + * @param configName Configuration name + * @param clazz Target class + * @param converter Conversion method + * @return Converted configuration collection + */ + private Collection getConfigResourceCollection(String configName, Class clazz, Converter converter) { + if (this.contentResource != null) { + validateConfigurationName(configName); + + // get all possible colection parent config names + Collection collectionParentConfigNames = configurationPersistenceStrategy.getAllCollectionParentConfigNames(configName); + List> resourceInheritanceChains = new ArrayList<>(); + for (String collectionParentConfigName : collectionParentConfigNames) { + Collection> result = this.configurationResourceResolvingStrategy + .getResourceCollectionInheritanceChain(this.contentResource, configBucketNames, collectionParentConfigName); + if (result != null) { + resourceInheritanceChains.addAll(result); + } + } + + final Collection result = new ArrayList<>(); + for (final Iterator resourceInheritanceChain : resourceInheritanceChains) { + final T obj = convert(resourceInheritanceChain, clazz, converter, configName, true); + if (obj != null) { + result.add(obj); + } + } + return result; + } + else { + return Collections.emptyList(); + } + } + + @SuppressWarnings("unchecked") + private T convert(final Iterator resourceInhertianceChain, final Class clazz, final Converter converter, + final String name, final boolean isCollection) { + Resource configResource = null; + String conversionName = name; + if (resourceInhertianceChain != null) { + ResettableListIterator resettableResourceInhertianceChain = new ListIteratorWrapper(resourceInhertianceChain); + // apply persistence transformation + Iterator transformedResources = IteratorUtils.transformedIterator(resettableResourceInhertianceChain, + new Transformer() { + @Override + public Object transform(Object input) { + if (isCollection) { + return configurationPersistenceStrategy.getCollectionItemResource((Resource)input); + } + else { + return configurationPersistenceStrategy.getResource((Resource)input); + } + } + }); + // apply resource inheritance + configResource = configurationInheritanceStrategy.getResource(transformedResources); + // apply overrides + configResource = configurationOverrideMultiplexer.overrideProperties(contentResource.getPath(), name, configResource, contentResource.getResourceResolver()); + // build name + if (isCollection) { + // get untransformed resource for getting collection item name + resettableResourceInhertianceChain.reset(); + Resource untransformedConfigResource = configurationInheritanceStrategy.getResource(resettableResourceInhertianceChain); + if (untransformedConfigResource != null && configResource != null) { + conversionName = configurationPersistenceStrategy.getCollectionParentConfigName(conversionName, configResource.getPath()) + + "/" + untransformedConfigResource.getName(); + } + } + } + if (log.isTraceEnabled() && configResource != null) { + log.trace("+ Found config resource for context path " + contentResource.getPath() + ": " + configResource.getPath() + " " + + MapUtil.traceOutput(configResource.getValueMap())); + } + + // if no config resource found still check for overrides + if (configResource == null && contentResource != null) { + configResource = configurationOverrideMultiplexer.overrideProperties(contentResource.getPath(), name, (Resource)null, contentResource.getResourceResolver()); + } + + return converter.convert(configResource, clazz, conversionName, isCollection); + } + + /** + * Apply default values from configuration metadata (where no real data is present). + * @param resource Resource + * @param configName Configuration name + * @return null if no default values found, or a wrapped resource with added default properties. + */ + private Resource applyDefaultValues(Resource resource, String configName) { + if (resource == null) { + return null; + } + Map updatedMap = applyDefaultValues(resource.getValueMap(), configName); + if (updatedMap == null) { + return resource; + } + return new ConfigurationResourceWrapper(resource, new ValueMapDecorator(updatedMap)); + } + + /** + * Apply default values from configuration metadata (where no real data is present). + * @param props Properties + * @param configName Configuration name + * @return null if no default values found, or a new map with added default properties. + */ + private Map applyDefaultValues(Map props, String configName) { + ConfigurationMetadata metadata = configurationMetadataProvider.getConfigurationMetadata(configName); + if (metadata == null) { + // probably a configuration list - remove item name from end + if (StringUtils.contains(configName, "/")) { + String partialConfigName = StringUtils.substringBeforeLast(configName, "/"); + metadata = configurationMetadataProvider.getConfigurationMetadata(partialConfigName); + } + if (metadata == null) { + return null; + } + } + Map updatedMap = new HashMap<>(); + for (PropertyMetadata propertyMetadata : metadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + updatedMap.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + if (updatedMap.isEmpty()) { + return null; + } + updatedMap.putAll(props); + return updatedMap; + } + + // --- Annotation class support --- + + @Override + public @NotNull T as(@NotNull final Class clazz) { + final String name = getConfigurationNameForAnnotationClass(clazz); + if (log.isDebugEnabled()) { + log.debug("Get configuration for context path {}, name '{}', class {}", contentResource.getPath(), name, clazz.getName()); + } + return getConfigResource(name, clazz, new AnnotationConverter()); + } + + @Override + public @NotNull Collection asCollection(@NotNull Class clazz) { + final String name = getConfigurationNameForAnnotationClass(clazz); + if (log.isDebugEnabled()) { + log.debug("Get configuration collection for context path {}, name '{}', class {}", contentResource.getPath(), name, clazz.getName()); + } + return getConfigResourceCollection(name, clazz, new AnnotationConverter()); + } + + private String getConfigurationNameForAnnotationClass(Class clazz) { + if (this.configName != null) { + return this.configName; + } + else { + // derive configuration name from annotation class if no name specified + return AnnotationClassParser.getConfigurationName(clazz); + } + } + + private class AnnotationConverter implements Converter { + @Override + public T convert(final Resource resource, final Class clazz, final String configName, final boolean isCollection) { + return ConfigurationProxy.get(resource, clazz, new ChildResolver() { + private ConfigurationBuilder getConfiguration(String nestedConfigName) { + String childName; + String relatedConfigPath = resource != null ? resource.getPath() : null; + if (isCollection) { + childName = configurationPersistenceStrategy.getCollectionItemConfigName(configName, relatedConfigPath) + "/" + nestedConfigName; + } + else { + childName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigName; + } + return configurationResolver.get(contentResource).name(childName); + } + @Override + public C getChild(String configName, Class clazz) { + return getConfiguration(configName).as(clazz); + } + @Override + public Collection getChildren(String configName, Class clazz) { + return getConfiguration(configName).asCollection(clazz); + } + }); + } + } + + // --- ValueMap support --- + + @Override + public @NotNull ValueMap asValueMap() { + if (log.isDebugEnabled()) { + log.debug("Get ValueMap for context path {}, name '{}'", contentResource.getPath(), this.configName); + } + return getConfigResource(this.configName, ValueMap.class, new ValueMapConverter()); + } + + @Override + public @NotNull Collection asValueMapCollection() { + if (log.isDebugEnabled()) { + log.debug("Get ValueMap collection for context path {}, name '{}'", contentResource.getPath(), this.configName); + } + return getConfigResourceCollection(this.configName, ValueMap.class, new ValueMapConverter()); + } + + private class ValueMapConverter implements Converter { + @Override + public ValueMap convert(Resource resource, Class clazz, String configName, boolean isCollection) { + ValueMap props = ResourceUtil.getValueMap(resource); + Map updatedMap = applyDefaultValues(props, configName); + if (updatedMap != null) { + return new ValueMapDecorator(updatedMap); + } + else { + return props; + } + } + } + + // --- Adaptable support --- + + @Override + public T asAdaptable(@NotNull Class clazz) { + if (log.isDebugEnabled()) { + log.debug("Get adaptable for context path {}, name '{}', class {}", contentResource.getPath(), this.configName, clazz); + } + return getConfigResource(this.configName, clazz, new AdaptableConverter()); + } + + @Override + public @NotNull Collection asAdaptableCollection(@NotNull Class clazz) { + if (log.isDebugEnabled()) { + log.debug("Get adaptable collection for context path {}, name '{}', class {}", contentResource.getPath(), this.configName, clazz); + } + return getConfigResourceCollection(this.configName, clazz, new AdaptableConverter()); + } + + private class AdaptableConverter implements Converter { + @Override + public T convert(Resource resource, Class clazz, String configName, boolean isCollection) { + if (resource == null || clazz == ConfigurationBuilder.class) { + return null; + } + return applyDefaultValues(resource, configName).adaptTo(clazz); + } + } + + // --- Config Node Existence Check Support --- + + @Override + public boolean has(@NotNull Class clazz) { + final String name = getConfigurationNameForAnnotationClass(clazz); + if (log.isDebugEnabled()) { + log.debug("Check configuration for context path {}, name '{}', class {}", contentResource.getPath(), name, clazz.getName()); + } + return checkIfConfigNodeExists(name); + } + + @Override + public boolean has(@NotNull String configName) { + if (log.isDebugEnabled()) { + log.debug("Check configuration for context path {}, configuration name '{}' ", contentResource.getPath(), configName); + } + return checkIfConfigNodeExists(configName); + } + + private boolean checkIfConfigNodeExists(String configName) { + Resource configResource = null; + if (this.contentResource != null) { + validateConfigurationName(configName); + configResource = this.configurationResourceResolvingStrategy + .getResource(this.contentResource, configBucketNames, configName); + } + return configResource != null ? true : false; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImpl.java new file mode 100644 index 000000000..2822bea90 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImpl.java @@ -0,0 +1,90 @@ +/* + * 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.caconfig.impl; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.ResettableListIterator; +import org.apache.commons.collections4.iterators.ListIteratorWrapper; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationInheritanceStrategyMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Detects all {@link ConfigurationInheritanceStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationInheritanceStrategyMultiplexer.class, +reference={ + @Reference(name="configurationInheritanceStrategy", service=ConfigurationInheritanceStrategy.class, + bind="bindConfigurationInheritanceStrategy", unbind="unbindConfigurationInheritanceStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationInheritanceStrategyMultiplexerImpl implements ConfigurationInheritanceStrategyMultiplexer { + + private RankedServices items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationInheritanceStrategy(ConfigurationInheritanceStrategy item, Map props) { + items.bind(item, props); + } + + protected void unbindConfigurationInheritanceStrategy(ConfigurationInheritanceStrategy item, Map props) { + items.unbind(item, props); + } + + /** + * Get result from first strategy implementation that has an answer. + */ + @SuppressWarnings("unchecked") + @Override + public Resource getResource(@NotNull Iterator configResources) { + List itemList = items.getList(); + if (itemList.isEmpty()) { + return null; + } + else if (itemList.size() == 1) { + return itemList.get(0).getResource(configResources); + } + else { + ResettableListIterator resettableConfigResources = new ListIteratorWrapper(configResources); + for (ConfigurationInheritanceStrategy item : items) { + Resource result = item.getResource(resettableConfigResources); + if (result != null) { + return result; + } + else { + resettableConfigResources.reset(); + } + } + return null; + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationNameConstants.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationNameConstants.java new file mode 100644 index 000000000..d085310ee --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationNameConstants.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl; + +public final class ConfigurationNameConstants { + + private ConfigurationNameConstants() { + // constants only + } + + /** + * Bucket name for configuration data (configuration with key/value pairs). + */ + public static final String CONFIGS_BUCKET_NAME = "sling:configs"; + + /** + * Bundle header defining list of class names with all configuration annotation classes in this bundle. + */ + public static final String CONFIGURATION_CLASSES_HEADER = "Sling-ContextAware-Configuration-Classes"; + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationPersistenceStrategyBridge.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationPersistenceStrategyBridge.java new file mode 100644 index 000000000..f8600784d --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationPersistenceStrategyBridge.java @@ -0,0 +1,208 @@ +/* + * 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.caconfig.impl; + +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.ServiceUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Bridges services implementing the deprecated {@link ConfigurationPersistenceStrategy} interface + * to the {@link ConfigurationPersistenceStrategy2} interface for backwards compatibility. + */ +@Component(reference={ + @Reference(name="configurationPersistenceStrategy", service=ConfigurationPersistenceStrategy.class, + bind="bindConfigurationPersistenceStrategy", unbind="unbindConfigurationPersistenceStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +@SuppressWarnings("deprecation") +public final class ConfigurationPersistenceStrategyBridge { + + private volatile BundleContext bundleContext; + private final ConcurrentMap, ServiceRegistration> services = new ConcurrentHashMap<>(); + private final ConcurrentMap, ServiceInfo> preActivateServices = new ConcurrentHashMap<>(); + + protected void bindConfigurationPersistenceStrategy(ConfigurationPersistenceStrategy item, Map props) { + ServiceInfo serviceInfo = new ServiceInfo(item, props); + Comparable key = ServiceUtil.getComparableForServiceRanking(props, Order.ASCENDING); + if (bundleContext != null) { + services.put(key, registerBridgeService(serviceInfo)); + } + else { + preActivateServices.put(key, serviceInfo); + } + } + + protected void unbindConfigurationPersistenceStrategy(ConfigurationPersistenceStrategy item, Map props) { + Comparable key = ServiceUtil.getComparableForServiceRanking(props, Order.ASCENDING); + unregisterBridgeService(services.remove(key)); + } + + /** + * Register {@link ConfigurationPersistenceStrategy2} bridge service for {@link ConfigurationPersistenceStrategy} service. + * @param serviceInfo Service information + * @return Service registration + */ + private ServiceRegistration registerBridgeService(ServiceInfo serviceInfo) { + return bundleContext.registerService(ConfigurationPersistenceStrategy2.class, + new Adapter(serviceInfo.getService()), new Hashtable<>(serviceInfo.getProps())); + } + + /** + * Unregister {@link ConfigurationPersistenceStrategy2} bridge service. + * @param service Service registration + */ + private void unregisterBridgeService(ServiceRegistration service) { + if (service != null) { + service.unregister(); + } + } + + @Activate + private void activate(BundleContext bundleContext) { + this.bundleContext = bundleContext; + for (Map.Entry, ServiceInfo> entry : preActivateServices.entrySet()) { + services.put(entry.getKey(), registerBridgeService(entry.getValue())); + } + } + + + private static class ServiceInfo { + private final ConfigurationPersistenceStrategy service; + private final Map props; + + public ServiceInfo(ConfigurationPersistenceStrategy service, Map props) { + this.service = service; + this.props = props; + } + public ConfigurationPersistenceStrategy getService() { + return service; + } + public Map getProps() { + return props; + } + } + + + /** + * Adapter which delegates {@link ConfigurationPersistenceStrategy2} methods to a {@link ConfigurationPersistenceStrategy} service. + */ + public static class Adapter implements ConfigurationPersistenceStrategy2 { + private final ConfigurationPersistenceStrategy delegate; + + public Adapter(ConfigurationPersistenceStrategy delegate) { + this.delegate = delegate; + } + + /** + * @return Implementation class of the original service. + */ + public Class getOriginalServiceClass() { + return delegate.getClass(); + } + + @Override + public Resource getResource(@NotNull Resource resource) { + return delegate.getResource(resource); + } + + @Override + public Resource getCollectionParentResource(@NotNull Resource resource) { + // with SPI/Impl 1.2 it was not possible to manipulate collection parent resource + return resource; + } + + @Override + public Resource getCollectionItemResource(@NotNull Resource resource) { + return delegate.getResource(resource); + } + + @Override + public String getResourcePath(@NotNull String resourcePath) { + return delegate.getResourcePath(resourcePath); + } + + @Override + public String getCollectionParentResourcePath(@NotNull String resourcePath) { + // with SPI/Impl 1.2 it was not possible to manipulate collection parent resource + return resourcePath; + } + + @Override + public String getCollectionItemResourcePath(@NotNull String resourcePath) { + return delegate.getResourcePath(resourcePath); + } + + @Override + public String getConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + return delegate.getResourcePath(configName); + } + + @Override + public String getCollectionParentConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + // with SPI/Impl 1.2 it was not possible to manipulate collection parent resource + return configName; + } + + @Override + public String getCollectionItemConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + return delegate.getResourcePath(configName); + } + + @Override + public boolean persistConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath, + @NotNull ConfigurationPersistData data) { + return delegate.persistConfiguration(resourceResolver, configResourcePath, data); + } + + @Override + public boolean persistConfigurationCollection(@NotNull ResourceResolver resourceResolver, + @NotNull String configResourceCollectionParentPath, @NotNull ConfigurationCollectionPersistData data) { + return delegate.persistConfigurationCollection(resourceResolver, configResourceCollectionParentPath, data); + } + + @Override + public boolean deleteConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath) { + return delegate.deleteConfiguration(resourceResolver, configResourcePath); + } + + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationProxy.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationProxy.java new file mode 100644 index 000000000..7a9cf9361 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationProxy.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.impl.metadata.AnnotationClassParser; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Maps the property of a resource to a dynamic proxy object implementing + * the annotation class defining the configuration parameters. + * Nested configurations with annotation classes referencing other annotation classes are also supported. + */ +final class ConfigurationProxy { + + private ConfigurationProxy() { + // static methods only + } + + /** + * Get dynamic proxy for given resources's properties mapped to given annotation class. + * @param resource Resource + * @param clazz Annotation class + * @param childResolver This is used to resolve nested configuration objects relative to the current configuration resource + * @return Dynamic proxy object + */ + @SuppressWarnings("unchecked") + public @NotNull static T get(@Nullable Resource resource, @NotNull Class clazz, ChildResolver childResolver) { + + // only annotation interface classes are supported + if (!clazz.isAnnotation()) { + throw new ConfigurationResolveException("Annotation interface class expected: " + clazz.getName()); + } + + // create dynamic proxy for annotation class accessing underlying resource properties + // wrap in caching invocation handler so client code can call all methods multiple times + // without having to worry about performance + return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, + new CachingInvocationHandler(new DynamicProxyInvocationHandler(resource, childResolver))); + } + + /** + * Resolves nested configurations. + */ + public static interface ChildResolver { + T getChild(String configName, Class clazz); + Collection getChildren(String configName, Class clazz); + } + + /** + * Maps resource properties to annotation class proxy, and support nested configurations. + */ + static class DynamicProxyInvocationHandler implements InvocationHandler { + + private final Resource resource; + private final ChildResolver childResolver; + + private DynamicProxyInvocationHandler(Resource resource, ChildResolver childResolver) { + this.resource = resource; + this.childResolver = childResolver; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) { + String propName = AnnotationClassParser.getPropertyName(method.getName()); + + // check for nested configuration classes + Class targetType = method.getReturnType(); + Class componentType = method.getReturnType(); + boolean isArray = targetType.isArray(); + if (isArray) { + componentType = targetType.getComponentType(); + } + if (componentType.isAnnotation()) { + if (isArray) { + Collection listItems = childResolver.getChildren(propName, componentType); + return listItems.toArray((Object[])Array.newInstance(componentType, listItems.size())); + } + else { + return childResolver.getChild(propName, componentType); + } + } + + // validate type + if (!isValidType(componentType)) { + throw new ConfigurationResolveException("Unsupported type " + componentType.getName() + + " in " + method.getDeclaringClass() + "#" + method.getName()); + } + + // detect default value + Object defaultValue = method.getDefaultValue(); + if (defaultValue == null) { + if (isArray) { + defaultValue = Array.newInstance(componentType, 0); + } + else if (targetType.isPrimitive()) { + // get default value for primitive data type (use hack via array) + defaultValue = Array.get(Array.newInstance(targetType, 1), 0); + } + } + + // get value from valuemap with given type/default value + ValueMap props = ResourceUtil.getValueMap(resource); + Object value; + if (defaultValue != null) { + value = props.get(propName, defaultValue); + } + else { + value = props.get(propName, targetType); + } + return value; + + } + + /** + * Ensures the given type is support for reading configuration parameters. + * @param type Type + * @return true if type is supported + */ + private boolean isValidType(Class type) { + return PropertyMetadata.SUPPORTED_TYPES.contains(type); + } + + } + + /** + * Invocation handler that caches all results for each method name, and returns + * the result from cache on next invocation. + */ + static class CachingInvocationHandler implements InvocationHandler { + + private final InvocationHandler delegate; + private final Map results = new HashMap<>(); + private static final Object NULL_OBJECT = new Object(); + + public CachingInvocationHandler(InvocationHandler delegate) { + this.delegate = delegate; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + String key = method.getName(); + Object result = results.get(key); + if (result == null) { + result = delegate.invoke(proxy, method, args); + if (result == null) { + result = NULL_OBJECT; + } + results.put(key, result); + } + if (result == NULL_OBJECT) { + return null; + } + else { + return result; + } + } + + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResolverImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResolverImpl.java new file mode 100644 index 000000000..7a8212a88 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResolverImpl.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.caconfig.impl; + +import static org.apache.sling.caconfig.impl.ConfigurationNameConstants.CONFIGS_BUCKET_NAME; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.management.ConfigurationResourceResolverConfig; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationInheritanceStrategyMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationResourceResolvingStrategyMultiplexer; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +@Component(service={ ConfigurationResolver.class, ConfigurationResourceResolverConfig.class }, immediate=true) +@Designate(ocd=ConfigurationResolverImpl.Config.class) +public class ConfigurationResolverImpl implements ConfigurationResolver, ConfigurationResourceResolverConfig { + + @Reference + private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy; + @Reference + private ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy; + @Reference + private ConfigurationInheritanceStrategyMultiplexer configurationInheritanceStrategy; + @Reference + private ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + @Reference + private ConfigurationMetadataProviderMultiplexer configurationMetadataProvider; + + @ObjectClassDefinition(name="Apache Sling Context-Aware Configuration Resolver", + description="Getting context-aware configurations for a given resource context.") + static @interface Config { + + @AttributeDefinition(name = "Config bucket names", + description = "Additional bucket resource names to '" + CONFIGS_BUCKET_NAME + "' to store configuration resources. " + + "The names are used in the order defined, always starting with " + CONFIGS_BUCKET_NAME + ". " + + "Once a bucket resource with a matching name is found, that bucket is used and the following names are skipped. " + + "For writeback via ConfigurationManager always " + CONFIGS_BUCKET_NAME + " is used.") + String[] configBucketNames(); + + } + + private Collection configBucketNames; + + @Activate + private void activate(Config config) { + configBucketNames = new ArrayList<>(); + configBucketNames.add(ConfigurationNameConstants.CONFIGS_BUCKET_NAME); + if (!ArrayUtils.isEmpty(config.configBucketNames())) { + configBucketNames.addAll(Arrays.asList(config.configBucketNames())); + } + } + + @Override + public @NotNull ConfigurationBuilder get(@NotNull Resource resource) { + return new ConfigurationBuilderImpl(resource, this, + configurationResourceResolvingStrategy, configurationPersistenceStrategy, + configurationInheritanceStrategy, configurationOverrideMultiplexer, configurationMetadataProvider, + configBucketNames); + } + + @Override + public @NotNull Collection configBucketNames() { + return configBucketNames; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResourceWrapper.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResourceWrapper.java new file mode 100644 index 000000000..938886c77 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/ConfigurationResourceWrapper.java @@ -0,0 +1,132 @@ +/* + * 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.caconfig.impl; + +import java.util.Iterator; + +import org.apache.sling.api.resource.AbstractResource; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.jetbrains.annotations.NotNull; + +/** + * Wrapper that returns an enhanced value map for the resource + * providing a merged map with all inherited property values. + * Unfortunately it's not possible to use {@link org.apache.sling.api.resource.ResourceWrapper} because when adapting + * to a Sling Model the replace valuemap would not take effect but the original value map. + */ +public final class ConfigurationResourceWrapper extends AbstractResource { + + private final Resource resource; + private final ValueMap props; + + public ConfigurationResourceWrapper(Resource resource, ValueMap props) { + this.resource = unwrap(resource); + this.props = props; + } + + private static Resource unwrap(Resource resource) { + if (resource instanceof ConfigurationResourceWrapper) { + return ((ConfigurationResourceWrapper)resource).resource; + } + else { + return resource; + } + } + + @SuppressWarnings({ "unchecked", "null" }) + public AdapterType adaptTo(@NotNull Class type) { + if (type == ValueMap.class) { + return (AdapterType)props; + } + return super.adaptTo(type); + } + + public ValueMap getValueMap() { + return props; + } + + public @NotNull String getPath() { + return resource.getPath(); + } + + public String getName() { + return resource.getName(); + } + + public Resource getParent() { + return resource.getParent(); + } + + public Iterator listChildren() { + return resource.listChildren(); + } + + public Iterable getChildren() { + return resource.getChildren(); + } + + public Resource getChild(String relPath) { + return resource.getChild(relPath); + } + + public @NotNull String getResourceType() { + return resource.getResourceType(); + } + + public String getResourceSuperType() { + return resource.getResourceSuperType(); + } + + public boolean hasChildren() { + return resource.hasChildren(); + } + + public boolean isResourceType(String resourceType) { + return resource.isResourceType(resourceType); + } + + public @NotNull ResourceMetadata getResourceMetadata() { + return resource.getResourceMetadata(); + } + + public @NotNull ResourceResolver getResourceResolver() { + return resource.getResourceResolver(); + } + + /** + * @return Returns a string representation of this wrapper consisting of the class' + * simple name, the {@link #getResourceType() resource type} and + * {@link #getPath() path} as well as the string representation of the wrapped resource}. + */ + @Override + public String toString() { + final String className; + if (getClass().getSimpleName().length() == 0) { + className = getClass().getName(); + } else { + className = getClass().getSimpleName(); + } + return className + ", type=" + getResourceType() + + ", path=" + getPath() + ", resource=[" + resource + "]"; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/ConfigurationDefNameConstants.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/ConfigurationDefNameConstants.java new file mode 100644 index 000000000..ab38524d1 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/ConfigurationDefNameConstants.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl.def; + +public final class ConfigurationDefNameConstants { + + private ConfigurationDefNameConstants() { + // constants only + } + + /** + * Boolean property that controls whether the properties (key/value pairs) of configuration resources + * should be inherited from the configuration hierarchy and merged. + */ + public static final String PROPERTY_CONFIG_PROPERTY_INHERIT = "sling:configPropertyInherit"; + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationInheritanceStrategy.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationInheritanceStrategy.java new file mode 100644 index 000000000..1845075d3 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationInheritanceStrategy.java @@ -0,0 +1,106 @@ +/* + * 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.caconfig.impl.def; + +import static org.apache.sling.caconfig.impl.def.ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.impl.ConfigurationResourceWrapper; +import org.apache.sling.caconfig.resource.impl.util.PropertyUtil; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service=ConfigurationInheritanceStrategy.class) +@Designate(ocd=DefaultConfigurationInheritanceStrategy.Config.class) +public class DefaultConfigurationInheritanceStrategy implements ConfigurationInheritanceStrategy { + + @ObjectClassDefinition(name="Apache Sling Context-Aware Configuration Default Inheritance Strategy", + description="Standardized resource inheritance for configurations.") + public static @interface Config { + + @AttributeDefinition(name="Enabled", + description = "Enable this configuration inheritance strategy.") + boolean enabled() default true; + + @AttributeDefinition(name="Config property inheritance property names", + description = "Additional property names to " + PROPERTY_CONFIG_PROPERTY_INHERIT + " to handle property inheritance. The names are used in the order defined, " + + "always starting with " + PROPERTY_CONFIG_PROPERTY_INHERIT + ". Once a property with a value is found, that value is used and the following property names are skipped.") + String[] configPropertyInheritancePropertyNames(); + + } + + private Config config; + + private static final Logger log = LoggerFactory.getLogger(DefaultConfigurationInheritanceStrategy.class); + + @Activate + private void activate(final Config config) { + this.config = config; + } + + @Override + public Resource getResource(@NotNull Iterator configResources) { + if (!config.enabled()) { + return null; + } + if (!configResources.hasNext()) { + return null; + } + Resource primary = configResources.next(); + if (!isPropertyInheritance(primary) || !configResources.hasNext()) { + return primary; + } + Map mergedProps = getInheritedProperties(primary.getValueMap(), configResources); + return new ConfigurationResourceWrapper(primary, new ValueMapDecorator(mergedProps)); + } + + private boolean isPropertyInheritance(Resource resource) { + return PropertyUtil.getBooleanValueAdditionalKeys(resource.getValueMap(), PROPERTY_CONFIG_PROPERTY_INHERIT, + config.configPropertyInheritancePropertyNames()); + } + + private Map getInheritedProperties(Map parentProps, Iterator inheritanceChain) { + if (!inheritanceChain.hasNext()) { + return parentProps; + } + Resource next = inheritanceChain.next(); + log.trace("! Property inheritance: Merge with properties from {}", next.getPath()); + Map merged = new HashMap<>(next.getValueMap()); + merged.putAll(parentProps); + if (isPropertyInheritance(next)) { + return getInheritedProperties(merged, inheritanceChain); + } + else { + return merged; + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationPersistenceStrategy.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationPersistenceStrategy.java new file mode 100644 index 000000000..cf5571a81 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationPersistenceStrategy.java @@ -0,0 +1,276 @@ +/* + * 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.caconfig.impl.def; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.ModifiableValueMap; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.impl.PropertiesFilterUtil; +import org.apache.sling.caconfig.resource.impl.util.MapUtil; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceAccessDeniedException; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceException; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The default persistence strategy is quite simple: directly use the configuration resources. + * All existing properties are removed when new properties are stored in a singleton config resource. + * All existing child resources are removed when a new configs are stored for collection config resources. + */ +@Component(service = ConfigurationPersistenceStrategy2.class) +@Designate(ocd=DefaultConfigurationPersistenceStrategy.Config.class) +public class DefaultConfigurationPersistenceStrategy implements ConfigurationPersistenceStrategy2 { + + @ObjectClassDefinition(name="Apache Sling Context-Aware Configuration Default Resource Persistence Strategy", + description="Directly uses configuration resources for storing configuration data.") + static @interface Config { + + @AttributeDefinition(name="Enabled", + description = "Enable this configuration resource persistence strategy.") + boolean enabled() default true; + + } + + @Reference + private ConfigurationManagementSettings configurationManagementSettings; + + private volatile Config config; + + private static final Logger log = LoggerFactory.getLogger(DefaultConfigurationPersistenceStrategy.class); + + @Activate + private void activate(ComponentContext componentContext, Config config) { + this.config = config; + } + + @Override + public Resource getResource(@NotNull Resource resource) { + if (!config.enabled()) { + return null; + } + return resource; + } + + @Override + public Resource getCollectionParentResource(@NotNull Resource resource) { + if (!config.enabled()) { + return null; + } + return resource; + } + + @Override + public Resource getCollectionItemResource(@NotNull Resource resource) { + if (!config.enabled()) { + return null; + } + return resource; + } + + @Override + public String getResourcePath(@NotNull String resourcePath) { + if (!config.enabled()) { + return null; + } + return resourcePath; + } + + @Override + public String getCollectionParentResourcePath(@NotNull String resourcePath) { + if (!config.enabled()) { + return null; + } + return resourcePath; + } + + @Override + public String getCollectionItemResourcePath(@NotNull String resourcePath) { + if (!config.enabled()) { + return null; + } + return resourcePath; + } + + @Override + public String getConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + if (!config.enabled()) { + return null; + } + return configName; + } + + @Override + public String getCollectionParentConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + if (!config.enabled()) { + return null; + } + return configName; + } + + @Override + public String getCollectionItemConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + if (!config.enabled()) { + return null; + } + return configName; + } + + @Override + public boolean persistConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath, + @NotNull ConfigurationPersistData data) { + if (!config.enabled()) { + return false; + } + getOrCreateResource(resourceResolver, configResourcePath, data.getProperties()); + commit(resourceResolver, configResourcePath); + return true; + } + + @Override + public boolean persistConfigurationCollection(@NotNull ResourceResolver resourceResolver, @NotNull String configResourceCollectionParentPath, + @NotNull ConfigurationCollectionPersistData data) { + if (!config.enabled()) { + return false; + } + Resource configResourceParent = getOrCreateResource(resourceResolver, configResourceCollectionParentPath, data.getProperties()); + + // delete existing children and create new ones + deleteChildrenNotInCollection(configResourceParent, data); + for (ConfigurationPersistData item : data.getItems()) { + String path = configResourceParent.getPath() + "/" + item.getCollectionItemName(); + getOrCreateResource(resourceResolver, path, item.getProperties()); + } + + commit(resourceResolver, configResourceCollectionParentPath); + return true; + } + + @Override + public boolean deleteConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath) { + if (!config.enabled()) { + return false; + } + Resource resource = resourceResolver.getResource(configResourcePath); + if (resource != null) { + try { + log.trace("! Delete resource {}", resource.getPath()); + resourceResolver.delete(resource); + } + catch (PersistenceException ex) { + throw convertPersistenceException("Unable to delete configuration at " + configResourcePath, ex); + } + } + commit(resourceResolver, configResourcePath); + return true; + } + + private Resource getOrCreateResource(ResourceResolver resourceResolver, String path, Map properties) { + try { + Resource resource = ResourceUtil.getOrCreateResource(resourceResolver, path, (String)null, (String)null, false); + if (properties != null) { + replaceProperties(resource, properties); + } + return resource; + } + catch (PersistenceException ex) { + throw convertPersistenceException("Unable to persist configuration to " + path, ex); + } + } + + /** + * Delete children that are no longer contained in list of collection items. + * @param resource Parent resource + * @param data List of collection items + */ + private void deleteChildrenNotInCollection(Resource resource, ConfigurationCollectionPersistData data) { + ResourceResolver resourceResolver = resource.getResourceResolver(); + + Set collectionItemNames = new HashSet<>(); + for (ConfigurationPersistData item : data.getItems()) { + collectionItemNames.add(item.getCollectionItemName()); + } + + try { + for (Resource child : resource.getChildren()) { + if (!collectionItemNames.contains(child.getName())) { + log.trace("! Delete resource {}", child.getPath()); + resourceResolver.delete(child); + } + } + } + catch (PersistenceException ex) { + throw convertPersistenceException("Unable to remove children from " + resource.getPath(), ex); + } + } + + private void replaceProperties(Resource resource, Map properties) { + if (log.isTraceEnabled()) { + log.trace("! Store properties for resource {}: {}", resource.getPath(), MapUtil.traceOutput(properties)); + } + ModifiableValueMap modValueMap = resource.adaptTo(ModifiableValueMap.class); + if (modValueMap == null) { + throw new ConfigurationPersistenceAccessDeniedException("No write access: Unable to store configuration data to " + resource.getPath() + "."); + } + // remove all existing properties that are not filterd + Set propertyNamesToRemove = new HashSet<>(modValueMap.keySet()); + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesToRemove, configurationManagementSettings); + for (String propertyName : propertyNamesToRemove) { + modValueMap.remove(propertyName); + } + modValueMap.putAll(properties); + } + + private void commit(ResourceResolver resourceResolver, String relatedResourcePath) { + try { + resourceResolver.commit(); + } + catch (PersistenceException ex) { + throw convertPersistenceException("Unable to persist configuration changes to " + relatedResourcePath, ex); + } + } + + private ConfigurationPersistenceException convertPersistenceException(String message, PersistenceException ex) { + if (StringUtils.equals(ex.getCause().getClass().getName(), "javax.jcr.AccessDeniedException")) { + // detect if commit failed due to read-only access to repository + return new ConfigurationPersistenceAccessDeniedException("No write access: " + message, ex); + } + return new ConfigurationPersistenceException(message, ex); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProvider.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProvider.java new file mode 100644 index 000000000..28ccc1413 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProvider.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.caconfig.impl.metadata; + +import java.util.Collections; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentSkipListMap; + +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.jetbrains.annotations.NotNull; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.util.tracker.BundleTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Detects configuration annotation classes deployed by any bundle via OSGi extender pattern. + */ +@Component(immediate = true, service = ConfigurationMetadataProvider.class) +public class AnnotationClassConfigurationMetadataProvider implements ConfigurationMetadataProvider { + + private BundleTracker bundleTracker; + private ConcurrentSkipListMap bundleMappings = new ConcurrentSkipListMap<>(); + + private static final Logger log = LoggerFactory.getLogger(AnnotationClassConfigurationMetadataProvider.class); + + @Activate + private void activate(BundleContext bundleContext) { + ConfigClassBundleTackerCustomizer bundlerTrackerCustomizer = new ConfigClassBundleTackerCustomizer(this); + bundleTracker = new BundleTracker(bundleContext, Bundle.ACTIVE, bundlerTrackerCustomizer); + bundleTracker.open(); + } + + @Deactivate + private void deactivate() { + bundleTracker.close(); + bundleTracker = null; + } + + @Override + public @NotNull SortedSet getConfigurationNames() { + SortedSet allConfigNames = new TreeSet(); + for (BundleConfigurationMapping bundleMapping : bundleMappings.values()) { + allConfigNames.addAll(bundleMapping.getConfigurationNames()); + } + return Collections.unmodifiableSortedSet(allConfigNames); + } + + @Override + public ConfigurationMetadata getConfigurationMetadata(String configName) { + ConfigurationMapping mapping = getConfigurationMapping(configName); + if (mapping != null) { + return mapping.getConfigMetadata(); + } + else { + return null; + } + } + + /** + * Get configuration mapping for given config name. + * On the way check for config name mapping conflicts accross bundles and log a warning if found. + * Is difficult to do this beforehand due to the lazy initialization of the bundle config mappings and the + * dynamic behavior of coming and going bundles with configuration classes. + * @param configName Configuration name + * @return Configuration mapping or null if none found + */ + @SuppressWarnings("null") + ConfigurationMapping getConfigurationMapping(String configName) { + ConfigurationMapping matchingConfigMapping = null; + BundleConfigurationMapping matchingBundleMapping = null; + for (BundleConfigurationMapping bundleMapping : bundleMappings.values()) { + ConfigurationMapping configMapping = bundleMapping.getConfigurationMapping(configName); + if (configMapping != null) { + if (matchingConfigMapping == null) { + matchingConfigMapping = configMapping; + matchingBundleMapping = bundleMapping; + } + else { + // conflict in name mapping across bundles found + log.warn("Configuration name conflict: Both configuration classes {} (Bundle {}) " + + "and {} (Bundle {}) define the configuration name '{}', ignoring the latter.", + matchingConfigMapping.getConfigClass().getName(), + matchingBundleMapping.getBundle().getSymbolicName(), + configMapping.getConfigClass().getName(), + bundleMapping.getBundle().getSymbolicName(), + configName); + } + } + } + return matchingConfigMapping; + } + + void addBundeMapping(BundleConfigurationMapping bundleMapping) { + log.debug("Add bundle mapping: {}", bundleMapping); + bundleMappings.put(bundleMapping.getBundle(), bundleMapping); + } + + void removeBundleMapping(BundleConfigurationMapping bundleMapping) { + log.debug("Remove bundle mapping: {}", bundleMapping); + bundleMappings.remove(bundleMapping.getBundle()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java new file mode 100644 index 000000000..38cfb7fe7 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.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.caconfig.impl.metadata; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.annotation.Configuration; +import org.apache.sling.caconfig.annotation.Property; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; + +/** + * Helper methods for parsing metadata from configuration annotation classes. + */ +public final class AnnotationClassParser { + + private static final Pattern METHOD_NAME_MAPPING = Pattern.compile("(\\$\\$)|(\\$)|(__)|(_)"); + + private AnnotationClassParser() { + // static methods only + } + + /** + * Checks if the given class is suitable to be mapped with context-aware configuration. + * The given class has to be an annotation class, and the {@link Configuration} annotation has to be present. + * @param clazz Given class + * @return True if class is suitable for context-aware configuration + */ + public static boolean isContextAwareConfig(Class clazz) { + return clazz.isAnnotation() && clazz.isAnnotationPresent(Configuration.class); + } + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @return Configuration name + */ + public static String getConfigurationName(Class clazz) { + Configuration configAnnotation = clazz.getAnnotation(Configuration.class); + if (configAnnotation == null) { + return null; + } + return getConfigurationName(clazz, configAnnotation); + } + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @param configAnnotation Configuration metadata + * @return Configuration name + */ + private static String getConfigurationName(Class clazz, Configuration configAnnotation) { + String configName = configAnnotation.name(); + if (StringUtils.isBlank(configName)) { + configName = clazz.getName(); + } + return configName; + } + + /** + * Implements the method name mapping as defined in OSGi R6 Compendium specification, + * Chapter 112. Declarative Services Specification, Chapter 112.8.2.1. Component Property Mapping. + * @param methodName Method name + * @return Mapped property name + */ + public static String getPropertyName(String methodName) { + Matcher matcher = METHOD_NAME_MAPPING.matcher(methodName); + StringBuffer mappedName = new StringBuffer(); + while (matcher.find()) { + String replacement = ""; + if (matcher.group(1) != null) { + replacement = "\\$"; + } + if (matcher.group(2) != null) { + replacement = ""; + } + if (matcher.group(3) != null) { + replacement = "_"; + } + if (matcher.group(4) != null) { + replacement = "."; + } + matcher.appendReplacement(mappedName, replacement); + } + matcher.appendTail(mappedName); + return mappedName.toString(); + } + + /** + * Build configuration metadata by parsing the given annotation interface class and it's configuration annotations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ + public static ConfigurationMetadata buildConfigurationMetadata(Class clazz) { + Configuration configAnnotation = clazz.getAnnotation(Configuration.class); + if (configAnnotation == null) { + throw new IllegalArgumentException("Class has not @Configuration annotation: " + clazz.getName()); + } + + // configuration metadata and property metadata + String configName = getConfigurationName(clazz, configAnnotation); + ConfigurationMetadata configMetadata = new ConfigurationMetadata(configName, + buildConfigurationMetadata_PropertyMetadata(clazz), + configAnnotation.collection()) + .label(emptyToNull(configAnnotation.label())) + .description(emptyToNull(configAnnotation.description())) + .properties(propsArrayToMap(configAnnotation.property())); + + return configMetadata; + } + + /** + * Build configuration metadata by parsing the given annotation interface class which is used for nested configurations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ + private static ConfigurationMetadata buildConfigurationMetadata_Nested(Class clazz, String configName, boolean collection) { + return new ConfigurationMetadata(configName, + buildConfigurationMetadata_PropertyMetadata(clazz), + collection); + } + + private static Collection> buildConfigurationMetadata_PropertyMetadata(Class clazz) { + // sort properties by order number, or alternatively by label, name + SortedSet> propertyMetadataSet = new TreeSet<>(new Comparator>() { + @Override + public int compare(PropertyMetadata o1, PropertyMetadata o2) { + int compare = Integer.compare(o1.getOrder(), o2.getOrder()); + if (compare == 0) { + String sort1 = StringUtils.defaultString(o1.getLabel(), o1.getName()); + String sort2 = StringUtils.defaultString(o2.getLabel(), o2.getName()); + compare = sort1.compareTo(sort2); + } + return compare; + } + }); + Method[] propertyMethods = clazz.getDeclaredMethods(); + for (Method propertyMethod : propertyMethods) { + PropertyMetadata propertyMetadata = buildPropertyMetadata(propertyMethod, propertyMethod.getReturnType()); + propertyMetadataSet.add(propertyMetadata); + } + return propertyMetadataSet; + } + + @SuppressWarnings("unchecked") + private static PropertyMetadata buildPropertyMetadata(Method propertyMethod, Class type) { + String propertyName = getPropertyName(propertyMethod.getName()); + + PropertyMetadata propertyMetadata; + if (type.isArray() && type.getComponentType().isAnnotation()) { + ConfigurationMetadata nestedConfigMetadata = buildConfigurationMetadata_Nested(type.getComponentType(), propertyName, true); + propertyMetadata = new PropertyMetadata<>(propertyName, ConfigurationMetadata[].class) + .configurationMetadata(nestedConfigMetadata); + } + else if (type.isAnnotation()) { + ConfigurationMetadata nestedConfigMetadata = buildConfigurationMetadata_Nested(type, propertyName, false); + propertyMetadata = new PropertyMetadata<>(propertyName, ConfigurationMetadata.class) + .configurationMetadata(nestedConfigMetadata); + } + else { + propertyMetadata = new PropertyMetadata<>(propertyName, type) + .defaultValue((T)propertyMethod.getDefaultValue()); + } + + Property propertyAnnotation = propertyMethod.getAnnotation(Property.class); + if (propertyAnnotation != null) { + propertyMetadata.label(emptyToNull(propertyAnnotation.label())) + .description(emptyToNull(propertyAnnotation.description())) + .properties(propsArrayToMap(propertyAnnotation.property())) + .order(propertyAnnotation.order()); + } + else { + Map emptyMap = Collections.emptyMap(); + propertyMetadata.properties(emptyMap); + } + + return (PropertyMetadata)propertyMetadata; + } + + private static String emptyToNull(String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + else { + return value; + } + } + + private static Map propsArrayToMap(String[] properties) { + Map props = new HashMap<>(); + for (String property : properties) { + int index = StringUtils.indexOf(property, "="); + if (index >= 0) { + String key = property.substring(0, index); + String value = property.substring(index + 1); + props.put(key, value); + } + } + return props; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/BundleConfigurationMapping.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/BundleConfigurationMapping.java new file mode 100644 index 000000000..20480290a --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/BundleConfigurationMapping.java @@ -0,0 +1,132 @@ +/* + * 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.caconfig.impl.metadata; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.StringUtils; +import org.osgi.framework.Bundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Contains metadata of all configuration classes that where declared via the bundle header for a bundle. + * The configuration class metadata is not parsed on initialization, but lazily on first access. + */ +class BundleConfigurationMapping { + + private final Bundle bundle; + private final String classNamesList; + private final AtomicReference> configMappingsRef = new AtomicReference<>(null); + + private static final Logger log = LoggerFactory.getLogger(BundleConfigurationMapping.class); + + public BundleConfigurationMapping(Bundle bundle, String classNamesList) { + this.bundle = bundle; + this.classNamesList = classNamesList; + } + + public Bundle getBundle() { + return bundle; + } + + /** + * Thread-safe lazy initialization of configuration mappings. + * @return Configuration mappings + */ + private Map getConfigMappings() { + Map configMappings = configMappingsRef.get(); + if (configMappings == null) { + configMappings = initializeConfigMappings(); + if (configMappingsRef.compareAndSet(null, configMappings)) { + return configMappings; + } + else { + return configMappingsRef.get(); + } + } + else { + return configMappings; + } + } + + /** + * Parse all annotation classes + * @return + */ + private Map initializeConfigMappings() { + Map configMappings = new HashMap<>(); + + String[] classNames = StringUtils.split(StringUtils.deleteWhitespace(classNamesList), ","); + for (String className : classNames) { + try { + Class configClass = bundle.loadClass(className); + if (AnnotationClassParser.isContextAwareConfig(configClass)) { + log.debug("{}: Add configuration class {}", bundle.getSymbolicName(), className); + + ConfigurationMapping configMapping = new ConfigurationMapping(configClass); + if (!hasMappingConflict(configMapping, configMappings)) { + configMappings.put(configMapping.getConfigName(), configMapping); + } + } + else { + log.warn("Ignoring invalid configuration class: {}", className); + } + } + catch (ClassNotFoundException ex) { + log.warn("Unable to load class: " + className, ex); + } + } + + return configMappings; + } + + private boolean hasMappingConflict(ConfigurationMapping newConfigMapping, + Map configMappings) { + ConfigurationMapping conflictingConfigMapping = configMappings.get(newConfigMapping.getConfigName()); + if (conflictingConfigMapping != null) { + log.warn("Configuration name conflict in bundle {}: Both configuration classes {} and {} define the configuration name '{}', ignoring the latter.", + bundle.getSymbolicName(), + conflictingConfigMapping.getConfigClass().getName(), + newConfigMapping.getConfigClass().getName(), + newConfigMapping.getConfigName()); + return true; + } + else { + return false; + } + } + + public Set getConfigurationNames() { + return getConfigMappings().keySet(); + } + + public ConfigurationMapping getConfigurationMapping(String configName) { + return getConfigMappings().get(configName); + } + + @Override + public String toString() { + return "Classes from bundle '" + bundle.getSymbolicName() + "': " + classNamesList; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigClassBundleTackerCustomizer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigClassBundleTackerCustomizer.java new file mode 100644 index 000000000..fd1063810 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigClassBundleTackerCustomizer.java @@ -0,0 +1,62 @@ +/* + * 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.caconfig.impl.metadata; + +import static org.apache.sling.caconfig.impl.ConfigurationNameConstants.CONFIGURATION_CLASSES_HEADER; + +import java.util.Dictionary; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleEvent; +import org.osgi.util.tracker.BundleTrackerCustomizer; + +/** + * Detects deployed bundles with configuration class header. + */ +class ConfigClassBundleTackerCustomizer implements BundleTrackerCustomizer { + + private final AnnotationClassConfigurationMetadataProvider metadataProvider; + + public ConfigClassBundleTackerCustomizer(AnnotationClassConfigurationMetadataProvider metadataProvider) { + this.metadataProvider = metadataProvider; + } + + @Override + public BundleConfigurationMapping addingBundle(Bundle bundle, BundleEvent event) { + Dictionary headers = bundle.getHeaders(); + String classeNamesList = headers.get(CONFIGURATION_CLASSES_HEADER); + if (classeNamesList == null) { + return null; + } + BundleConfigurationMapping bundleMapping = new BundleConfigurationMapping(bundle, classeNamesList); + metadataProvider.addBundeMapping(bundleMapping); + return bundleMapping; + } + + @Override + public void modifiedBundle(Bundle bundle, BundleEvent event, BundleConfigurationMapping bundleMapping) { + // nothing to do + } + + @Override + public void removedBundle(Bundle bundle, BundleEvent event, BundleConfigurationMapping bundleMapping) { + metadataProvider.removeBundleMapping(bundleMapping); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMapping.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMapping.java new file mode 100644 index 000000000..13e856636 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMapping.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.caconfig.impl.metadata; + +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; + +/** + * Contains mapping of configuration class to metadata parsed from it's fields and annotations. + */ +class ConfigurationMapping { + + private final Class configClass; + private final ConfigurationMetadata configMetadata; + + public ConfigurationMapping(Class configClass) { + this.configClass = configClass; + this.configMetadata = AnnotationClassParser.buildConfigurationMetadata(configClass); + } + + public Class getConfigClass() { + return configClass; + } + + public String getConfigName() { + return configMetadata.getName(); + } + + public ConfigurationMetadata getConfigMetadata() { + return configMetadata; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImpl.java new file mode 100644 index 000000000..dd76471aa --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImpl.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl.metadata; + +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Detects all {@link ConfigurationMetadataProvider} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationMetadataProviderMultiplexer.class, +reference={ + @Reference(name="configurationMetadataProvider", service=ConfigurationMetadataProvider.class, + bind="bindConfigurationMetadataProvider", unbind="unbindConfigurationMetadataProvider", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationMetadataProviderMultiplexerImpl implements ConfigurationMetadataProviderMultiplexer { + + private RankedServices items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationMetadataProvider(ConfigurationMetadataProvider configurationMetadataProvider, Map props) { + items.bind(configurationMetadataProvider, props); + } + + protected void unbindConfigurationMetadataProvider(ConfigurationMetadataProvider configurationMetadataProvider, Map props) { + items.unbind(configurationMetadataProvider, props); + } + + /** + * Merge configuration names from all providers. + */ + @Override + public @NotNull SortedSet getConfigurationNames() { + SortedSet configNames = new TreeSet<>(); + for (ConfigurationMetadataProvider item : items) { + configNames.addAll(item.getConfigurationNames()); + } + return configNames; + } + + /** + * Get configuration metadata from first provider (ranking priority) that has an answer. + */ + @Override + public ConfigurationMetadata getConfigurationMetadata(String configName) { + for (ConfigurationMetadataProvider item : items) { + ConfigurationMetadata configMetadata = item.getConfigurationMetadata(configName); + if (configMetadata != null) { + return configMetadata; + } + } + return null; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImpl.java new file mode 100644 index 000000000..c788e5838 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImpl.java @@ -0,0 +1,158 @@ +/* + * 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.caconfig.impl.override; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.SyntheticResource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.impl.ConfigurationResourceWrapper; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.MapUtil; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.apache.sling.commons.osgi.RankedServices.ChangeListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Detects all {@link ConfigurationOverrideProvider} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationOverrideMultiplexer.class, +reference={ + @Reference(name="configurationOverrideProvider", service=ConfigurationOverrideProvider.class, + bind="bindConfigurationOverrideProvider", unbind="unbindConfigurationOverrideProvider", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationOverrideMultiplexerImpl implements ConfigurationOverrideMultiplexer, ChangeListener { + + private RankedServices items = new RankedServices<>(Order.DESCENDING, this); + private volatile Collection allOverrides = Collections.emptyList(); + + private static final Logger log = LoggerFactory.getLogger(ConfigurationOverrideMultiplexerImpl.class); + + protected void bindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map props) { + items.bind(item, props); + } + + protected void unbindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map props) { + items.unbind(item, props); + } + + @Override + public boolean isAllOverridden(@NotNull String contextPath, @NotNull String configName) { + for (OverrideItem override : allOverrides) { + if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) { + if (override.isAllProperties()) { + return true; + } + } + } + return false; + } + + @Override + public Map overrideProperties(@NotNull String contextPath, @NotNull String configName, @NotNull Map properties) { + if (allOverrides.size() == 0) { + return null; + } + boolean anyMatch = false; + Map overrideProperties = new HashMap<>(properties); + + for (OverrideItem override : allOverrides) { + if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) { + if (override.isAllProperties()) { + overrideProperties.clear(); + } + overrideProperties.putAll(override.getProperties()); + anyMatch = true; + } + } + + if (anyMatch) { + return overrideProperties; + } + else { + return null; + } + } + + @Override + public Resource overrideProperties(@NotNull String contextPath, @NotNull String configName, @Nullable Resource configResource) { + if (configResource == null) { + return null; + } + return overrideProperties(contextPath, configName, configResource, configResource.getResourceResolver()); + } + + @Override + public Resource overrideProperties(@NotNull String contextPath, @NotNull String configName, @Nullable Resource configResource, @NotNull ResourceResolver resourceResolver) { + Map overrideProperties = overrideProperties(contextPath, configName, configResource != null ? configResource.getValueMap() : ValueMap.EMPTY); + if (overrideProperties == null) { + return configResource; + } + Resource configResourceToUse = configResource; + if (configResourceToUse == null) { + // build synthetic resource if override properties exist + configResourceToUse = new SyntheticResource(resourceResolver, (String)null, (String)null); + } + if (log.isTraceEnabled()) { + log.trace("! Override properties for context path " + contextPath + ", name '" + configName + "', " + + (configResource != null ? "config path " + configResource.getPath() : "no config path") + ": " + + (configResource != null ? MapUtil.traceOutput(configResource.getValueMap()) : "empty") + " -> " + MapUtil.traceOutput(overrideProperties)); + } + return new ConfigurationResourceWrapper(configResourceToUse, new ValueMapDecorator(overrideProperties)); + } + + /** + * If a provider is added or removed parse and collect all overrides again (to ensure correct overall order is preserved). + */ + @Override + public void changed() { + List overrides = new ArrayList<>(); + for (ConfigurationOverrideProvider item : items) { + Collection itemOverrides = OverrideStringParser.parse(item.getOverrideStrings()); + if (log.isDebugEnabled() && !itemOverrides.isEmpty()) { + log.debug("Override items from " + item.getClass().getName() + ":\n" + StringUtils.join(itemOverrides, "\n")); + } + overrides.addAll(itemOverrides); + } + allOverrides = overrides; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OsgiConfigurationOverrideProvider.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OsgiConfigurationOverrideProvider.java new file mode 100644 index 000000000..eb18d11f8 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OsgiConfigurationOverrideProvider.java @@ -0,0 +1,85 @@ +/* + * 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.caconfig.impl.override; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Provides parameter override map from OSGi factory configuration. + */ +@Component(service = ConfigurationOverrideProvider.class, immediate = true) +@Designate(ocd = OsgiConfigurationOverrideProvider.Config.class, factory = true) +public final class OsgiConfigurationOverrideProvider implements ConfigurationOverrideProvider { + + @ObjectClassDefinition(name = "Apache Sling Context-Aware Configuration Override Provider: OSGi configuration", + description = "Allows to override configuration property values from OSGi configurations.") + public static @interface Config { + + @AttributeDefinition(name = "Description", + description = "This description is used for display in the web console.") + String description(); + + @AttributeDefinition(name = "Overrides", + description = "Override strings - examples:\n" + + "{configName}/{propertyName}={propertyJsonValue}\n" + + "{configName}={propertyJsonObject}\n" + + "[{contextPath}]{configName}/{propertyName}={propertyJsonValue}\n" + + "[{contextPath}]{configName}={propertyJsonObject}") + String[] overrides(); + + @AttributeDefinition(name = "Enabled", + description = "Enable this override provider.") + boolean enabled() default false; + + @AttributeDefinition(name = "Service Ranking", + description = "Priority of configuration override providers (higher = higher priority).") + int service_ranking() default 100; + + String webconsole_configurationFactory_nameHint() default "{description}, enabled={enabled}"; + + } + + private Collection overrideStrings; + + @Activate + void activate(Config config) { + List overrides = new ArrayList<>(); + if (config.enabled()) { + overrides.addAll(Arrays.asList(config.overrides())); + } + this.overrideStrings = overrides; + } + + @Override + public @NotNull Collection getOverrideStrings() { + return overrideStrings; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OverrideItem.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OverrideItem.java new file mode 100644 index 000000000..6ff8c31b8 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OverrideItem.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl.override; + +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * Holds override information provided by override providers. + */ +class OverrideItem { + + private final String path; + private final Pattern pathPattern; + private final String configName; + private final Map properties; + private final boolean allProperties; + + public OverrideItem(String path, String configName, + Map properties, boolean allProperties) { + this.path = path; + this.pathPattern = toPathPattern(path); + this.configName = configName; + this.properties = properties; + this.allProperties = allProperties; + } + + private static Pattern toPathPattern(String path) { + if (StringUtils.isBlank(path)) { + return null; + } + return Pattern.compile("^" + Pattern.quote(StringUtils.trim(path)) + "(/.*)?$"); + } + + /** + * @return Path (incl. subtree) to match - or null for all paths + */ + public String getPath() { + return path; + } + + /** + * @param path Path to check + * @return true if path matches + */ + public boolean matchesPath(String path) { + if (pathPattern == null) { + return true; + } + else { + return pathPattern.matcher(path).matches(); + } + } + + /** + * @return Configuration name (may contain a relative hierarchy with "/") + */ + public String getConfigName() { + return configName; + } + + /** + * @return Properties map + */ + public Map getProperties() { + return properties; + } + + /** + * @return If true, all properties for this config name should be replaced + * with those from the map. Otherwise they are merged. + */ + public boolean isAllProperties() { + return allProperties; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OverrideStringParser.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OverrideStringParser.java new file mode 100644 index 000000000..57dc0b9e3 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/OverrideStringParser.java @@ -0,0 +1,288 @@ +/* + * 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.caconfig.impl.override; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonException; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonReaderFactory; +import javax.json.JsonString; +import javax.json.JsonValue; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parses override configuration strings like these: + *
    + *
  • {configName}/{propertyName}={propertyJsonValue}
  • + *
  • {configName}={propertyJsonObject}
  • + *
  • [{contextPath}]{configName}/{propertyName}={propertyJsonValue}
  • + *
  • [{contextPath}]{configName}={propertyJsonObject}
  • + *
+ */ +class OverrideStringParser { + + private static final Logger log = LoggerFactory.getLogger(OverrideStringParser.class); + + private static final Pattern OVERRIDE_PATTERN = Pattern.compile("^(\\[([^\\[\\]=]+)\\])?([^\\[\\]=]+)=(.*)$"); + + private static final JsonReaderFactory JSON_READER_FACTORY = Json.createReaderFactory(Collections.emptyMap()); + + private OverrideStringParser() { + // static method sonly + } + + /** + * Parses a list of override strings from a override provider. + * @param overrideStrings Override strings + * @return Override objects + */ + public static Collection parse(Collection overrideStrings) { + List result = new ArrayList<>(); + + for (String overrideString : overrideStrings) { + + // check if override generic pattern is matched + Matcher matcher = OVERRIDE_PATTERN.matcher(StringUtils.defaultString(overrideString)); + if (!matcher.matches()) { + log.warn("Ignore config override string - invalid syntax: {}", overrideString); + continue; + } + + // get single parts + String path = StringUtils.trim(matcher.group(2)); + String configName = StringUtils.trim(matcher.group(3)); + String value = StringUtils.trim(StringUtils.defaultString(matcher.group(4))); + + OverrideItem item; + try { + // check if value is JSON = defines whole parameter map for a config name + JsonObject json = toJson(value); + if (json != null) { + item = new OverrideItem(path, configName, toMap(json), true); + } + else { + // otherwise it defines a key/value pair in a single line + String propertyName = StringUtils.substringAfterLast(configName, "/"); + if (StringUtils.isEmpty(propertyName)) { + log.warn("Ignore config override string - missing property name: {}", overrideString); + continue; + } + configName = StringUtils.substringBeforeLast(configName, "/"); + Map props = new HashMap<>(); + props.put(propertyName, convertJsonValue(value)); + item = new OverrideItem(path, configName, props, false); + } + } + catch (JsonException ex) { + log.warn("Ignore config override string - invalid JSON syntax ({}): {}", ex.getMessage(), overrideString); + continue; + } + + // validate item + if (!isValid(item, overrideString)) { + continue; + } + + // if item does not contain a full property set try to merge with existing one + if (!item.isAllProperties()) { + boolean foundMatchingItem = false; + for (OverrideItem existingItem : result) { + if (!existingItem.isAllProperties() + && StringUtils.equals(item.getPath(), existingItem.getPath()) + && StringUtils.equals(item.getConfigName(), existingItem.getConfigName())) { + existingItem.getProperties().putAll(item.getProperties()); + foundMatchingItem = true; + break; + } + } + if (foundMatchingItem) { + continue; + } + } + + // add item to result + result.add(item); + } + + return result; + } + + /** + * Try to convert value to JSON object + * @param value Value string + * @return JSON object or null if the string does not start with "{" + * @throws JSONException when JSON parsing failed + */ + private static JsonObject toJson(String value) { + if (!StringUtils.startsWith(value, "{")) { + return null; + } + try (Reader reader = new StringReader(value); + JsonReader jsonReader = JSON_READER_FACTORY.createReader(reader)) { + return jsonReader.readObject(); + } + catch (IOException ex) { + return null; + } + } + + /** + * Convert JSON object to map. + * @param json JSON object + * @return Map (keys/values are not validated) + */ + private static Map toMap(JsonObject json) { + Map props = new HashMap<>(); + Iterator keys = json.keySet().iterator(); + while (keys.hasNext()) { + String key = keys.next(); + props.put(key, convertJsonValue(json.get(key))); + } + return props; + } + + /** + * Convert single JSON-conformant value object + * @param jsonValue JSON value + * @return Object + * @throws JSONException If JSON-parsing of value failed + */ + private static Object convertJsonValue(String jsonValue) { + String jsonString = "{\"value\":" + jsonValue + "}"; + JsonObject json = toJson(jsonString); + return convertJsonValue(json.get("value")); + } + + private static Object convertJsonValue(JsonValue jsonValue) { + switch (jsonValue.getValueType()) { + case STRING: + return ((JsonString)jsonValue).getString(); + case NUMBER: + JsonNumber number = (JsonNumber)jsonValue; + if (number.isIntegral()) { + return number.longValue(); + } + else { + return number.doubleValue(); + } + case TRUE: + return true; + case FALSE: + return false; + case NULL: + return null; + case ARRAY: + return convertJsonArray((JsonArray)jsonValue); + default: + throw new RuntimeException("Unexpected JSON value type: " + jsonValue.getValueType() + ": " + jsonValue); + } + } + + private static Object convertJsonArray(JsonArray jsonArray) { + if (jsonArray.size() > 0) { + Object firstValue = convertJsonValue(jsonArray.get(0)); + if (firstValue != null) { + Class firstType = firstValue.getClass(); + Object convertedArray = Array.newInstance(firstType, jsonArray.size()); + for (int i=0; i entry : item.getProperties().entrySet()) { + String propertyName = entry.getKey(); + if (StringUtils.isEmpty(propertyName) || StringUtils.contains(propertyName, "/")) { + log.warn("Ignore config override string - invalid property name ({}): {}", propertyName, overrideString); + return false; + } + Object value = entry.getValue(); + if (value == null || !isSupportedType(value)) { + log.warn("Ignore config override string - invalid property value ({} - {}): {}", value, value != null ? value.getClass().getName() : "", overrideString); + return false; + } + } + return true; + } + + /** + * Validate if the given object is not null, and the type is supported for configuration values. + * @param value Value + * @return true if valid + */ + private static boolean isSupportedType(Object value) { + if (value == null) { + return false; + } + Class clazz = value.getClass(); + if (clazz.isArray()) { + clazz = clazz.getComponentType(); + } + for (Class type : PropertyMetadata.SUPPORTED_TYPES) { + if (type.equals(clazz )) { + return true; + } + if (type.isPrimitive() && ClassUtils.primitiveToWrapper(type).equals(clazz)) { + return true; + } + } + return false; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/SystemPropertyConfigurationOverrideProvider.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/SystemPropertyConfigurationOverrideProvider.java new file mode 100644 index 000000000..54301ecc4 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/impl/override/SystemPropertyConfigurationOverrideProvider.java @@ -0,0 +1,90 @@ +/* + * 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.caconfig.impl.override; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Provides parameter override map from system properties. + */ +@Component(service = ConfigurationOverrideProvider.class, immediate = true) +@Designate(ocd = SystemPropertyConfigurationOverrideProvider.Config.class) +public final class SystemPropertyConfigurationOverrideProvider implements ConfigurationOverrideProvider { + + /** + * Prefix for override system property + */ + public static final String SYSTEM_PROPERTY_PREFIX = "sling.caconfig.override."; + + @ObjectClassDefinition(name = "Apache Sling Context-Aware Configuration Override Provider: System Properties", + description = "Allows to override configuration property values from system environment properties.") + public static @interface Config { + + @AttributeDefinition(name = "Enabled", + description = "Enable this override provider.") + boolean enabled() default false; + + @AttributeDefinition(name = "Service Ranking", + description = "Priority of configuration override providers (higher = higher priority).") + int service_ranking() default 200; + + } + + private Collection overrideStrings; + + @Activate + void activate(Config config) { + List overrides = new ArrayList<>(); + + if (config.enabled()) { + Properties properties = System.getProperties(); + Enumeration keys = properties.keys(); + while (keys.hasMoreElements()) { + Object keyObject = keys.nextElement(); + if (keyObject instanceof String) { + String key = (String) keyObject; + if (StringUtils.startsWith(key, SYSTEM_PROPERTY_PREFIX)) { + overrides.add(StringUtils.substringAfter(key, SYSTEM_PROPERTY_PREFIX) + "=" + System.getProperty(key)); + } + } + } + } + + this.overrideStrings = overrides; + } + + @Override + public @NotNull Collection getOverrideStrings() { + return overrideStrings; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationCollectionData.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationCollectionData.java new file mode 100644 index 000000000..331d00f01 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationCollectionData.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management; + +import java.util.Collection; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Provides access to the configuration collection data and metadata for a given context path. + */ +@ProviderType +public interface ConfigurationCollectionData { + + /** + * Get configuration name. + * @return Configuration name + */ + @NotNull String getConfigName(); + + /** + * @return Configuration collection items + */ + @NotNull Collection getItems(); + + /** + * @return Path of the configuration collection resource parent path or null if it cannot be determined. + */ + @Nullable String getResourcePath(); + + /** + * @return Properties for the configuration collection itself. Does not contain configuration data, but control data e.g. for enabling collection inheritance. + */ + @NotNull Map getProperties(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationData.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationData.java new file mode 100644 index 000000000..75ff5c008 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationData.java @@ -0,0 +1,88 @@ +/* + * 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.caconfig.management; + +import java.util.Set; + +import org.apache.sling.api.resource.ValueMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Provides access to the configuration data and metadata for a given context path. + */ +@ProviderType +public interface ConfigurationData { + + /** + * Get configuration name. + * @return Configuration name + */ + @NotNull String getConfigName(); + + /** + * In case of configuration resource collection, this returns the collection item resource name. + * @return Item resource name or null if it is a singleton resource. + */ + @Nullable String getCollectionItemName(); + + /** + * @return Path of the configuration resource or null if it cannot be determined. + */ + @Nullable String getResourcePath(); + + /** + * List of effective property names defined in configuration metadata or values are defined for. + * @return Property names + */ + @NotNull Set getPropertyNames(); + + /** + * Configuration values stored for the given context path. No inherited values. No default values. + * The properties of the resource identified by {@link #getResourcePath()} are returned. + * If this resources does not exist, the map is empty. + * @return Values + */ + @NotNull ValueMap getValues(); + + /** + * Configuration values stored for the given context path merged with inherited values and default values. + * @return Values + */ + @NotNull ValueMap getEffectiveValues(); + + /** + * Get detailed metadata information about the property value. + * @param propertyName Property name + * @return Value information. Null if neither property metadata nor an existing value exists. + */ + @Nullable ValueInfo getValueInfo(String propertyName); + + /** + * @return true if the whole configuration is inherited. + */ + boolean isInherited(); + + /** + * @return true if the whole configuration is overridden by an configuration override provider. + */ + boolean isOverridden(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationManagementSettings.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationManagementSettings.java new file mode 100644 index 000000000..0b865cfa4 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationManagementSettings.java @@ -0,0 +1,44 @@ +/* + * 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.caconfig.management; + +import java.util.Collection; +import java.util.Set; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * Settings for reading and writing configurations. + */ +@ProviderType +public interface ConfigurationManagementSettings { + + /** + * Detects property names that should be ignored/filtered out when reading or writing configuration data properties. + * @param propertyNames Existing property names to evaluate. + * @return Property names that should be ignored/filtered out from the given set of property names. + */ + Set getIgnoredPropertyNames(Set propertyNames); + + /** + * @return Config collection parent properties resource names. + */ + Collection getConfigCollectionPropertiesResourceNames(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationManager.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationManager.java new file mode 100644 index 000000000..518b2c511 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationManager.java @@ -0,0 +1,111 @@ +/* + * 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.caconfig.management; + +import java.util.SortedSet; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Manages reading and writing configurations e.g. for Editor GUIs. + * It manages only configurations handled by {@link org.apache.sling.caconfig.ConfigurationResolver}, + * no low-level configuration resources managed by {@link org.apache.sling.caconfig.resource.ConfigurationResourceResolver}. + */ +@ProviderType +public interface ConfigurationManager { + + /** + * Get configuration data for the given context resource and configuration name. + * @param resource Context resource + * @param configName Configuration name + * @return Configuration data. Is null when no configuration resource found and no configuration metadata exists. + */ + @Nullable ConfigurationData getConfiguration(@NotNull Resource resource, @NotNull String configName); + + /** + * Get configuration data collection for the given context resource and configuration name. + * @param resource Context resource + * @param configName Configuration name + * @return Configuration data collection. Is empty when no configuration resources found. + */ + @NotNull ConfigurationCollectionData getConfigurationCollection(@NotNull Resource resource, @NotNull String configName); + + /** + * Write configuration data to repository using the inner-most context path as reference. + * @param resource Context resource + * @param configName Configuration name + * @param data Configuration data to be stored. All existing properties are erased and replaced with the new ones. + */ + void persistConfiguration(@NotNull Resource resource, @NotNull String configName, + @NotNull ConfigurationPersistData data); + + /** + * Write configuration data collection using the inner-most context path as reference. + * @param resource Context resource + * @param configName Configuration name + * @param data Configuration collection data to be stored. All existing collection entries on this context path level are erased and replaced with the new ones. + */ + void persistConfigurationCollection(@NotNull Resource resource, @NotNull String configName, + @NotNull ConfigurationCollectionPersistData data); + + /** + * Creates a new empty configuration data item for a configuration data collection for the given configuration name. + * @param resource Context resource + * @param configName Configuration name + * @return Configuration data. Is null when no configuration metadata exists. + */ + @Nullable ConfigurationData newCollectionItem(@NotNull Resource resource, @NotNull String configName); + + /** + * Delete configuration or configuration collection data from repository using the inner-most context path as reference. + * @param resource Context resource + * @param configName Configuration name + */ + void deleteConfiguration(@NotNull Resource resource, @NotNull String configName); + + /** + * Get all configuration names. + * The results of all configuration metadata provider implementations are merged. + * @return Configuration names + */ + @NotNull SortedSet getConfigurationNames(); + + /** + * Get configuration metadata from any configuration metadata provider. + * @param configName Configuration name + * @return Configuration metadata or null if none exists for the given name. + */ + @Nullable ConfigurationMetadata getConfigurationMetadata(@NotNull String configName); + + /** + * Rewrite given resource path or configuration name according to current persistence strategies. + * @param configResourcePath Resource path or config name + * @return Rewritten resource path or config name + * @deprecated Please use {@link org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer} instead. + */ + @Deprecated + @Nullable String getPersistenceResourcePath(@NotNull String configResourcePath); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationResourceResolverConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationResourceResolverConfig.java new file mode 100644 index 000000000..fea212499 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ConfigurationResourceResolverConfig.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management; + +import java.util.Collection; + +import org.jetbrains.annotations.NotNull; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Service for accessing the ConfigurationResourceResolver configuration from other services. + */ +@ProviderType +public interface ConfigurationResourceResolverConfig { + + /** + * All bucket names starting with sling:configs and the alternative ones. + * @return Bucket names + */ + @NotNull Collection configBucketNames(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ContextPathStrategyMultiplexer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ContextPathStrategyMultiplexer.java new file mode 100644 index 000000000..c1fe8d018 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ContextPathStrategyMultiplexer.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management; + +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; + +/** + * Detects all {@link ContextPathStrategy} implementations in the container + * and consolidates their result based on service ranking. + * @deprecated Please use {@link org.apache.sling.caconfig.management.multiplexer.ContextPathStrategyMultiplexer} instead. + */ +@Deprecated +public interface ContextPathStrategyMultiplexer extends ContextPathStrategy { + + // inherits all methods from {@link ContextPathStrategy} + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ValueInfo.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ValueInfo.java new file mode 100644 index 000000000..8bf630711 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/ValueInfo.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management; + +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Provides detailed information about a given configuration value. + * @param Property type + */ +@ProviderType +public interface ValueInfo { + + /** + * Property name. + * @return Property name. + */ + @NotNull String getName(); + + /** + * Property metadata. + * @return Property metadata. Null if no metadata exists. + */ + @Nullable PropertyMetadata getPropertyMetadata(); + + /** + * Get value stored for the current context path. No inherited value. No default value. + * @return Value + */ + @Nullable T getValue(); + + /** + * Get value storedf or the current context path, or inherited from upper levels, or the default value. + * @return Value + */ + @Nullable T getEffectiveValue(); + + /** + * Get the path of the configuration resource the value is stored in. + * @return Resource path or null if no resource associated. + */ + @Nullable String getConfigSourcePath(); + + /** + * @return true if no value is defined but a default value is returned. + */ + boolean isDefault(); + + /** + * @return true if the value is not defined for the current context path but inherited from upper levels. + */ + boolean isInherited(); + + /** + * @return true if the value is overridden by an configuration override provider. + */ + boolean isOverridden(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationCollectionDataImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationCollectionDataImpl.java new file mode 100644 index 000000000..ce1f5f5f3 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationCollectionDataImpl.java @@ -0,0 +1,80 @@ +/* + * 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.caconfig.management.impl; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.caconfig.management.ConfigurationCollectionData; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationCollectionDataImpl implements ConfigurationCollectionData { + + private final String configName; + private final Collection items; + private final String resourcePath; + private final Map properties; + private final ConfigurationManagementSettings configurationManagementSettings; + private Map filteredPropertiesCache; + + public ConfigurationCollectionDataImpl(String configName, Collection items, + String resourcePath, Map properties, + ConfigurationManagementSettings configurationManagementSettings) { + this.configName = configName; + this.items = items; + this.resourcePath = resourcePath; + this.properties = properties; + this.configurationManagementSettings = configurationManagementSettings; + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public @NotNull Collection getItems() { + return items; + } + + @Override + public String getResourcePath() { + return resourcePath; + } + + @Override + public @NotNull Map getProperties() { + if (filteredPropertiesCache == null) { + filteredPropertiesCache = new HashMap<>(); + if (properties != null) { + filteredPropertiesCache.putAll(properties); + PropertiesFilterUtil.removeIgnoredProperties(filteredPropertiesCache, configurationManagementSettings); + } + else { + filteredPropertiesCache.put(ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT, true); + } + } + return filteredPropertiesCache; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java new file mode 100644 index 000000000..6d43dcf00 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java @@ -0,0 +1,281 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override + public @NotNull ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + Map props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new ValueMapDecorator(props); + } + return effectiveValuesCache; + } + + private void resolveNestedConfigs(Map props) { + if (configMetadata == null) { + return; + } + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + String relatedConfigPath = resolvedConfigurationResource != null ? resolvedConfigurationResource.getPath() : null; + String nestedConfigName; + if (configResourceCollection) { + String collectionItemName = StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + + "/" + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath) + + "/" + nestedConfigMetadata.getName(); + } + else { + nestedConfigName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(ConfigurationMetadata.class)) { + ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } + else if (propertyMetadata.getType().equals(ConfigurationMetadata[].class)) { + Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new ConfigurationData[configDatas.size()])); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationManagementSettingsImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationManagementSettingsImpl.java new file mode 100644 index 000000000..3124d85cd --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationManagementSettingsImpl.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl; + +import static org.apache.sling.api.resource.ResourceResolver.PROPERTY_RESOURCE_TYPE; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service = ConfigurationManagementSettings.class) +@Designate(ocd=ConfigurationManagementSettingsImpl.Config.class) +public class ConfigurationManagementSettingsImpl implements ConfigurationManagementSettings { + + @ObjectClassDefinition(name="Apache Sling Context-Aware Configuration Management Settings", + description="Management settings for reading and writing configurations.") + static @interface Config { + + @AttributeDefinition(name="Ignore Property Regex", + description = "List of regular expressions with property names that should be ignored when reading or writing configuration data properties.") + String[] ignorePropertyNameRegex() default { + "^jcr:.+$", + "^" + PROPERTY_RESOURCE_TYPE + "$" + }; + + @AttributeDefinition(name="Config collection parent properties resource names", + description = "Names of resource to try to look up configuration collection properties in. If list is empty only the collection parent resource is checked." + + " If the list is not empty than only those listed resources are used for look up. If you want to include the collection parent resource you can use a dot for the value.") + String[] configCollectionPropertiesResourceNames(); + + } + + private static final Logger log = LoggerFactory.getLogger(ConfigurationManagementSettingsImpl.class); + + private Pattern[] ignorePropertyNameRegex; + private Collection configCollectionPropertiesResourceNames; + + + @Activate + private void activate(Config config) { + List patterns = new ArrayList<>(); + for (String patternString : config.ignorePropertyNameRegex()) { + try { + patterns.add(Pattern.compile(patternString)); + } + catch (PatternSyntaxException ex) { + log.warn("Ignoring invalid regex pattern: " + patternString, ex); + } + } + + this.ignorePropertyNameRegex = patterns.toArray(new Pattern[patterns.size()]); + + String[] configCollectionPropertiesResourceNames = config.configCollectionPropertiesResourceNames(); + if (configCollectionPropertiesResourceNames == null || configCollectionPropertiesResourceNames.length == 0) { + configCollectionPropertiesResourceNames = new String[] { "." }; + } + this.configCollectionPropertiesResourceNames = Collections.unmodifiableList(Arrays.asList(configCollectionPropertiesResourceNames)); + } + + @Override + public Set getIgnoredPropertyNames(Set propertyNames) { + Set ignoredPropertyNames = new HashSet<>(); + for (String propertyName : propertyNames) { + for (Pattern ignorePattern : ignorePropertyNameRegex) { + if (ignorePattern.matcher(propertyName).matches()) { + ignoredPropertyNames.add(propertyName); + break; + } + } + } + return ignoredPropertyNames; + } + + @Override + public Collection getConfigCollectionPropertiesResourceNames() { + return configCollectionPropertiesResourceNames; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImpl.java new file mode 100644 index 000000000..fc46b7c5b --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImpl.java @@ -0,0 +1,425 @@ +/* + * 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.caconfig.management.impl; + +import static org.apache.sling.caconfig.impl.ConfigurationNameConstants.CONFIGS_BUCKET_NAME; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.iterators.ListIteratorWrapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.caconfig.management.ConfigurationCollectionData; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ConfigurationResourceResolverConfig; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationInheritanceStrategyMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationResourceResolvingStrategyMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil; +import org.apache.sling.caconfig.resource.impl.util.MapUtil; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceException; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service = ConfigurationManager.class) +public class ConfigurationManagerImpl implements ConfigurationManager { + + @Reference + private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy; + @Reference + private ConfigurationMetadataProviderMultiplexer configurationMetadataProvider; + @Reference + private ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy; + @Reference + private ConfigurationInheritanceStrategyMultiplexer configurationInheritanceStrategy; + @Reference + private ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + @Reference + private ConfigurationResourceResolverConfig configurationResourceResolverConfig; + @Reference + private ConfigurationManagementSettings configurationManagementSettings; + + private static final Logger log = LoggerFactory.getLogger(ConfigurationManagerImpl.class); + + @SuppressWarnings("unchecked") + @Override + public ConfigurationData getConfiguration(@NotNull Resource resource, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + if (log.isDebugEnabled()) { + log.debug("Get configuration for context path {}, name '{}'", resource.getPath(), configName); + } + ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); + Resource configResource = null; + + Iterator configResourceInheritanceChain = configurationResourceResolvingStrategy + .getResourceInheritanceChain(resource, configurationResourceResolverConfig.configBucketNames(), configName); + + if (configResourceInheritanceChain != null) { + ResettableIterator resettableConfigResourceInheritanceChain = new ListIteratorWrapper(configResourceInheritanceChain); + configResource = applyPersistenceAndInheritance(resource.getPath(), configName, resettableConfigResourceInheritanceChain, false, resource.getResourceResolver()); + if (configResource != null) { + // get writeback resource for "reverse inheritance detection" + Resource writebackConfigResource = null; + + String writebackConfigResourcePath = null; + for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + writebackConfigResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, configBucketName, configName); + if (writebackConfigResourcePath != null) { + writebackConfigResource = resource.getResourceResolver().getResource(writebackConfigResourcePath); + if (writebackConfigResource != null) { + writebackConfigResource = configurationPersistenceStrategy.getResource(writebackConfigResource); + break; + } + } + } + + if (log.isTraceEnabled()) { + log.trace("+ Found config resource for context path " + resource.getPath() + ": " + configResource.getPath() + " " + + MapUtil.traceOutput(configResource.getValueMap()) + ", " + + "writeback config resource: " + writebackConfigResourcePath); + } + resettableConfigResourceInheritanceChain.reset(); + return new ConfigurationDataImpl(configMetadata, configResource, writebackConfigResource, + applyPersistence(resettableConfigResourceInheritanceChain, false), + resource, configName, this, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, false, null); + } + } + if (configMetadata != null) { + // if no config resource found still check for overrides + configResource = configurationOverrideMultiplexer.overrideProperties(resource.getPath(), configName, (Resource)null, resource.getResourceResolver()); + if (configResource != null) { + return new ConfigurationDataImpl(configMetadata, configResource, null, null, + resource, configName, this, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, false, null); + } + + // if no config resource found but config metadata exist return empty config data with default values + return new ConfigurationDataImpl(configMetadata, + resource, configName, this, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, false); + } + return null; + } + + @SuppressWarnings("unchecked") + @Override + public@NotNull ConfigurationCollectionData getConfigurationCollection(@NotNull Resource resource, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + if (log.isDebugEnabled()) { + log.debug("Get configuration collection for context path {}, name '{}'", resource.getPath(), configName); + } + ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); + List configData = new ArrayList<>(); + + // get all possible colection parent config names + Collection collectionParentConfigNames = configurationPersistenceStrategy.getAllCollectionParentConfigNames(configName); + + // get configuration resource items + List> configResourceInheritanceChains = new ArrayList<>(); + for (String collectionParentConfigName : collectionParentConfigNames) { + Collection> result = configurationResourceResolvingStrategy + .getResourceCollectionInheritanceChain(resource, configurationResourceResolverConfig.configBucketNames(), collectionParentConfigName); + if (result != null) { + configResourceInheritanceChains.addAll(result); + } + } + + String writebackConfigResourceCollectionParentPath = null; + for (Iterator configResourceInheritanceChain : configResourceInheritanceChains) { + ResettableIterator resettableConfigResourceInheritanceChain = new ListIteratorWrapper(configResourceInheritanceChain); + Resource configResource = applyPersistenceAndInheritance(resource.getPath(), configName, resettableConfigResourceInheritanceChain, true, resource.getResourceResolver()); + resettableConfigResourceInheritanceChain.reset(); + Resource untransformedConfigResource = (Resource)resettableConfigResourceInheritanceChain.next(); + if (configResource != null) { + // get writeback resource for "reverse inheritance detection" + Resource writebackConfigResource = null; + + String writebackConfigResourcePath = null; + for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + writebackConfigResourceCollectionParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, configBucketName, configName); + if (writebackConfigResourceCollectionParentPath != null) { + writebackConfigResourceCollectionParentPath = configurationPersistenceStrategy.getCollectionParentResourcePath(writebackConfigResourceCollectionParentPath); + writebackConfigResourcePath = writebackConfigResourceCollectionParentPath + "/" + untransformedConfigResource.getName(); + writebackConfigResource = configResource.getResourceResolver().getResource(writebackConfigResourcePath); + if (writebackConfigResource != null) { + writebackConfigResource = configurationPersistenceStrategy.getCollectionItemResource(writebackConfigResource); + break; + } + } + } + + if (log.isTraceEnabled()) { + log.trace("+ Found config resource for context path " + resource.getPath() + ": " + configResource.getPath() + " " + + MapUtil.traceOutput(configResource.getValueMap()) + ", " + + "writeback config resource: " + writebackConfigResourcePath); + } + resettableConfigResourceInheritanceChain.reset(); + configData.add(new ConfigurationDataImpl(configMetadata, configResource, writebackConfigResource, + applyPersistence(resettableConfigResourceInheritanceChain, true), + resource, configName, this, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, + true, untransformedConfigResource.getName())); + } + } + // fallback for writeback path detection when no configuration resources does exist yet + if (writebackConfigResourceCollectionParentPath == null) { + for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + writebackConfigResourceCollectionParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, configBucketName, configName); + if (writebackConfigResourceCollectionParentPath != null) { + break; + } + } + } + + // get properties of parent resource of the current level + Map resourceCollectionParentProps = null; + if (writebackConfigResourceCollectionParentPath != null) { + Resource writebackConfigResourceCollectionParent = resource.getResourceResolver().getResource(writebackConfigResourceCollectionParentPath); + if (writebackConfigResourceCollectionParent != null) { + for (String resourceName : configurationManagementSettings.getConfigCollectionPropertiesResourceNames()) { + Resource propsResource = writebackConfigResourceCollectionParent.getChild(resourceName); + if (propsResource != null) { + resourceCollectionParentProps = propsResource.getValueMap(); + break; + } + } + } + } + + return new ConfigurationCollectionDataImpl( + configName, + configData, + writebackConfigResourceCollectionParentPath, + resourceCollectionParentProps, + configurationManagementSettings + ); + } + + @SuppressWarnings("unchecked") + private Iterator applyPersistence(final Iterator configResourceInheritanceChain, final boolean isCollection) { + if (configResourceInheritanceChain == null) { + return null; + } + return IteratorUtils.transformedIterator(configResourceInheritanceChain, + new Transformer() { + @Override + public Object transform(Object input) { + if (isCollection) { + return configurationPersistenceStrategy.getCollectionItemResource((Resource)input); + } + else { + return configurationPersistenceStrategy.getResource((Resource)input); + } + } + }); + } + + private Resource applyPersistenceAndInheritance(String contextPath, String configName, Iterator configResourceInheritanceChain, + boolean isCollection, ResourceResolver resourceResolver) { + if (configResourceInheritanceChain == null) { + return null; + } + + // apply configuration persistence transformation + Iterator transformedConfigResources = applyPersistence(configResourceInheritanceChain, isCollection); + + // apply resource inheritance + Resource configResource = configurationInheritanceStrategy.getResource(transformedConfigResources); + + // apply overrides + return configurationOverrideMultiplexer.overrideProperties(contextPath, configName, configResource, resourceResolver); + } + + @Override + public void persistConfiguration(@NotNull Resource resource, @NotNull String configName, @NotNull ConfigurationPersistData data) { + ConfigNameUtil.ensureValidConfigName(configName); + String configResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, CONFIGS_BUCKET_NAME, configName); + if (configResourcePath == null) { + throw new ConfigurationPersistenceException("Unable to persist configuration: Configuration resolving strategy returned no path."); + } + if (log.isDebugEnabled()) { + log.debug("Persist configuration for context path {}, name '{}' to {}", resource.getPath(), configName, configResourcePath); + } + if (!configurationPersistenceStrategy.persistConfiguration(resource.getResourceResolver(), configResourcePath, data)) { + throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); + } + } + + @Override + public void persistConfigurationCollection(@NotNull Resource resource, @NotNull String configName, @NotNull ConfigurationCollectionPersistData data) { + ConfigNameUtil.ensureValidConfigName(configName); + String configResourceParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, CONFIGS_BUCKET_NAME, configName); + if (configResourceParentPath == null) { + throw new ConfigurationPersistenceException("Unable to persist configuration collection: Configuration resolving strategy returned no parent path."); + } + if (log.isDebugEnabled()) { + log.debug("Persist configuration collection for context path {}, name '{}' to {}", resource.getPath(), configName, configResourceParentPath); + } + if (!configurationPersistenceStrategy.persistConfigurationCollection(resource.getResourceResolver(), configResourceParentPath, data)) { + throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); + } + } + + @Override + public ConfigurationData newCollectionItem(@NotNull Resource resource, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); + if (configMetadata != null) { + return new ConfigurationDataImpl(configMetadata, + resource, configName, this, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, true); + } + return null; + } + + @Override + public void deleteConfiguration(@NotNull Resource resource, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + + // try to delete from all config bucket names + boolean foundAnyPath = false; + for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + String configResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, configBucketName, configName); + if (configResourcePath != null) { + foundAnyPath = true; + if (log.isDebugEnabled()) { + log.debug("Delete configuration for context path {}, name '{}' from {}", resource.getPath(), configName, configResourcePath); + } + if (!configurationPersistenceStrategy.deleteConfiguration(resource.getResourceResolver(), configResourcePath)) { + throw new ConfigurationPersistenceException("Unable to delete configuration: No persistence strategy found."); + } + } + } + if (!foundAnyPath) { + throw new ConfigurationPersistenceException("Unable to delete configuration: Configuration resolving strategy returned no path."); + } + } + + @Override + public @NotNull SortedSet getConfigurationNames() { + return configurationMetadataProvider.getConfigurationNames(); + } + + @Override + public ConfigurationMetadata getConfigurationMetadata(@NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + ConfigurationMetadata metadata = configurationMetadataProvider.getConfigurationMetadata(configName); + if (metadata != null) { + log.trace("+ Configuration metadata found for: {}", configName); + return metadata; + } + + // if no metadata found with direct match try to resolve nested configuration metadata references + for (String partialConfigName : ConfigNameUtil.getAllPartialConfigNameVariations(configName)) { + ConfigurationMetadata partialConfigMetadata = getConfigurationMetadata(partialConfigName); + if (partialConfigMetadata != null) { + ConfigurationMetadata nestedConfigMetadata = getNestedConfigurationMetadata(partialConfigMetadata, configName, partialConfigName); + if (nestedConfigMetadata != null) { + log.trace("+ Nested configuration metadata found for: {}", configName); + return nestedConfigMetadata; + } + } + } + + log.trace("- No configuration metadata found for: {}", configName); + return null; + } + + private ConfigurationMetadata getNestedConfigurationMetadata(ConfigurationMetadata configMetadata, String configName, String partialConfigName) { + if (StringUtils.startsWith(configName, partialConfigName + "/")) { + + // depending on different persistence strategies config names can be transformed differently - try all combinations here + Set prefixesToRemove = new LinkedHashSet<>(); + if (configMetadata.isCollection()) { + String collectionItemName = StringUtils.substringBefore(StringUtils.substringAfter(configName, partialConfigName + "/"), "/"); + for (String collectionParentConfigName : configurationPersistenceStrategy.getAllCollectionParentConfigNames(partialConfigName)) { + for (String collectionItemConfigName : configurationPersistenceStrategy.getAllCollectionItemConfigNames(collectionItemName)) { + prefixesToRemove.add(collectionParentConfigName + "/" + collectionItemConfigName + "/"); + } + } + } + else { + for (String configNameItem : configurationPersistenceStrategy.getAllConfigNames(partialConfigName)) { + prefixesToRemove.add(configNameItem + "/"); + } + } + + for (String prefixToRemove : prefixesToRemove) { + String remainingConfigName = StringUtils.substringAfter(configName, prefixToRemove); + // try direct match + ConfigurationMetadata nestedConfigMetadata = getNestedConfigurationMetadataFromProperty(configMetadata, remainingConfigName); + if (nestedConfigMetadata != null) { + return nestedConfigMetadata; + } + // try to find partial match for deeper nestings + for (String partialRemainingConfigName : ConfigNameUtil.getAllPartialConfigNameVariations(remainingConfigName)) { + ConfigurationMetadata partialConfigMetadata = getNestedConfigurationMetadataFromProperty(configMetadata, partialRemainingConfigName); + if (partialConfigMetadata != null) { + nestedConfigMetadata = getNestedConfigurationMetadata(partialConfigMetadata, remainingConfigName, partialRemainingConfigName); + if (nestedConfigMetadata != null) { + return nestedConfigMetadata; + } + } + } + } + } + return null; + } + + private ConfigurationMetadata getNestedConfigurationMetadataFromProperty(ConfigurationMetadata partialConfigMetadata, String configName) { + for (PropertyMetadata propertyMetadata : partialConfigMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + if (StringUtils.equals(configName, nestedConfigMetadata.getName())) { + return nestedConfigMetadata; + } + } + } + return null; + } + + @Override + public String getPersistenceResourcePath(@NotNull String configResourcePath) { + return configurationPersistenceStrategy.getResourcePath(configResourcePath); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationPersistenceStrategyMultiplexerImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationPersistenceStrategyMultiplexerImpl.java new file mode 100644 index 000000000..bdae9a8da --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationPersistenceStrategyMultiplexerImpl.java @@ -0,0 +1,241 @@ +/* + * 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.caconfig.management.impl; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Detects all {@link ConfigurationPersistenceStrategy2} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationPersistenceStrategyMultiplexer.class, +reference={ + @Reference(name="configurationPersistenceStrategy", service=ConfigurationPersistenceStrategy2.class, + bind="bindConfigurationPersistenceStrategy", unbind="unbindConfigurationPersistenceStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationPersistenceStrategyMultiplexerImpl implements ConfigurationPersistenceStrategyMultiplexer { + + private RankedServices items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationPersistenceStrategy(ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, Map props) { + items.bind(configurationPersistenceStrategy, props); + } + + protected void unbindConfigurationPersistenceStrategy(ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, Map props) { + items.unbind(configurationPersistenceStrategy, props); + } + + /** + * Transform the configuration resource by the first implementation that has an answer. + */ + @Override + public Resource getResource(@NotNull Resource resource) { + for (ConfigurationPersistenceStrategy2 item : items) { + Resource result = item.getResource(resource); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public Resource getCollectionParentResource(@NotNull Resource resource) { + for (ConfigurationPersistenceStrategy2 item : items) { + Resource result = item.getCollectionParentResource(resource); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public Resource getCollectionItemResource(@NotNull Resource resource) { + for (ConfigurationPersistenceStrategy2 item : items) { + Resource result = item.getCollectionItemResource(resource); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public String getResourcePath(@NotNull String resourcePath) { + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getResourcePath(resourcePath); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public String getCollectionParentResourcePath(@NotNull String resourcePath) { + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getCollectionParentResourcePath(resourcePath); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public String getCollectionItemResourcePath(@NotNull String resourcePath) { + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getCollectionItemResourcePath(resourcePath); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public String getConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getConfigName(configName, relatedConfigPath); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public String getCollectionParentConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getCollectionParentConfigName(configName, relatedConfigPath); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public String getCollectionItemConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getCollectionItemConfigName(configName, relatedConfigPath); + if (result != null) { + return result; + } + } + return null; + } + + @Override + public @NotNull Collection getAllConfigNames(@NotNull String configName) { + Set configNames = new LinkedHashSet<>(); + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getConfigName(configName, null); + if (result != null) { + configNames.add(result); + } + } + return configNames; + } + + @Override + public @NotNull Collection getAllCollectionParentConfigNames(@NotNull String configName) { + Set configNames = new LinkedHashSet<>(); + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getCollectionParentConfigName(configName, null); + if (result != null) { + configNames.add(result); + } + } + return configNames; + } + + @Override + public @NotNull Collection getAllCollectionItemConfigNames(@NotNull String configName) { + Set configNames = new LinkedHashSet<>(); + for (ConfigurationPersistenceStrategy2 item : items) { + String result = item.getCollectionItemConfigName(configName, null); + if (result != null) { + configNames.add(result); + } + } + return configNames; + } + + /** + * Persist configuration data with the first implementation that accepts it. + */ + @Override + public boolean persistConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath, + @NotNull ConfigurationPersistData data) { + for (ConfigurationPersistenceStrategy2 item : items) { + if (item.persistConfiguration(resourceResolver, configResourcePath, data)) { + return true; + } + } + return false; + } + + /** + * Persist configuration data with the first implementation that accepts it. + */ + @Override + public boolean persistConfigurationCollection(@NotNull ResourceResolver resourceResolver, @NotNull String configResourceCollectionParentPath, + @NotNull ConfigurationCollectionPersistData data) { + for (ConfigurationPersistenceStrategy2 item : items) { + if (item.persistConfigurationCollection(resourceResolver, configResourceCollectionParentPath, data)) { + return true; + } + } + return false; + } + + @Override + public boolean deleteConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath) { + for (ConfigurationPersistenceStrategy2 item : items) { + if (item.deleteConfiguration(resourceResolver, configResourcePath)) { + return true; + } + } + return false; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ContextPathStrategyMultiplexerImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ContextPathStrategyMultiplexerImpl.java new file mode 100644 index 000000000..f75c7b257 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ContextPathStrategyMultiplexerImpl.java @@ -0,0 +1,111 @@ +/* + * 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.caconfig.management.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.multiplexer.ContextPathStrategyMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.ResourceEliminateDuplicatesIterator; +import org.apache.sling.caconfig.resource.impl.util.ResourcePathCollatingIterator; +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Detects all {@link ContextPathStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@SuppressWarnings("deprecation") +@Component(service = { ContextPathStrategyMultiplexer.class, org.apache.sling.caconfig.management.ContextPathStrategyMultiplexer.class }, +reference={ + @Reference(name="contextPathStrategy", service=ContextPathStrategy.class, + bind="bindContextPathStrategy", unbind="unbindContextPathStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ContextPathStrategyMultiplexerImpl implements ContextPathStrategyMultiplexer, org.apache.sling.caconfig.management.ContextPathStrategyMultiplexer { + + private RankedServices items = new RankedServices<>(Order.DESCENDING); + + protected void bindContextPathStrategy(ContextPathStrategy contextPathStrategy, Map props) { + items.bind(contextPathStrategy, props); + } + + protected void unbindContextPathStrategy(ContextPathStrategy contextPathStrategy, Map props) { + items.unbind(contextPathStrategy, props); + } + + /** + * Merges all results from the detected implementations into a single answer. + */ + @Override + public @NotNull Iterator findContextResources(@NotNull Resource resource) { + List> allResults = getAllResults(resource); + if (allResults.isEmpty()) { + return Collections.emptyIterator(); + } + if (allResults.size() == 1) { + return allResults.get(0); + } + return mergeResults(allResults); + } + + /** + * Get all results from all registered context path strategies. + * @param resource Start resource + * @return List of all results + */ + private List> getAllResults(Resource resource) { + List> results = new ArrayList<>(); + for (ContextPathStrategy item : items) { + Iterator result = item.findContextResources(resource); + if (result.hasNext()) { + results.add(result); + } + } + return results; + } + + /** + * Merges results from different context path strategy implementations. + * Eliminating of duplicates and sorting is done solely based on path length. + * The contract of the ContextPathStrategy defines that only parents or the resource itself + * is returned, so the assumption should be safe. + * @param allResults List of all results + * @return Merged result + */ + private Iterator mergeResults(List> allResults) { + return new ResourceEliminateDuplicatesIterator( + new ResourcePathCollatingIterator(allResults) + ); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/PropertiesFilterUtil.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/PropertiesFilterUtil.java new file mode 100644 index 000000000..268ee1401 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/PropertiesFilterUtil.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl; + +import java.util.Map; +import java.util.Set; + +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; + +/** + * Filter internal properties from ConfigManager API output. + */ +public final class PropertiesFilterUtil { + + private PropertiesFilterUtil() { + // static methods only + } + + public static void removeIgnoredProperties(Set propertyNames, ConfigurationManagementSettings settings) { + Set ignoredProperties = settings.getIgnoredPropertyNames(propertyNames); + propertyNames.removeAll(ignoredProperties); + } + + public static void removeIgnoredProperties(Map props, ConfigurationManagementSettings settings) { + for (String propertyName : settings.getIgnoredPropertyNames(props.keySet())) { + props.remove(propertyName); + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ValueInfoImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ValueInfoImpl.java new file mode 100644 index 000000000..486008021 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/ValueInfoImpl.java @@ -0,0 +1,174 @@ +/* + * 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.caconfig.management.impl; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ValueInfoImpl implements ValueInfo { + + private final String name; + private final T value; + private final T effectiveValue; + private final T defaultValue; + private final PropertyMetadata propertyMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final boolean isAllOverridden; + + public ValueInfoImpl(String name, T value, T effectiveValue, PropertyMetadata propertyMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + List configurationResourceInheritanceChain, + Resource contextResource, String configName, ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + boolean isAllOverridden) { + this.name = name; + this.value = value; + this.effectiveValue = effectiveValue; + this.defaultValue = propertyMetadata != null ? propertyMetadata.getDefaultValue() : null; + this.propertyMetadata = propertyMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain; + this.contextResource = contextResource; + this.configName = configName; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.isAllOverridden = isAllOverridden; + } + + @Override + public @NotNull String getName() { + return name; + } + + @Override + public PropertyMetadata getPropertyMetadata() { + return propertyMetadata; + } + + @Override + public T getValue() { + return value; + } + + @Override + public T getEffectiveValue() { + return effectiveValue; + } + + @Override + public String getConfigSourcePath() { + if (effectiveValue != null && resolvedConfigurationResource != null) { + Resource resource = getResourceFromInheritanceChain(); + if (resource != null) { + return resource.getPath(); + } + } + return null; + } + + @Override + public boolean isDefault() { + if (defaultValue == null) { + return false; + } + if (resolvedConfigurationResource == null) { + return true; + } + else { + return !resolvedConfigurationResource.getValueMap().containsKey(name); + } + } + + @Override + public boolean isInherited() { + if (isDefault() || effectiveValue == null) { + return false; + } + else if (resolvedConfigurationResource == null || resolvedConfigurationResource.getPath() == null) { + return false; + } + else if (writebackConfigurationResource == null) { + return true; + } + else if (!StringUtils.equals(resolvedConfigurationResource.getPath(), writebackConfigurationResource.getPath())) { + return true; + } + else { + Resource inheritanceSource = getResourceFromInheritanceChain(); + if (inheritanceSource != null) { + return !StringUtils.equals(resolvedConfigurationResource.getPath(), inheritanceSource.getPath()); + } + else { + return false; + } + } + } + + private Resource getResourceFromInheritanceChain() { + if (configurationResourceInheritanceChain == null) { + return null; + } + return getResourceFromInheritanceChain(configurationResourceInheritanceChain.iterator()); + } + + private Resource getResourceFromInheritanceChain(Iterator inheritanceChain) { + if (!inheritanceChain.hasNext()) { + return null; + } + Resource resource = inheritanceChain.next(); + Object valueFromResource = resource.getValueMap().get(name, effectiveValue.getClass()); + if (valueFromResource != null) { + return resource; + } + return getResourceFromInheritanceChain(inheritanceChain); + } + + @Override + public boolean isOverridden() { + if (contextResource == null) { + return false; + } + if (isAllOverridden) { + return true; + } + Map overrideProperties = configurationOverrideMultiplexer.overrideProperties( + contextResource.getPath(), configName, Collections.emptyMap()); + if (overrideProperties != null) { + return overrideProperties.containsKey(name) + || (getValue() != null && effectiveValue == null); + } + else { + return false; + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/CAConfigInventoryPrinter.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/CAConfigInventoryPrinter.java new file mode 100644 index 000000000..d477b2ba7 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/CAConfigInventoryPrinter.java @@ -0,0 +1,172 @@ +/* + * 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.caconfig.management.impl.console; + +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.felix.inventory.Format; +import org.apache.felix.inventory.InventoryPrinter; +import org.apache.sling.caconfig.resource.spi.CollectionInheritanceDecider; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.ServiceUtil; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; + +/** + * Web console configuration printer. + */ +@Component(service=InventoryPrinter.class, +property={Constants.SERVICE_DESCRIPTION + "=Apache Sling Context-Aware Configuration Resolver Console Inventory Printer", + InventoryPrinter.NAME + "=" + CAConfigInventoryPrinter.NAME, + InventoryPrinter.TITLE + "=" + CAConfigInventoryPrinter.TITLE, + InventoryPrinter.FORMAT + "=TEXT"}) +public class CAConfigInventoryPrinter implements InventoryPrinter { + + public static final String NAME = "slingcaconfig"; + public static final String TITLE = "Sling Context-Aware Configuration"; + + private BundleContext bundleContext; + + @Activate + private void activate(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Override + public void print(PrintWriter pw, Format format, boolean isZip) { + if (format != Format.TEXT) { + return; + } + + printSPISection(pw, ContextPathStrategy.class, "Context Path Strategies"); + printSPISection(pw, ConfigurationResourceResolvingStrategy.class, "Configuration Resource Resolving Strategies"); + printSPISection(pw, CollectionInheritanceDecider.class, "Collection Inheritance Deciders"); + printSPISection(pw, ConfigurationInheritanceStrategy.class, "Configuration Inheritance Strategies"); + printSPISection(pw, ConfigurationPersistenceStrategy2.class, "Configuration Persistance Strategies", + new ConfigurationPersistenceStrategyPrinter()); + printSPISection(pw, ConfigurationMetadataProvider.class, "Configuration Metadata Providers", + new ConfigurationMetadataPrinter()); + printSPISection(pw, ConfigurationOverrideProvider.class, "Configuration Override Providers", + new ConfigurationOverridePrinter()); + } + + @SafeVarargs + private final void printSPISection(PrintWriter pw, Class clazz, String title, ServiceConfigurationPrinter... serviceConfigPrinters) { + Collection> serviceReferences = getServiceReferences(clazz); + + pw.println(title); + pw.println(StringUtils.repeat('-', title.length())); + + if (serviceReferences.isEmpty()) { + pw.println("(none)"); + } + else { + for (ServiceReference serviceReference : serviceReferences) { + pw.print(ServiceConfigurationPrinter.BULLET); + pw.print(getServiceClassName(serviceReference)); + pw.print(" ["); + pw.print(getServiceRanking(serviceReference)); + if (!isEnabled(serviceReference)) { + pw.print(", disabled"); + } + pw.print("]"); + pw.println(); + for (ServiceConfigurationPrinter serviceConfigPrinter : serviceConfigPrinters) { + serviceConfigPrinter.printConfiguration(pw, serviceReference, bundleContext); + } + } + } + pw.println(); + } + + private Collection> getServiceReferences(Class clazz) { + try { + SortedMap,ServiceReference> sortedServices = new TreeMap<>(); + Collection> serviceReferences = bundleContext.getServiceReferences(clazz, null); + for (ServiceReference serviceReference : serviceReferences) { + Map props = new HashMap<>(); + for (String property : serviceReference.getPropertyKeys()) { + props.put(property, serviceReference.getProperty(property)); + } + sortedServices.put( + ServiceUtil.getComparableForServiceRanking(props, Order.DESCENDING), + serviceReference + ); + } + return sortedServices.values(); + } + catch (InvalidSyntaxException ex) { + throw new RuntimeException(ex); + } + } + + private String getServiceClassName(ServiceReference serviceReference) { + Object service = bundleContext.getService(serviceReference); + String serviceClassName = service.getClass().getName(); + bundleContext.ungetService(serviceReference); + return serviceClassName; + } + + private int getServiceRanking(ServiceReference serviceReference) { + Object serviceRanking = serviceReference.getProperty(Constants.SERVICE_RANKING); + if (serviceRanking == null) { + return 0; + } + else { + if (serviceRanking instanceof Number) { + return ((Number)serviceRanking).intValue(); + } + else { + return NumberUtils.toInt(serviceRanking.toString(), 0); + } + } + } + + private boolean isEnabled(ServiceReference serviceReference) { + Object enabledObject = (Object)serviceReference.getProperty("enabled"); + if (enabledObject != null) { + if (enabledObject instanceof Boolean) { + return ((Boolean)enabledObject).booleanValue(); + } + else { + return BooleanUtils.toBoolean(enabledObject.toString()); + } + } + return true; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationMetadataPrinter.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationMetadataPrinter.java new file mode 100644 index 000000000..04a7587ca --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationMetadataPrinter.java @@ -0,0 +1,83 @@ +/* + * 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.caconfig.management.impl.console; + +import java.io.PrintWriter; +import java.lang.reflect.Array; + +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * Print configuration metadata provided by a {@link ConfigurationMetadata}. + */ +class ConfigurationMetadataPrinter implements ServiceConfigurationPrinter { + + @Override + public void printConfiguration(PrintWriter pw, ServiceReference serviceReference, BundleContext bundleContext) { + ConfigurationMetadataProvider service = bundleContext.getService(serviceReference); + + for (String configName : service.getConfigurationNames()) { + ConfigurationMetadata metadata = service.getConfigurationMetadata(configName); + if (metadata == null) { + continue; + } + pw.print(INDENT); + pw.print(BULLET); + pw.println(metadata.getName()); + + for (PropertyMetadata property : metadata.getPropertyMetadata().values()) { + pw.print(INDENT_2); + pw.print(BULLET); + pw.print(property.getName()); + + pw.print("("); + pw.print(property.getType().getSimpleName()); + pw.print(")"); + + if (property.getDefaultValue() != null) { + pw.print(" = "); + printValue(pw, property.getDefaultValue()); + } + + pw.println(); + } + } + + bundleContext.ungetService(serviceReference); + } + + private void printValue(PrintWriter pw, Object value) { + if (value.getClass().isArray()) { + for (int i=0; i 0) { + pw.print(", "); + } + printValue(pw, Array.get(value, i)); + } + } + else { + pw.print(value); + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationOverridePrinter.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationOverridePrinter.java new file mode 100644 index 000000000..1412edb4e --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationOverridePrinter.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl.console; + +import java.io.PrintWriter; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * Print override strings from a {@link ConfigurationOverrideProvider}. + */ +class ConfigurationOverridePrinter implements ServiceConfigurationPrinter { + + @Override + public void printConfiguration(PrintWriter pw, ServiceReference serviceReference, BundleContext bundleContext) { + ConfigurationOverrideProvider service = bundleContext.getService(serviceReference); + + for (String overrideString : service.getOverrideStrings()) { + if (StringUtils.isBlank(overrideString)) { + continue; + } + pw.print(INDENT); + pw.print(BULLET); + pw.println(overrideString); + } + + bundleContext.ungetService(serviceReference); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationPersistenceStrategyPrinter.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationPersistenceStrategyPrinter.java new file mode 100644 index 000000000..fe6172cdb --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationPersistenceStrategyPrinter.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl.console; + +import java.io.PrintWriter; + +import org.apache.sling.caconfig.impl.ConfigurationPersistenceStrategyBridge; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * Print original class information for a {@link ConfigurationPersistenceStrategy2} bridge service. + */ +class ConfigurationPersistenceStrategyPrinter implements ServiceConfigurationPrinter { + + @Override + public void printConfiguration(PrintWriter pw, ServiceReference serviceReference, BundleContext bundleContext) { + ConfigurationPersistenceStrategy2 service = bundleContext.getService(serviceReference); + if (service instanceof ConfigurationPersistenceStrategyBridge.Adapter) { + ConfigurationPersistenceStrategyBridge.Adapter adapter = + (ConfigurationPersistenceStrategyBridge.Adapter)service; + pw.print(INDENT); + pw.print(BULLET); + pw.println("Delegates to " + adapter.getOriginalServiceClass().getName()); + } + bundleContext.ungetService(serviceReference); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationWebConsolePlugin.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationWebConsolePlugin.java new file mode 100644 index 000000000..360b5f588 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationWebConsolePlugin.java @@ -0,0 +1,399 @@ +/* + * 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.caconfig.management.impl.console; + +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.felix.webconsole.AbstractWebConsolePlugin; +import org.apache.felix.webconsole.WebConsoleConstants; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ContextPathStrategyMultiplexer; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.apache.sling.xss.XSSAPI; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Web console plugin to test configuration resolution. + */ +@Component(service=Servlet.class, + property={Constants.SERVICE_DESCRIPTION + "=Apache Sling Context-Aware Configuration Web Console Plugin", + WebConsoleConstants.PLUGIN_LABEL + "=" + ConfigurationWebConsolePlugin.LABEL, + WebConsoleConstants.PLUGIN_TITLE + "=" + ConfigurationWebConsolePlugin.TITLE, + WebConsoleConstants.PLUGIN_CATEGORY + "=Sling"}) +@SuppressWarnings("serial") +public class ConfigurationWebConsolePlugin extends AbstractWebConsolePlugin { + + public static final String LABEL = "slingcaconfig"; + public static final String TITLE = "Context-Aware Configuration"; + + private static final Logger log = LoggerFactory.getLogger(ConfigurationWebConsolePlugin.class); + + @Reference(policyOption = ReferencePolicyOption.GREEDY) + private ResourceResolverFactory resolverFactory; + + @Reference(policyOption = ReferencePolicyOption.GREEDY) + private ConfigurationManager configurationManager; + + @Reference(policyOption = ReferencePolicyOption.GREEDY) + private ContextPathStrategyMultiplexer contextPathStrategyMultiplexer; + + @Reference(policyOption = ReferencePolicyOption.GREEDY) + private XSSAPI xss; + + @Override + public String getLabel() { + return LABEL; + } + + @Override + public String getTitle() { + return TITLE; + } + + @Override + protected void renderContent(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + final PrintWriter pw = response.getWriter(); + + info(pw, "Configurations are managed in the resource tree. Use this tool to test configuration resolutions."); + + pw.println("
"); + + printResolutionTestTool(request, pw); + } + + private String getParameter(final HttpServletRequest request, final String name, final String defaultValue) { + String value = request.getParameter(name); + if ( value != null && !value.trim().isEmpty() ) { + return value.trim(); + } + return defaultValue; + } + + private void printResolutionTestTool(HttpServletRequest request, PrintWriter pw) { + final String path = this.getParameter(request, "path", "/content"); + String configNameOther = this.getParameter(request, "configNameOther", null); + String configName = this.getParameter(request, "configName", null); + if (configName == null) { + configName = configNameOther; + } + else { + configNameOther = null; + } + final boolean resourceCollection = BooleanUtils.toBoolean(this.getParameter(request, "resourceCollection", "false")); + + ResourceResolver resolver = null; + try { + Resource contentResource = null; + if (path != null) { + resolver = getResolver(request); + if (resolver != null) { + contentResource = resolver.getResource(path); + } + } + + pw.println("
"); + + tableStart(pw, "Test Configuration Resolution", 2); + + String alertMessage = null; + if (path != null) { + if (resolver == null) { + alertMessage = "Unable to access repository - please check system configuration."; + } + else if (contentResource == null) { + alertMessage = "Path does not exist."; + } + } + textField(pw, "Content Path", "path", path, alertMessage); + + tableRows(pw); + selectField(pw, "Config Name", "configName", configName, configurationManager.getConfigurationNames()); + + tableRows(pw); + textField(pw, "Other Config Name", "configNameOther", configNameOther); + + tableRows(pw); + checkboxField(pw, "Resource collection", "resourceCollection", resourceCollection); + + tableRows(pw); + pw.println(""); + pw.println(""); + tableEnd(pw); + + pw.println("
"); + + pw.println("
"); + + if (contentResource != null && configName != null) { + + // context paths + Iterator contextResources = contextPathStrategyMultiplexer.findContextResources(contentResource); + tableStart(pw, "Context paths", 3); + pw.println("Context path"); + pw.println("Config reference"); + pw.println("Ranking"); + while (contextResources.hasNext()) { + ContextResource contextResource = contextResources.next(); + tableRows(pw); + pw.println("" + xss.encodeForHTML(contextResource.getResource().getPath()) + ""); + pw.println("" + xss.encodeForHTML(contextResource.getConfigRef()) + ""); + pw.println("" + contextResource.getServiceRanking() + ""); + } + tableEnd(pw); + + pw.println("
"); + + // resolve configuration + Collection configDatas; + if (resourceCollection) { + configDatas = configurationManager.getConfigurationCollection(contentResource, configName).getItems(); + } + else { + ConfigurationData configData = configurationManager.getConfiguration(contentResource, configName); + if (configData != null) { + configDatas = Collections.singletonList(configData); + } + else { + configDatas = Collections.emptyList(); + } + } + + tableStart(pw, "Result", 6); + + if (configDatas.size() == 0) { + pw.println(""); + alertDiv(pw, "No matching item found."); + pw.println("
 "); + } + else { + + pw.println("Property"); + pw.println("Effective Value"); + pw.println("Value"); + pw.println("Default"); + pw.println("Inherited"); + pw.println("Overwritten"); + + for (ConfigurationData data : configDatas) { + tableRows(pw); + pw.println(""); + pw.print("Path: " + xss.encodeForHTML(data.getResourcePath())); + pw.println(""); + + for (String propertyName : data.getPropertyNames()) { + ValueInfo valueInfo = data.getValueInfo(propertyName); + if (valueInfo == null) { + continue; + } + tableRows(pw); + td(pw, propertyName); + td(pw, valueInfo.getEffectiveValue()); + td(pw, valueInfo.getValue()); + td(pw, valueInfo.isDefault()); + + String title = null; + if (valueInfo.isInherited()) { + title = "Source path: " + valueInfo.getConfigSourcePath(); + } + td(pw, valueInfo.isInherited(), title); + + td(pw, valueInfo.isOverridden()); + } + + } + + } + + tableEnd(pw); + } + + } + finally { + if (resolver != null) { + resolver.close(); + } + } + } + + private void info(PrintWriter pw, String text) { + pw.print("

"); + pw.print(xss.encodeForHTML(text)); + pw.println("

"); + } + + private void tableStart(PrintWriter pw, String title, int colspan) { + pw.println(""); + pw.println(""); + pw.println(""); + pw.print(""); + pw.println(""); + pw.println(""); + pw.println(""); + pw.println(""); + } + + private void tableEnd(PrintWriter pw) { + pw.println(""); + pw.println(""); + pw.println("
"); + pw.print(xss.encodeForHTML(title)); + pw.println("
"); + } + + private void tableRows(PrintWriter pw) { + pw.println(""); + pw.println(""); + } + + private void textField(PrintWriter pw, String label, String fieldName, String value, String... alertMessages) { + pw.print(""); + pw.print(xss.encodeForHTMLAttr(label)); + pw.println(""); + pw.print(""); + for (String alertMessage : alertMessages) { + alertDiv(pw, alertMessage); + } + pw.println(""); + } + + private void selectField(PrintWriter pw, String label, String fieldName, String value, Collection options) { + pw.print(""); + pw.print(xss.encodeForHTMLAttr(label)); + pw.println(""); + pw.print(""); + pw.println(""); + } + + private void checkboxField(PrintWriter pw, String label, String fieldName, boolean checked) { + pw.print(""); + pw.print(xss.encodeForHTMLAttr(label)); + pw.println(""); + pw.print(""); + } + + private void alertDiv(PrintWriter pw, String text) { + if (StringUtils.isBlank(text)) { + return; + } + pw.println("
"); + pw.println(""); + pw.print(""); + pw.print(xss.encodeForHTML(text)); + pw.println(""); + pw.println("
"); + } + + private void td(PrintWriter pw, Object value, String... title) { + pw.print(" 0 && !StringUtils.isBlank(title[0])) { + pw.print(" title='"); + pw.print(xss.encodeForHTML(title[0])); + pw.print("'"); + } + pw.print(">"); + + if (value != null) { + if (value.getClass().isArray()) { + for (int i = 0; i < Array.getLength(value); i++) { + Object itemValue = Array.get(value, i); + pw.print(xss.encodeForHTML(ObjectUtils.defaultIfNull(itemValue, "").toString())); + pw.println("
"); + } + } + else { + pw.print(xss.encodeForHTML(value.toString())); + } + } + + if (title.length > 0 && !StringUtils.isBlank(title[0])) { + pw.print(""); + } + pw.print(""); + } + + private ResourceResolver getResolver(HttpServletRequest request) { + ResourceResolver resolver = null; + try { + resolver = resolverFactory.getServiceResourceResolver(null); + } + catch (final LoginException ex) { + // fallback if no service user is registered - try to get current web console resource resolver + resolver = (ResourceResolver)request.getAttribute("org.apache.sling.auth.core.ResourceResolver"); + if (resolver == null) { + log.warn("Unable to get resource resolver - please ensure a system user is configured: {}", ex.getMessage()); + } + else { + log.debug("No system user configured, use resource resolver from web console."); + } + } + return resolver; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ServiceConfigurationPrinter.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ServiceConfigurationPrinter.java new file mode 100644 index 000000000..348aadf26 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/impl/console/ServiceConfigurationPrinter.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.caconfig.management.impl.console; + +import java.io.PrintWriter; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * Print additional configuration per service. + * @param Service type + */ +interface ServiceConfigurationPrinter { + + /** + * Bullet character + */ + String BULLET = "- "; + + /** + * Indentation 1 step + */ + String INDENT = " "; + + /** + * Indentation 2 steps + */ + String INDENT_2 = INDENT + INDENT; + + /** + * Indentation 3 steps + */ + String INDENT_3 = INDENT_2 + INDENT; + + /** + * Print configuration + * @param printWriter Print writer + * @param serviceReference Service reference + * @param bundleContext Bundle context + */ + void printConfiguration(PrintWriter printWriter, ServiceReference serviceReference, BundleContext bundleContext); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationInheritanceStrategyMultiplexer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationInheritanceStrategyMultiplexer.java new file mode 100644 index 000000000..7f9b33d57 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationInheritanceStrategyMultiplexer.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.multiplexer; + +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Detects all {@link ConfigurationInheritanceStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@ProviderType +public interface ConfigurationInheritanceStrategyMultiplexer extends ConfigurationInheritanceStrategy { + + // inherits all methods from {@link ConfigurationInheritanceStrategy} + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationMetadataProviderMultiplexer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationMetadataProviderMultiplexer.java new file mode 100644 index 000000000..79d765399 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationMetadataProviderMultiplexer.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.multiplexer; + +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Detects all {@link ConfigurationMetadataProvider} implementations in the container + * and consolidates their result based on service ranking. + */ +@ProviderType +public interface ConfigurationMetadataProviderMultiplexer extends ConfigurationMetadataProvider { + + // inherits all methods from {@link ConfigurationMetadataProvider} + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationOverrideMultiplexer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationOverrideMultiplexer.java new file mode 100644 index 000000000..df040caee --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationOverrideMultiplexer.java @@ -0,0 +1,75 @@ +/* + * 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.caconfig.management.multiplexer; + +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Detects all {@link org.apache.sling.caconfig.spi.ConfigurationOverrideProvider} implementations in the container + * and consolidates their result based on service ranking. + */ +@ProviderType +public interface ConfigurationOverrideMultiplexer { + + /** + * Checks if the whole configuration for the given context path and name is overridden. + * @param contextPath Context path + * @param configName Config name + * @return true if the whole configuration is overridden. + */ + boolean isAllOverridden(@NotNull String contextPath, @NotNull String configName); + + /** + * Override properties for given context path and configuration name. + * @param contextPath Path of context resource for which configuration was resolved + * @param configName Configuration name + * @param properties Resolved configuration properties + * @return Overwritten or replaced properties - or null if no override took place + */ + @Nullable Map overrideProperties(@NotNull String contextPath, @NotNull String configName, @NotNull Map properties); + + /** + * Override properties in given configuration resource (if any overrides are defined). + * @param contextPath Context path + * @param configName Configuration name + * @param configResource Resolved configuration resource + * @return Resource with overwritten configuration properties - or original configuration resource if no override took place + * @deprecated Please use {@link #overrideProperties(String, String, Resource, ResourceResolver)} instead. + */ + @Deprecated + @Nullable Resource overrideProperties(@NotNull String contextPath, @NotNull String configName, @Nullable Resource configResource); + + /** + * Override properties in given configuration resource (if any overrides are defined). + * @param contextPath Context path + * @param configName Configuration name + * @param configResource Resolved configuration resource + * @param resourceResolver Resource resolver + * @return Resource with overwritten configuration properties - or original configuration resource if no override took place + */ + @Nullable Resource overrideProperties(@NotNull String contextPath, @NotNull String configName, @Nullable Resource configResource, + @NotNull ResourceResolver resourceResolver); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationPersistenceStrategyMultiplexer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationPersistenceStrategyMultiplexer.java new file mode 100644 index 000000000..26aaf5581 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationPersistenceStrategyMultiplexer.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.multiplexer; + +import java.util.Collection; + +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.jetbrains.annotations.NotNull; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Detects all {@link ConfigurationPersistenceStrategy2} implementations in the container + * and consolidates their result based on service ranking. + */ +@ProviderType +public interface ConfigurationPersistenceStrategyMultiplexer extends ConfigurationPersistenceStrategy2 { + + /** + * Get all configuration names from all configuration persistence strategies. This can be used when no nested parent resources is known. + * @param configName Configuration name + * @return Possible configuration names in order of persistence strategy service ranking. + */ + @NotNull Collection getAllConfigNames(@NotNull String configName); + + /** + * Get all configuration names from all configuration persistence strategies. This can be used when no nested parent resources is known. + * @param configName Configuration name + * @return Possible configuration names in order of persistence strategy service ranking. + */ + @NotNull Collection getAllCollectionParentConfigNames(@NotNull String configName); + + /** + * Get all configuration names from all configuration persistence strategies. This can be used when no nested parent resources is known. + * @param configName Configuration name + * @return Possible configuration names in order of persistence strategy service ranking. + */ + @NotNull Collection getAllCollectionItemConfigNames(@NotNull String configName); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationResourceResolvingStrategyMultiplexer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationResourceResolvingStrategyMultiplexer.java new file mode 100644 index 000000000..879cdc86f --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ConfigurationResourceResolvingStrategyMultiplexer.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.multiplexer; + +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Detects all {@link ConfigurationResourceResolvingStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@ProviderType +public interface ConfigurationResourceResolvingStrategyMultiplexer extends ConfigurationResourceResolvingStrategy { + + // inherits all methods from {@link ConfigurationResourceResolvingStrategy} + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ContextPathStrategyMultiplexer.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ContextPathStrategyMultiplexer.java new file mode 100644 index 000000000..3c55b000e --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/ContextPathStrategyMultiplexer.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.multiplexer; + +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; +import org.osgi.annotation.versioning.ProviderType; + +/** + * Detects all {@link ContextPathStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@ProviderType +public interface ContextPathStrategyMultiplexer extends ContextPathStrategy { + + // inherits all methods from {@link ContextPathStrategy} + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/package-info.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/package-info.java new file mode 100644 index 000000000..a634a612d --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/multiplexer/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. + */ +/** + * Multiplexer services provide aggregated access to all implementations of the related SPI interface. + */ +@org.osgi.annotation.versioning.Version("1.1.2") +package org.apache.sling.caconfig.management.multiplexer; diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/package-info.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/package-info.java new file mode 100644 index 000000000..02282227c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/management/package-info.java @@ -0,0 +1,25 @@ +/* + * 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. + */ +/** + * Management API of the Context-Aware configuration implementation. + * This API is only indented for advanced use cases like writing a configuration editor, + * not for "normal" applications just reading configuration. + */ +@org.osgi.annotation.versioning.Version("2.2.2") +package org.apache.sling.caconfig.management; diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImpl.java new file mode 100644 index 000000000..56c34290a --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImpl.java @@ -0,0 +1,82 @@ +/* + * 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.caconfig.resource.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationResourceResolvingStrategyMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ContextPathStrategyMultiplexer; +import org.apache.sling.caconfig.resource.ConfigurationResourceResolver; +import org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +@Component(service=ConfigurationResourceResolver.class, immediate=true) +public class ConfigurationResourceResolverImpl implements ConfigurationResourceResolver { + + @Reference + private ContextPathStrategyMultiplexer contextPathStrategy; + @Reference + private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy; + + @Override + public Resource getResource(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + return configurationResourceResolvingStrategy.getResource(resource, Collections.singleton(bucketName), configName); + } + + @Override + public @NotNull Collection getResourceCollection(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + Collection result = configurationResourceResolvingStrategy.getResourceCollection(resource, Collections.singleton(bucketName), configName); + if (result == null) { + result = Collections.emptyList(); + } + return result; + } + + @Override + public String getContextPath(@NotNull Resource resource) { + Iterator it = contextPathStrategy.findContextResources(resource); + if (it.hasNext()) { + return it.next().getResource().getPath(); + } + else { + return null; + } + } + + @Override + public @NotNull Collection getAllContextPaths(@NotNull Resource resource) { + final List contextPaths = new ArrayList<>(); + Iterator contextResources = contextPathStrategy.findContextResources(resource); + while (contextResources.hasNext()) { + contextPaths.add(contextResources.next().getResource().getPath()); + } + return contextPaths; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolvingStrategyMultiplexerImpl.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolvingStrategyMultiplexerImpl.java new file mode 100644 index 000000000..166912471 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolvingStrategyMultiplexerImpl.java @@ -0,0 +1,145 @@ +/* + * 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.caconfig.resource.impl; + +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationResourceResolvingStrategyMultiplexer; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Detects all {@link ConfigurationResourceResolvingStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationResourceResolvingStrategyMultiplexer.class, +reference={ + @Reference(name="configurationResourceResolvingStrategy", service=ConfigurationResourceResolvingStrategy.class, + bind="bindConfigurationResourceResolvingStrategy", unbind="unbindConfigurationResourceResolvingStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationResourceResolvingStrategyMultiplexerImpl implements ConfigurationResourceResolvingStrategyMultiplexer { + + private RankedServices items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationResourceResolvingStrategy(ConfigurationResourceResolvingStrategy contextPathStrategy, Map props) { + items.bind(contextPathStrategy, props); + } + + protected void unbindConfigurationResourceResolvingStrategy(ConfigurationResourceResolvingStrategy contextPathStrategy, Map props) { + items.unbind(contextPathStrategy, props); + } + + /** + * Gets the configuration resource from the first implementation that has an answer. + */ + @Override + public Resource getResource(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + Resource result = item.getResource(resource, bucketNames, configName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Gets the configuration resource collection from the first implementation that has an answer. + */ + @Override + public Collection getResourceCollection(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + Collection result = item.getResourceCollection(resource, bucketNames, configName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Gets the configuration resource inheritance chain from the first implementation that has an answer. + */ + @Override + public Iterator getResourceInheritanceChain(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + Iterator result = item.getResourceInheritanceChain(resource, bucketNames, configName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Gets the configuration resource collection inheritance chains from the first implementation that has an answer. + */ + @Override + public Collection> getResourceCollectionInheritanceChain(@NotNull Resource resource, @NotNull Collection bucketNames, + @NotNull String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + Collection> result = item.getResourceCollectionInheritanceChain(resource, bucketNames, configName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Gets the configuration resource path from the first implementation that has an answer. + */ + @Override + public String getResourcePath(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + String result = item.getResourcePath(resource, bucketName, configName); + if (result != null) { + return result; + } + } + return null; + } + + /** + * Gets the configuration resource collection parent path from the first implementation that has an answer. + */ + @Override + public String getResourceCollectionParentPath(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + for (ConfigurationResourceResolvingStrategy item : items) { + String result = item.getResourceCollectionParentPath(resource, bucketName, configName); + if (result != null) { + return result; + } + } + return null; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/ConfigurationResourceNameConstants.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/ConfigurationResourceNameConstants.java new file mode 100644 index 000000000..50e33e57b --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/ConfigurationResourceNameConstants.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.resource.impl.def; + +public final class ConfigurationResourceNameConstants { + + private ConfigurationResourceNameConstants() { + // constants only + } + + /** + * Property that points to the configuration path to be used. + * Additionally each resource having this property marks the beginning of a new context sub-tree. + */ + public static final String PROPERTY_CONFIG_REF = "sling:configRef"; + + /** + * Boolean property that controls whether config resource collections should be merged on inheritance or not. + * Merging means merging the lists, not the list items (properties of the resources) itself. + */ + public static final String PROPERTY_CONFIG_COLLECTION_INHERIT = "sling:configCollectionInherit"; + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java new file mode 100644 index 000000000..38cb165ad --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategy.java @@ -0,0 +1,474 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.resource.impl.def; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.PredicateUtils; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.iterators.ArrayIterator; +import org.apache.commons.collections4.iterators.FilterIterator; +import org.apache.commons.collections4.iterators.IteratorChain; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.management.multiplexer.ContextPathStrategyMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil; +import org.apache.sling.caconfig.resource.impl.util.PathEliminateDuplicatesIterator; +import org.apache.sling.caconfig.resource.impl.util.PathParentExpandIterator; +import org.apache.sling.caconfig.resource.impl.util.PropertyUtil; +import org.apache.sling.caconfig.resource.spi.CollectionInheritanceDecider; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.apache.sling.caconfig.resource.spi.InheritanceDecision; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.FieldOption; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service=ConfigurationResourceResolvingStrategy.class) +@Designate(ocd=DefaultConfigurationResourceResolvingStrategy.Config.class) +public class DefaultConfigurationResourceResolvingStrategy implements ConfigurationResourceResolvingStrategy { + + @ObjectClassDefinition(name="Apache Sling Context-Aware Configuration Default Resource Resolving Strategy", + description="Standardized access to configurations in the resource tree.") + public static @interface Config { + + @AttributeDefinition(name="Enabled", + description = "Enable this configuration resource resolving strategy.") + boolean enabled() default true; + + @AttributeDefinition(name="Configurations path", + description = "Paths where the configurations are stored in.") + String configPath() default "/conf"; + + @AttributeDefinition(name="Fallback paths", + description = "Global fallback configurations, ordered from most specific (checked first) to least specific.") + String[] fallbackPaths() default {"/conf/global", "/apps/conf", "/libs/conf"}; + + @AttributeDefinition(name="Config collection inheritance property names", + description = "Additional property names to " + PROPERTY_CONFIG_COLLECTION_INHERIT + " to handle configuration inheritance. The names are used in the order defined, " + + "always starting with " + PROPERTY_CONFIG_COLLECTION_INHERIT + ". Once a property with a value is found, that value is used and the following property names are skipped.") + String[] configCollectionInheritancePropertyNames(); + + } + + private static final Logger log = LoggerFactory.getLogger(DefaultConfigurationResourceResolvingStrategy.class); + + private volatile Config config; + + @Reference + private ContextPathStrategyMultiplexer contextPathStrategy; + + @Reference(cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, + fieldOption=FieldOption.REPLACE) + private volatile List collectionInheritanceDeciders; + + @Activate + private void activate(final Config config) { + this.config = config; + } + + @Deactivate + private void deactivate() { + this.config = null; + } + + @SuppressWarnings("unchecked") + Iterator getResolvePaths(final Resource contentResource, final Collection bucketNames) { + return new IteratorChain( + // add all config references found in resource hierarchy + findConfigRefs(contentResource, bucketNames), + // finally add the global fallbacks + new ArrayIterator(this.config.fallbackPaths()) + ); + } + + /** + * Searches the resource hierarchy upwards for all config references and returns them. + * @param startResource Resource to start searching + * @param bucketNames Bucket names to search in + */ + @SuppressWarnings("unchecked") + private Iterator findConfigRefs(final Resource startResource, final Collection bucketNames) { + + // collect all context path resources (but filter out those without config reference) + final Iterator contextResources = new FilterIterator(contextPathStrategy.findContextResources(startResource), + new Predicate() { + @Override + public boolean evaluate(Object object) { + ContextResource contextResource = (ContextResource)object; + return StringUtils.isNotBlank(contextResource.getConfigRef()); + } + }); + + // get config resource path for each context resource, filter out items where not reference could be resolved + final Iterator configPaths = new Iterator() { + + private final List relativePaths = new ArrayList<>(); + + private String next = seek(); + + private String useFromRelativePathsWith; + + private String seek() { + String val = null; + while ( val == null && (useFromRelativePathsWith != null || contextResources.hasNext()) ) { + if ( useFromRelativePathsWith != null ) { + final ContextResource contextResource = relativePaths.remove(relativePaths.size() - 1); + val = checkPath(contextResource, useFromRelativePathsWith + "/" + contextResource.getConfigRef(), bucketNames); + if (val != null) { + log.trace("+ Found reference for context path {}: {}", contextResource.getResource().getPath(), val); + } + if ( relativePaths.isEmpty() ) { + useFromRelativePathsWith = null; + } + } else { + final ContextResource contextResource = contextResources.next(); + val = contextResource.getConfigRef(); + + // if absolute path found we are (probably) done + if (val != null && val.startsWith("/")) { + val = checkPath(contextResource, val, bucketNames); + } + + if (val != null) { + final boolean isAbsolute = val.startsWith("/"); + if ( isAbsolute && !relativePaths.isEmpty() ) { + useFromRelativePathsWith = val; + val = null; + } else if ( !isAbsolute ) { + relativePaths.add(0, contextResource); + val = null; + } + } + + if (val != null) { + log.trace("+ Found reference for context path {}: {}", contextResource.getResource().getPath(), val); + } + } + } + if ( val == null && !relativePaths.isEmpty() ) { + log.error("Relative references not used as no absolute reference was found: {}", relativePaths); + } + return val; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public String next() { + if ( next == null ) { + throw new NoSuchElementException(); + } + final String result = next; + next = seek(); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + + // expand paths and eliminate duplicates + return new PathEliminateDuplicatesIterator(new PathParentExpandIterator(config.configPath(), configPaths)); + } + + private String checkPath(final ContextResource contextResource, String ref, final Collection bucketNames) { + // combine full path if relativeRef is present + ref = ResourceUtil.normalize(ref); + + for (String bucketName : bucketNames) { + String notAllowedPostfix = "/" + bucketName; + if (ref != null && ref.endsWith(notAllowedPostfix)) { + log.debug("Ignoring reference to {} from {} - Probably misconfigured as it ends with '{}'", + contextResource.getConfigRef(), contextResource.getResource().getPath(), notAllowedPostfix); + ref = null; + } + } + + if (ref != null && !isAllowedConfigPath(ref)) { + log.debug("Ignoring reference to {} from {} - not in allowed paths.", + contextResource.getConfigRef(), contextResource.getResource().getPath()); + ref = null; + } + + if (ref != null && isFallbackConfigPath(ref)) { + log.debug("Ignoring reference to {} from {} - already a fallback path.", + contextResource.getConfigRef(), contextResource.getResource().getPath()); + ref = null; + } + + return ref; + } + + private boolean isAllowedConfigPath(String path) { + return path.startsWith(config.configPath() + "/"); + } + + private boolean isFallbackConfigPath(final String ref) { + for(final String path : this.config.fallbackPaths()) { + if (StringUtils.equals(ref, path) || StringUtils.startsWith(ref, path + "/")) { + return true; + } + } + return false; + } + + private boolean isEnabledAndParamsValid(final Resource contentResource, final Collection bucketNames, final String configName) { + return config.enabled() && contentResource != null && ConfigNameUtil.isValid(bucketNames) && ConfigNameUtil.isValid(configName); + } + + private String buildResourcePath(String path, String name) { + return ResourceUtil.normalize(path + "/" + name); + } + + @Override + public Resource getResource(@NotNull final Resource contentResource, @NotNull final Collection bucketNames, @NotNull final String configName) { + Iterator resources = getResourceInheritanceChain(contentResource, bucketNames, configName); + if (resources != null && resources.hasNext()) { + return resources.next(); + } + return null; + } + + @SuppressWarnings("unchecked") + private Iterator getResourceInheritanceChainInternal(final Collection bucketNames, final String configName, + final Iterator paths, final ResourceResolver resourceResolver) { + + // find all matching items among all configured paths + Iterator matchingResources = IteratorUtils.transformedIterator(paths, new Transformer() { + @Override + public Object transform(Object input) { + String path = (String)input; + for (String bucketName : bucketNames) { + final String name = bucketName + "/" + configName; + final String configPath = buildResourcePath(path, name); + Resource resource = resourceResolver.getResource(configPath); + if (resource != null) { + log.trace("+ Found matching config resource for inheritance chain: {}", configPath); + return resource; + } + else { + log.trace("- No matching config resource for inheritance chain: {}", configPath); + } + } + return null; + } + }); + Iterator result = IteratorUtils.filteredIterator(matchingResources, PredicateUtils.notNullPredicate()); + if (result.hasNext()) { + return result; + } + return null; + } + + @Override + public Iterator getResourceInheritanceChain(@NotNull Resource contentResource, @NotNull Collection bucketNames, @NotNull String configName) { + if (!isEnabledAndParamsValid(contentResource, bucketNames, configName)) { + return null; + } + final ResourceResolver resourceResolver = contentResource.getResourceResolver(); + + Iterator paths = getResolvePaths(contentResource, bucketNames); + return getResourceInheritanceChainInternal(bucketNames, configName, paths, resourceResolver); + } + + private boolean include(final List deciders, + final String bucketName, + final Resource resource, + final Set blockedItems) { + boolean result = !blockedItems.contains(resource.getName()); + if ( result && deciders != null && !deciders.isEmpty() ) { + for(int i=deciders.size()-1;i>=0;i--) { + final InheritanceDecision decision = deciders.get(i).decide(resource, bucketName); + if ( decision == InheritanceDecision.EXCLUDE ) { + log.trace("- Block resource collection inheritance for bucket {}, resource {} because {} retruned EXCLUDE.", + bucketName, resource.getPath(), deciders.get(i)); + result = false; + break; + } else if ( decision == InheritanceDecision.BLOCK ) { + log.trace("- Block resource collection inheritance for bucket {}, resource {} because {} retruned BLOCK.", + bucketName, resource.getPath(), deciders.get(i)); + result = false; + blockedItems.add(resource.getName()); + break; + } + } + } + return result; + } + + private Collection getResourceCollectionInternal(final Collection bucketNames, final String configName, + Iterator paths, ResourceResolver resourceResolver) { + + final Map result = new LinkedHashMap<>(); + final List deciders = this.collectionInheritanceDeciders; + final Set blockedItems = new HashSet<>(); + + boolean inherit = false; + while (paths.hasNext()) { + final String path = paths.next(); + + Resource item = null; + String bucketNameUsed = null; + for (String bucketName : bucketNames) { + String name = bucketName + "/" + configName; + String configPath = buildResourcePath(path, name); + item = resourceResolver.getResource(configPath); + if (item != null) { + bucketNameUsed = bucketName; + break; + } + else { + log.trace("- No collection parent resource found: {}", configPath); + } + } + + if (item != null) { + log.trace("o Check children of collection parent resource: {}", item.getPath()); + if (item.hasChildren()) { + for (Resource child : item.getChildren()) { + if (isValidResourceCollectionItem(child) + && !result.containsKey(child.getName()) + && include(deciders, bucketNameUsed, child, blockedItems)) { + log.trace("+ Found collection resource item {}", child.getPath()); + result.put(child.getName(), child); + } + } + } + + // check collection inheritance mode on current level - should we check on next-highest level as well? + final ValueMap valueMap = item.getValueMap(); + inherit = PropertyUtil.getBooleanValueAdditionalKeys(valueMap, PROPERTY_CONFIG_COLLECTION_INHERIT, + config.configCollectionInheritancePropertyNames()); + if (!inherit) { + break; + } + } + } + + return result.values(); + } + + @Override + public Collection getResourceCollection(@NotNull final Resource contentResource, @NotNull final Collection bucketNames, @NotNull final String configName) { + if (!isEnabledAndParamsValid(contentResource, bucketNames, configName)) { + return null; + } + Iterator paths = getResolvePaths(contentResource, bucketNames); + Collection result = getResourceCollectionInternal(bucketNames, configName, paths, contentResource.getResourceResolver()); + if (!result.isEmpty()) { + return result; + } + else { + return null; + } + } + + @SuppressWarnings("unchecked") + @Override + public Collection> getResourceCollectionInheritanceChain(@NotNull final Resource contentResource, + @NotNull final Collection bucketNames, @NotNull final String configName) { + if (!isEnabledAndParamsValid(contentResource, bucketNames, configName)) { + return null; + } + final ResourceResolver resourceResolver = contentResource.getResourceResolver(); + final List paths = IteratorUtils.toList(getResolvePaths(contentResource, bucketNames)); + + // get resource collection with respect to collection inheritance + Collection resourceCollection = getResourceCollectionInternal(bucketNames, configName, paths.iterator(), resourceResolver); + + // get inheritance chain for each item found + // yes, this resolves the closest item twice, but is the easiest solution to combine both logic aspects + Iterator> result = IteratorUtils.transformedIterator(resourceCollection.iterator(), new Transformer() { + @Override + public Object transform(Object input) { + Resource item = (Resource)input; + return getResourceInheritanceChainInternal(bucketNames, configName + "/" + item.getName(), paths.iterator(), resourceResolver); + } + }); + if (result.hasNext()) { + return IteratorUtils.toList(result); + } + else { + return null; + } + } + + private boolean isValidResourceCollectionItem(Resource resource) { + // do not include jcr:content nodes in resource collection list + return !StringUtils.equals(resource.getName(), "jcr:content"); + } + + @Override + public String getResourcePath(@NotNull Resource contentResource, @NotNull String bucketName, @NotNull String configName) { + if (!isEnabledAndParamsValid(contentResource, Collections.singleton(bucketName), configName)) { + return null; + } + String name = bucketName + "/" + configName; + + Iterator configPaths = this.findConfigRefs(contentResource, Collections.singleton(bucketName)); + if (configPaths.hasNext()) { + String configPath = buildResourcePath(configPaths.next(), name); + log.trace("+ Building configuration path for name '{}' for resource {}: {}", name, contentResource.getPath(), configPath); + return configPath; + } + else { + log.trace("- No configuration path for name '{}' found for resource {}", name, contentResource.getPath()); + return null; + } + } + + @Override + public String getResourceCollectionParentPath(@NotNull Resource contentResource, @NotNull String bucketName, @NotNull String configName) { + return getResourcePath(contentResource, bucketName, configName); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/DefaultContextPathStrategy.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/DefaultContextPathStrategy.java new file mode 100644 index 000000000..d925f66cc --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/def/DefaultContextPathStrategy.java @@ -0,0 +1,170 @@ +/* + * 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.caconfig.resource.impl.def; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; + +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service = ContextPathStrategy.class) +@Designate(ocd=DefaultContextPathStrategy.Config.class) +public class DefaultContextPathStrategy implements ContextPathStrategy { + + @ObjectClassDefinition(name="Apache Sling Context-Aware Configuration Default Context Path Strategy", + description="Detects context path by existence of " + PROPERTY_CONFIG_REF + " properties.") + public static @interface Config { + + @AttributeDefinition(name="Enabled", + description = "Enable this context path strategy.") + boolean enabled() default true; + + @AttributeDefinition(name="Config ref. resource names", + description = "Names of resource to try to look up " + PROPERTY_CONFIG_REF + " property in. If list is empty only current resource is checked." + + " If the list is not empty than only those listed resources are used for look up. If you want to include the current resource you can use a dot for the value.") + String[] configRefResourceNames(); + + @AttributeDefinition(name="Config ref. property names", + description = "Additional property names to " + PROPERTY_CONFIG_REF + " to look up a configuration reference. The names are used in the order defined, " + + "always starting with " + PROPERTY_CONFIG_REF + ". Once a property with a value is found, that value is used and the following property names are skipped.") + String[] configRefPropertyNames(); + + @AttributeDefinition(name = "Service Ranking", + description = "Priority of persistence strategy (higher = higher priority).") + int service_ranking() default 0; + } + + private static final Logger log = LoggerFactory.getLogger(DefaultContextPathStrategy.class); + + private volatile Config config; + + @Activate + private void activate(Config config) { + this.config = config; + } + + @Override + public @NotNull Iterator findContextResources(@NotNull Resource resource) { + if (!config.enabled()) { + return Collections.emptyIterator(); + } + return new ConfigResourceIterator(resource); + } + + /** + * Searches the resource hierarchy upwards for all context and returns the root resource for each of them. + */ + private class ConfigResourceIterator implements Iterator { + + private ContextResource next; + + public ConfigResourceIterator(Resource startResource) { + next = findNextContextResource(startResource); + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public ContextResource next() { + if (next == null) { + throw new NoSuchElementException(); + } + ContextResource result = next; + next = findNextContextResource(next.getResource().getParent()); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Find next configuration context root for given resource. + * @param startResource Resource to start searching + * @return Next resource with sling:configRef property or null if none found. + */ + private ContextResource findNextContextResource(Resource startResource) { + // start at resource, go up + Resource resource = startResource; + + while (resource != null) { + String configRef = getConfigRef(resource); + if (configRef != null) { + log.trace("+ Found context path {}, configRef {}", resource.getPath(), configRef); + return new ContextResource(resource, configRef, config.service_ranking()); + } + // if getParent() returns null, stop + resource = resource.getParent(); + } + + // if hit root and nothing found, return null + return null; + } + + private String getConfigRefValue(final Resource resource) { + final ValueMap map = resource.getValueMap(); + String val = map.get(PROPERTY_CONFIG_REF, String.class); + if ( val == null && !ArrayUtils.isEmpty(config.configRefPropertyNames()) ) { + for(final String name : config.configRefPropertyNames()) { + val = map.get(name, String.class); + if ( val != null ) { + break; + } + } + } + return val; + } + + private String getConfigRef(final Resource resource) { + if (ArrayUtils.isEmpty(config.configRefResourceNames())) { + return getConfigRefValue(resource); + } + for (final String name : config.configRefResourceNames()) { + final Resource lookupResource = resource.getChild(name); + if (lookupResource != null) { + String configRef = getConfigRefValue(lookupResource); + if (configRef != null) { + return configRef; + } + } + } + return null; + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ConfigNameUtil.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ConfigNameUtil.java new file mode 100644 index 000000000..63ab2cc60 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ConfigNameUtil.java @@ -0,0 +1,91 @@ +/* + * 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.caconfig.resource.impl.util; + +import java.util.Collection; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * Helper methods for configuration names. + */ +public final class ConfigNameUtil { + + private ConfigNameUtil() { + // static methods only + } + + /** + * Check if the config name is valid. + * @param configName The name + * @return {@code true} if it is valid + */ + public static boolean isValid(final String configName) { + return !StringUtils.isBlank(configName) + && !StringUtils.startsWith(configName, "/") + && !StringUtils.contains(configName, "../"); + } + + /** + * Check if the config name is valid. + * @param configNames The names + * @return {@code true} if it is valid + */ + public static boolean isValid(final Collection configNames) { + if (configNames == null) { + return false; + } + for (String configName : configNames) { + if (!isValid(configName)) { + return false; + } + } + return true; + } + + /** + * Ensure that the config name is valid. + * @param configName The name + * @throws IllegalArgumentException if the config name is not valid + */ + public static void ensureValidConfigName(final String configName) { + if (!isValid(configName)) { + throw new IllegalArgumentException("Invalid configuration name: " + configName); + } + } + + /** + * Returns all partial combinations like: a, a/b, a/b/c from config name a/b/c/d + * @param configName Config name + * @return All partial combinations + */ + public static String[] getAllPartialConfigNameVariations(String configName) { + String[] configNameParts = StringUtils.splitPreserveAllTokens(configName, "/"); + if (configNameParts.length < 2) { + return ArrayUtils.EMPTY_STRING_ARRAY; + } + String[] partialConfigNameVariations = new String[configNameParts.length - 1]; + for (int i = 0; i < configNameParts.length - 1; i++) { + partialConfigNameVariations[i] = StringUtils.join(ArrayUtils.subarray(configNameParts, 0, i + 1), "/"); + } + return partialConfigNameVariations; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/MapUtil.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/MapUtil.java new file mode 100644 index 000000000..c2f87faf7 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/MapUtil.java @@ -0,0 +1,77 @@ +/* + * 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.caconfig.resource.impl.util; + +import java.lang.reflect.Array; +import java.util.Iterator; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +public final class MapUtil { + + private MapUtil() { + // static methods only + } + + /** + * Produce trace output for properties map. + * @param properties Properties + * @return Debug output + */ + public static final String traceOutput(Map properties) { + SortedSet propertyNames = new TreeSet<>(properties.keySet()); + StringBuilder sb = new StringBuilder(); + sb.append("{"); + Iterator propertyNameIterator = propertyNames.iterator(); + while (propertyNameIterator.hasNext()) { + String propertyName = propertyNameIterator.next(); + sb.append(propertyName).append(": "); + appendValue(sb, properties.get(propertyName)); + if (propertyNameIterator.hasNext()) { + sb.append(", "); + } + } + sb.append("}"); + return sb.toString(); + } + + private static void appendValue(StringBuilder sb, Object value) { + if (value == null) { + sb.append("null"); + } + else if (value.getClass().isArray()) { + sb.append("["); + for (int i = 0; i < Array.getLength(value); i++) { + if (i > 0) { + sb.append(","); + } + appendValue(sb, Array.get(value, i)); + } + sb.append("]"); + } + else if (value instanceof String) { + sb.append("'").append(value.toString()).append("'"); + } + else { + sb.append(value.toString()); + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PathEliminateDuplicatesIterator.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PathEliminateDuplicatesIterator.java new file mode 100644 index 000000000..d55107d09 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PathEliminateDuplicatesIterator.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.caconfig.resource.impl.util; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.iterators.FilterIterator; + +/** + * Iterator that eliminates duplicate paths. + */ +public class PathEliminateDuplicatesIterator extends FilterIterator { + + public PathEliminateDuplicatesIterator(Iterator iterator) { + super(iterator, new Predicate() { + private final Set resourcePaths = new HashSet<>(); + + @Override + public boolean evaluate(String object) { + return resourcePaths.add(object); + } + + }); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PathParentExpandIterator.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PathParentExpandIterator.java new file mode 100644 index 000000000..3cb62b35c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PathParentExpandIterator.java @@ -0,0 +1,70 @@ +/* + * 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.caconfig.resource.impl.util; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Queue; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.ResourceUtil; + +/** + * Expands all paths from the iterator with their parent paths up to the given root paths. + * The expanded path are added directly after each given path. + * Duplicates are not eliminated. + */ +public class PathParentExpandIterator implements Iterator { + + private final String rootPath; + private final Iterator paths; + private final Queue expandedPaths = new LinkedList<>(); + + public PathParentExpandIterator(String rootPath, Iterator paths) { + this.rootPath = rootPath; + this.paths = paths; + } + + @Override + public boolean hasNext() { + return paths.hasNext() || !expandedPaths.isEmpty(); + } + + @Override + public String next() { + if (expandedPaths.isEmpty()) { + expandPaths(paths.next()); + } + return expandedPaths.remove(); + } + + private void expandPaths(String path) { + expandedPaths.add(path); + String parentPath = ResourceUtil.getParent(path); + if (parentPath != null && !StringUtils.equals(parentPath, rootPath)) { + expandPaths(parentPath); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PropertyUtil.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PropertyUtil.java new file mode 100644 index 000000000..248d1ed5d --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/PropertyUtil.java @@ -0,0 +1,50 @@ +/* + * 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.caconfig.resource.impl.util; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.sling.api.resource.ValueMap; + +public final class PropertyUtil { + + private PropertyUtil() { + // static methods only + } + + /** + * Get boolean value from value map with key, or with alternative keys if not set. + * @param valueMap Value map + * @param key Primary key + * @param additionalKeys Alternative keys + * @return Value + */ + public static boolean getBooleanValueAdditionalKeys(final ValueMap valueMap, final String key, final String[] additionalKeys) { + Boolean result = valueMap.get(key, Boolean.class); + if ( result == null && !ArrayUtils.isEmpty(additionalKeys) ) { + for(final String name : additionalKeys) { + result = valueMap.get(name, Boolean.class); + if ( result != null ) { + break; + } + } + } + return result == null ? false : result.booleanValue(); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ResourceEliminateDuplicatesIterator.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ResourceEliminateDuplicatesIterator.java new file mode 100644 index 000000000..bf9522446 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ResourceEliminateDuplicatesIterator.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.caconfig.resource.impl.util; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.collections4.Predicate; +import org.apache.commons.collections4.iterators.FilterIterator; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.resource.spi.ContextResource; + +/** + * Iterator that eliminates duplicate resources (having same path). + */ +public class ResourceEliminateDuplicatesIterator extends FilterIterator { + + public ResourceEliminateDuplicatesIterator(Iterator iterator) { + super(iterator, new Predicate() { + private final Set keys = new HashSet<>(); + + @Override + public boolean evaluate(ContextResource contextResource) { + String key = contextResource.getResource().getPath() + "#" + StringUtils.defaultString(contextResource.getConfigRef()); + return keys.add(key); + } + + }); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ResourcePathCollatingIterator.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ResourcePathCollatingIterator.java new file mode 100644 index 000000000..fb31ee4f6 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/main/java/org/apache/sling/caconfig/resource/impl/util/ResourcePathCollatingIterator.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.caconfig.resource.impl.util; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.collections4.iterators.CollatingIterator; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.resource.spi.ContextResource; + +/** + * Expected a list of iterators containing paths, where each path is a direct or indirect parent of the previous one + * (= sorted by path hierarchy starting with the deepest path). + * Result is a new iterator with all resources combined from all iterators in the same order, duplicates not eliminated. + */ +public class ResourcePathCollatingIterator extends CollatingIterator { + + private static Comparator PATH_LENGTH_COMPARATOR = new Comparator() { + @Override + public int compare(ContextResource o1, ContextResource o2) { + Integer length1 = o1.getResource().getPath().length(); + Integer length2 = o2.getResource().getPath().length(); + int result = length2.compareTo(length1); + if (result == 0) { + Integer ranking1 = o1.getServiceRanking(); + Integer ranking2 = o2.getServiceRanking(); + result = ranking2.compareTo(ranking1); + if (result == 0) { + result = StringUtils.defaultString(o1.getConfigRef()).compareTo(StringUtils.defaultString(o2.getConfigRef())); + } + } + return result; + } + }; + + @SuppressWarnings("unchecked") + public ResourcePathCollatingIterator(List> iterator) { + super(PATH_LENGTH_COMPARATOR, (Collection)iterator); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/AllTypesConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/AllTypesConfig.java new file mode 100644 index 000000000..77d4ff1ed --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/AllTypesConfig.java @@ -0,0 +1,77 @@ +/* + * 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.caconfig.example; + +import static org.apache.sling.caconfig.example.AllTypesDefaults.BOOL_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.BOOL_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.DOUBLE_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.DOUBLE_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.INT_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.INT_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.LONG_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.LONG_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.STRING_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.STRING_DEFAULT_2; + +import org.apache.sling.caconfig.annotation.Configuration; + +@Configuration +public @interface AllTypesConfig { + + String stringParam(); + + String stringParamWithDefault() default STRING_DEFAULT; + + int intParam(); + + int intParamWithDefault() default INT_DEFAULT; + + long longParam(); + + long longParamWithDefault() default LONG_DEFAULT; + + double doubleParam(); + + double doubleParamWithDefault() default DOUBLE_DEFAULT; + + boolean boolParam(); + + boolean boolParamWithDefault() default BOOL_DEFAULT; + + String[] stringArrayParam(); + + String[] stringArrayParamWithDefault() default { STRING_DEFAULT, STRING_DEFAULT_2 }; + + int[] intArrayParam(); + + int[] intArrayParamWithDefault() default { INT_DEFAULT, INT_DEFAULT_2 }; + + long[] longArrayParam(); + + long[] longArrayParamWithDefault() default { LONG_DEFAULT, LONG_DEFAULT_2 }; + + double[] doubleArrayParam(); + + double[] doubleArrayParamWithDefault() default { DOUBLE_DEFAULT, DOUBLE_DEFAULT_2 }; + + boolean[] boolArrayParam(); + + boolean[] boolArrayParamWithDefault() default { BOOL_DEFAULT, BOOL_DEFAULT_2 }; + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/AllTypesDefaults.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/AllTypesDefaults.java new file mode 100644 index 000000000..437d32677 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/AllTypesDefaults.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.example; + +public final class AllTypesDefaults { + + private AllTypesDefaults() { + // constants only + } + + public static final String STRING_DEFAULT = "myDefault"; + + public static final String STRING_DEFAULT_2 = "myDefault2"; + + public static final int INT_DEFAULT = 12345; + + public static final int INT_DEFAULT_2 = 23456; + + public static final long LONG_DEFAULT = 1234567890L; + + public static final long LONG_DEFAULT_2 = 2345678901L; + + public static final double DOUBLE_DEFAULT = 123.456d; + + public static final double DOUBLE_DEFAULT_2 = 234.567d; + + public static final boolean BOOL_DEFAULT = true; + + public static final boolean BOOL_DEFAULT_2 = false; + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/IllegalTypesConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/IllegalTypesConfig.java new file mode 100644 index 000000000..ef72a6288 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/IllegalTypesConfig.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; + +/** + * All types in the example configuration class are *not* supported. + */ +@Configuration +public @interface IllegalTypesConfig { + + Class clazz(); + + byte byteSingle(); + + byte[] byteArray(); + + short shortSingle(); + + short[] shortArray(); + + float floatSingle(); + + float[] floatArray(); + + char charSingle(); + + char[] charArray(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListConfig.java new file mode 100644 index 000000000..bc337f87d --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListConfig.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; + +@Configuration(collection = true) +public @interface ListConfig { + + String stringParam(); + + int intParam(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListDoubleNestedConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListDoubleNestedConfig.java new file mode 100644 index 000000000..44051866e --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListDoubleNestedConfig.java @@ -0,0 +1,32 @@ +/* + * 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.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; + +@Configuration(collection = true) +public @interface ListDoubleNestedConfig { + + String stringParam(); + + int intParam(); + + ListNestedConfig[] subListNestedConfig(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListNestedConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListNestedConfig.java new file mode 100644 index 000000000..c815fc99a --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/ListNestedConfig.java @@ -0,0 +1,32 @@ +/* + * 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.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; + +@Configuration(collection = true) +public @interface ListNestedConfig { + + String stringParam(); + + int intParam(); + + ListConfig[] subListConfig(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/MetadataSimpleConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/MetadataSimpleConfig.java new file mode 100644 index 000000000..f2588a38a --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/MetadataSimpleConfig.java @@ -0,0 +1,42 @@ +/* + * 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.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; +import org.apache.sling.caconfig.annotation.Property; + +@Configuration(name = "simpleConfig", + label = "Simple configuration", + description = "This is a configuration example with additional metadata.", + property = { + "param1=value1", + "param2=123" + }) +public @interface MetadataSimpleConfig { + + @Property(label = "String Param", description = "Enter strings here.", property = "p1=v1", order = 1) + String stringParam(); + + @Property(label = "Integer Param", order = 2) + int intParam() default 5; + + @Property(order = 3) + boolean boolParam(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/NestedConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/NestedConfig.java new file mode 100644 index 000000000..aca84fd5e --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/NestedConfig.java @@ -0,0 +1,34 @@ +/* + * 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.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; + +@Configuration +public @interface NestedConfig { + + String stringParam(); + + SimpleConfig subConfig(); + + ListConfig[] subListConfig(); + + WithoutAnnotationConfig subConfigWithoutAnnotation(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SimpleConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SimpleConfig.java new file mode 100644 index 000000000..23e2758ea --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SimpleConfig.java @@ -0,0 +1,32 @@ +/* + * 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.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; + +@Configuration +public @interface SimpleConfig { + + String stringParam(); + + int intParam() default 5; + + boolean boolParam(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SimpleSlingModel.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SimpleSlingModel.java new file mode 100644 index 000000000..93ad760f1 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SimpleSlingModel.java @@ -0,0 +1,37 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.example; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.models.annotations.Model; +import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; + +@Model(adaptables = Resource.class) +public interface SimpleSlingModel { + + @ValueMapValue(name="stringParam") + String getStringParam(); + + @ValueMapValue(name="intParam", optional = true) + int getIntParam(); + + @ValueMapValue(name="boolParam", optional = true) + boolean getBoolParam(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SpecialNamesConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SpecialNamesConfig.java new file mode 100644 index 000000000..4d54b3ea7 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/SpecialNamesConfig.java @@ -0,0 +1,32 @@ +/* + * 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.caconfig.example; + +import org.apache.sling.caconfig.annotation.Configuration; + +@Configuration +public @interface SpecialNamesConfig { + + String $stringParam(); + + int int__Param() default 5; + + boolean bool_Param(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/WithoutAnnotationConfig.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/WithoutAnnotationConfig.java new file mode 100644 index 000000000..ac21b86ac --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/example/WithoutAnnotationConfig.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.example; + +/** + * Annotation classes without the proper Configuration annotation are not supported. + */ +public @interface WithoutAnnotationConfig { + + String stringParam(); + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationBindingsValueProviderTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationBindingsValueProviderTest.java new file mode 100644 index 000000000..c1cde2697 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationBindingsValueProviderTest.java @@ -0,0 +1,139 @@ +/* + * 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.caconfig.impl; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; +import java.util.SortedSet; + +import javax.script.Bindings; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.impl.metadata.ConfigurationMetadataProviderMultiplexerImpl; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSortedSet; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("unchecked") +public class ConfigurationBindingsValueProviderTest { + + private final static ValueMap VALUEMAP = new ValueMapDecorator( + ImmutableMap. of("param1", "value1")); + + private static final SortedSet CONFIG_NAMES = ImmutableSortedSet.of("name1", "name.2"); + + @Rule + public SlingContext context = new SlingContext(); + + @Mock + private SlingHttpServletRequest request; + @Mock + private Resource resource; + @Mock + private Bindings bindings; + @Mock + private ConfigurationBuilder configBuilder; + @Mock + private ConfigurationMetadataProvider configMetadataProvider; + + private ConfigurationBindingsValueProvider underTest; + + @Before + public void setUp() { + context.registerInjectActivateService(new ConfigurationMetadataProviderMultiplexerImpl()); + context.registerService(ConfigurationMetadataProvider.class, configMetadataProvider); + when(configMetadataProvider.getConfigurationNames()).thenReturn(CONFIG_NAMES); + + when(bindings.containsKey(SlingBindings.REQUEST)).thenReturn(true); + when(bindings.get(SlingBindings.REQUEST)).thenReturn(request); + when(request.getResource()).thenReturn(resource); + when(resource.adaptTo(ConfigurationBuilder.class)).thenReturn(configBuilder); + when(configBuilder.name(anyString())).thenReturn(configBuilder); + when(configBuilder.asValueMap()).thenReturn(VALUEMAP); + when(configBuilder.asValueMapCollection()).thenReturn(ImmutableList.of(VALUEMAP)); + + when(configMetadataProvider.getConfigurationMetadata("name1")).thenReturn( + new ConfigurationMetadata("name1", ImmutableList.>of(), false)); + when(configMetadataProvider.getConfigurationMetadata("name.2")).thenReturn( + new ConfigurationMetadata("name.2", ImmutableList.>of(), true)); + } + + @Test + public void testWithConfig() { + underTest = context.registerInjectActivateService(new ConfigurationBindingsValueProvider(), "enabled", true); + underTest.addBindings(bindings); + + ArgumentCaptor> configMapCaptor = ArgumentCaptor.forClass(Map.class); + verify(bindings).put(eq(ConfigurationBindingsValueProvider.BINDING_VARIABLE), configMapCaptor.capture()); + + Map configMap = configMapCaptor.getValue(); + assertEquals(CONFIG_NAMES, configMap.keySet()); + assertEquals(VALUEMAP, configMap.get("name1")); + assertEquals(ImmutableList.of(VALUEMAP), configMap.get("name.2")); + } + + @Test + public void testNoResource() { + when(request.getResource()).thenReturn(null); + underTest = context.registerInjectActivateService(new ConfigurationBindingsValueProvider(), "enabled", true); + underTest.addBindings(bindings); + verify(bindings, never()).put(anyString(), any(Object.class)); + } + + @Test + public void testNoRequest() { + underTest = context.registerInjectActivateService(new ConfigurationBindingsValueProvider(), "enabled", true); + when(bindings.containsKey(SlingBindings.REQUEST)).thenReturn(false); + underTest.addBindings(bindings); + verify(bindings, never()).put(anyString(), any(Object.class)); + } + + @Test + public void testDisabled() { + underTest = context.registerInjectActivateService(new ConfigurationBindingsValueProvider(), "enabled", false); + underTest.addBindings(bindings); + verify(bindings, never()).put(anyString(), any(Object.class)); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationBuilderAdapterFactoryTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationBuilderAdapterFactoryTest.java new file mode 100644 index 000000000..609d7eb1c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationBuilderAdapterFactoryTest.java @@ -0,0 +1,66 @@ +/* + * 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.caconfig.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.example.SimpleConfig; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +@SuppressWarnings("null") +public class ConfigurationBuilderAdapterFactoryTest { + + @Rule + public SlingContext context = new SlingContext(); + + private Resource site1Page1; + + @Before + public void setUp() { + ConfigurationTestUtils.registerConfigurationResolver(context); + context.registerInjectActivateService(new ConfigurationBuilderAdapterFactory()); + + // config resource + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.SimpleConfig", + "stringParam", "configValue1", + "intParam", 111, + "boolParam", true); + + // content resources + context.build().resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/content/site1"); + site1Page1 = context.create().resource("/content/site1/page1"); + } + + @Test + public void testGetAdapter() { + ConfigurationBuilder cfgBuilder = site1Page1.adaptTo(ConfigurationBuilder.class); + SimpleConfig cfg = cfgBuilder.as(SimpleConfig.class); + + assertEquals("configValue1", cfg.stringParam()); + assertEquals(111, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImplTest.java new file mode 100644 index 000000000..4e354a064 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImplTest.java @@ -0,0 +1,125 @@ +/* + * 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.caconfig.impl; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.util.Iterator; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; + +import com.google.common.collect.ImmutableList; + +@RunWith(MockitoJUnitRunner.class) +public class ConfigurationInheritanceStrategyMultiplexerImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Mock + private Resource resource1; + @Mock + private Resource resource2; + + private Iterator resources; + private ConfigurationInheritanceStrategy underTest; + + @Before + public void setUp() { + resources = ImmutableList.of(resource1, resource2).iterator(); + underTest = context.registerInjectActivateService(new ConfigurationInheritanceStrategyMultiplexerImpl()); + } + + @Test + public void testWithNoStrategies() { + assertNull(underTest.getResource(resources)); + } + + @SuppressWarnings("unchecked") + @Test + public void testWithOneStrategy() { + ConfigurationInheritanceStrategy strategy = mock(ConfigurationInheritanceStrategy.class); + + when(strategy.getResource((Iterator)any())).thenAnswer(new Answer() { + @Override + public Resource answer(InvocationOnMock invocation) throws Throwable { + Iterator items = invocation.getArgument(0); + return items.next(); + } + }); + + context.registerService(ConfigurationInheritanceStrategy.class, strategy); + + assertSame(resource1, underTest.getResource(resources)); + } + + @SuppressWarnings("unchecked") + @Test + public void testWithMultipleStrategies() { + ConfigurationInheritanceStrategy strategy1 = mock(ConfigurationInheritanceStrategy.class); + ConfigurationInheritanceStrategy strategy2 = mock(ConfigurationInheritanceStrategy.class); + ConfigurationInheritanceStrategy strategy3 = mock(ConfigurationInheritanceStrategy.class); + + when(strategy1.getResource((Iterator)any())).thenAnswer(new Answer() { + @Override + public Resource answer(InvocationOnMock invocation) throws Throwable { + Iterator items = invocation.getArgument(0); + while (items.hasNext()) { + items.next(); + } + return null; + } + }); + when(strategy2.getResource((Iterator)any())).thenAnswer(new Answer() { + @Override + public Resource answer(InvocationOnMock invocation) throws Throwable { + Iterator items = invocation.getArgument(0); + return items.next(); + } + }); + + context.registerService(ConfigurationInheritanceStrategy.class, strategy1); + context.registerService(ConfigurationInheritanceStrategy.class, strategy2); + context.registerService(ConfigurationInheritanceStrategy.class, strategy3); + + assertSame(resource1, underTest.getResource(resources)); + + verify(strategy1, times(1)).getResource((Iterator)any()); + verify(strategy2, times(1)).getResource((Iterator)any()); + verifyNoMoreInteractions(strategy3); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationProxyCachingInvocationHandlerTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationProxyCachingInvocationHandlerTest.java new file mode 100644 index 000000000..088852196 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationProxyCachingInvocationHandlerTest.java @@ -0,0 +1,84 @@ +/* + * 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.caconfig.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +import org.apache.sling.caconfig.impl.ConfigurationProxy.CachingInvocationHandler; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ConfigurationProxyCachingInvocationHandlerTest { + + private InvocationHandler underTest; + + @Mock + private InvocationHandler invocationHandler; + private Object testObject; + private Method testMethod; + + @Before + public void setUp() throws Exception { + underTest = new CachingInvocationHandler(invocationHandler); + testObject = new Object(); + testMethod = Object.class.getMethod("toString"); + } + + @Test + public void testCacheMiss() throws Throwable { + when(invocationHandler.invoke(testObject, testMethod, null)).thenReturn("value1"); + assertEquals("value1", underTest.invoke(testObject, testMethod, null)); + verify(invocationHandler, times(1)).invoke(testObject, testMethod, null); + } + + @Test + public void testCacheMissNull() throws Throwable { + when(invocationHandler.invoke(testObject, testMethod, null)).thenReturn(null); + assertNull(underTest.invoke(testObject, testMethod, null)); + verify(invocationHandler, times(1)).invoke(testObject, testMethod, null); + } + + @Test + public void testCacheHit() throws Throwable { + when(invocationHandler.invoke(testObject, testMethod, null)).thenReturn("value1"); + assertEquals("value1", underTest.invoke(testObject, testMethod, null)); + assertEquals("value1", underTest.invoke(testObject, testMethod, null)); + verify(invocationHandler, times(1)).invoke(testObject, testMethod, null); + } + + @Test + public void testCacheHitNull() throws Throwable { + when(invocationHandler.invoke(testObject, testMethod, null)).thenReturn(null); + assertNull(underTest.invoke(testObject, testMethod, null)); + assertNull(underTest.invoke(testObject, testMethod, null)); + verify(invocationHandler, times(1)).invoke(testObject, testMethod, null); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationProxyTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationProxyTest.java new file mode 100644 index 000000000..774b341de --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationProxyTest.java @@ -0,0 +1,271 @@ +/* + * 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.caconfig.impl; + +import static org.apache.sling.caconfig.example.AllTypesDefaults.BOOL_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.BOOL_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.DOUBLE_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.DOUBLE_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.INT_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.INT_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.LONG_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.LONG_DEFAULT_2; +import static org.apache.sling.caconfig.example.AllTypesDefaults.STRING_DEFAULT; +import static org.apache.sling.caconfig.example.AllTypesDefaults.STRING_DEFAULT_2; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; + +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.example.AllTypesConfig; +import org.apache.sling.caconfig.example.IllegalTypesConfig; +import org.apache.sling.caconfig.example.ListConfig; +import org.apache.sling.caconfig.example.NestedConfig; +import org.apache.sling.caconfig.example.SimpleConfig; +import org.apache.sling.caconfig.example.SpecialNamesConfig; +import org.apache.sling.caconfig.example.WithoutAnnotationConfig; +import org.apache.sling.caconfig.impl.ConfigurationProxy.ChildResolver; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Rule; +import org.junit.Test; + +public class ConfigurationProxyTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Test + public void testNonExistingConfig_AllTypes() { + AllTypesConfig cfg = get(null, AllTypesConfig.class); + + assertNull(cfg.stringParam()); + assertEquals(STRING_DEFAULT, cfg.stringParamWithDefault()); + assertEquals(0, cfg.intParam()); + assertEquals(INT_DEFAULT, cfg.intParamWithDefault()); + assertEquals(0L, cfg.longParam()); + assertEquals(LONG_DEFAULT, cfg.longParamWithDefault()); + assertEquals(0d, cfg.doubleParam(), 0.001d); + assertEquals(DOUBLE_DEFAULT, cfg.doubleParamWithDefault(), 0.001d); + assertEquals(false, cfg.boolParam()); + assertEquals(BOOL_DEFAULT, cfg.boolParamWithDefault()); + + assertArrayEquals(new String[0], cfg.stringArrayParam()); + assertArrayEquals(new String[] {STRING_DEFAULT,STRING_DEFAULT_2}, cfg.stringArrayParamWithDefault()); + assertArrayEquals(new int[0], cfg.intArrayParam()); + assertArrayEquals(new int[] {INT_DEFAULT,INT_DEFAULT_2}, cfg.intArrayParamWithDefault()); + assertArrayEquals(new long[0], cfg.longArrayParam()); + assertArrayEquals(new long[] {LONG_DEFAULT,LONG_DEFAULT_2}, cfg.longArrayParamWithDefault()); + assertArrayEquals(new double[0],cfg.doubleArrayParam(), 0.001d); + assertArrayEquals(new double[] {DOUBLE_DEFAULT,DOUBLE_DEFAULT_2}, cfg.doubleArrayParamWithDefault(), 0.001d); + assertArrayEquals(new boolean[0], cfg.boolArrayParam()); + assertArrayEquals(new boolean[] {BOOL_DEFAULT,BOOL_DEFAULT_2}, cfg.boolArrayParamWithDefault()); + } + + @Test + public void testNonExistingConfig_Nested() { + NestedConfig cfg = get(null, NestedConfig.class); + + assertNull(cfg.stringParam()); + + SimpleConfig subConfig = cfg.subConfig(); + assertNull(subConfig.stringParam()); + assertEquals(5, subConfig.intParam()); + assertFalse(subConfig.boolParam()); + + assertArrayEquals(new ListConfig[0], cfg.subListConfig()); + } + + @Test + public void testConfig_AllTypes() { + Resource resource = context.build().resource("/test", + "stringParam", "configValue2", + "intParam", 222, + "longParam", 3456L, + "doubleParam", 0.123d, + "boolParam", true, + "stringArrayParam", new String[] {STRING_DEFAULT_2,STRING_DEFAULT}, + "intArrayParam", new int[] {INT_DEFAULT_2}, + "longArrayParam", new long[] {LONG_DEFAULT_2,LONG_DEFAULT}, + "doubleArrayParam", new double[] {DOUBLE_DEFAULT_2}, + "boolArrayParam", new boolean[] {BOOL_DEFAULT_2,BOOL_DEFAULT}) + .getCurrentParent(); + AllTypesConfig cfg = get(resource, AllTypesConfig.class); + + assertEquals("configValue2", cfg.stringParam()); + assertEquals(STRING_DEFAULT, cfg.stringParamWithDefault()); + assertEquals(222, cfg.intParam()); + assertEquals(INT_DEFAULT, cfg.intParamWithDefault()); + assertEquals(3456L, cfg.longParam()); + assertEquals(LONG_DEFAULT, cfg.longParamWithDefault()); + assertEquals(0.123d, cfg.doubleParam(), 0.001d); + assertEquals(DOUBLE_DEFAULT, cfg.doubleParamWithDefault(), 0.001d); + assertEquals(true, cfg.boolParam()); + assertEquals(BOOL_DEFAULT, cfg.boolParamWithDefault()); + + assertArrayEquals(new String[] {STRING_DEFAULT_2,STRING_DEFAULT}, cfg.stringArrayParam()); + assertArrayEquals(new String[] {STRING_DEFAULT,STRING_DEFAULT_2}, cfg.stringArrayParamWithDefault()); + assertArrayEquals(new int[] {INT_DEFAULT_2}, cfg.intArrayParam()); + assertArrayEquals(new int[] { INT_DEFAULT, INT_DEFAULT_2}, cfg.intArrayParamWithDefault()); + assertArrayEquals(new long[] {LONG_DEFAULT_2,LONG_DEFAULT}, cfg.longArrayParam()); + assertArrayEquals(new long[] {LONG_DEFAULT,LONG_DEFAULT_2 }, cfg.longArrayParamWithDefault()); + assertArrayEquals(new double[] {DOUBLE_DEFAULT_2}, cfg.doubleArrayParam(), 0.001d); + assertArrayEquals(new double[] {DOUBLE_DEFAULT,DOUBLE_DEFAULT_2}, cfg.doubleArrayParamWithDefault(), 0.001d); + assertArrayEquals(new boolean[] {BOOL_DEFAULT_2,BOOL_DEFAULT}, cfg.boolArrayParam()); + assertArrayEquals(new boolean[] {BOOL_DEFAULT,BOOL_DEFAULT_2}, cfg.boolArrayParamWithDefault()); + } + + @Test + public void testConfig_SpecialNames() { + Resource resource = context.build() + .resource("/test", "stringParam", "configValue2", "int_Param", 222, "bool.Param", true) + .getCurrentParent(); + SpecialNamesConfig cfg = get(resource, SpecialNamesConfig.class); + + assertEquals("configValue2", cfg.$stringParam()); + assertEquals(222, cfg.int__Param()); + assertEquals(true, cfg.bool_Param()); + } + + @Test + public void testConfig_Nested() { + context.build().resource("/test", "stringParam", "v1") + .resource("/test/subConfig", "stringParam", "v2", "intParam", 444, "boolParam", true) + .resource("/test/subListConfig/1", "stringParam", "v3.1") + .resource("/test/subListConfig/2", "stringParam", "v3.2") + .resource("/test/subListConfig/3", "stringParam", "v3.3") + .resource("/test/subConfigWithoutAnnotation", "stringParam", "v4"); + + Resource resource = context.resourceResolver().getResource("/test"); + + NestedConfig cfg = get(resource, NestedConfig.class); + + assertEquals("v1", cfg.stringParam()); + + SimpleConfig subConfig = cfg.subConfig(); + assertEquals("v2", subConfig.stringParam()); + assertEquals(444, subConfig.intParam()); + assertEquals(true, subConfig.boolParam()); + + ListConfig[] listConfig = cfg.subListConfig(); + assertEquals(3, listConfig.length); + assertEquals("v3.1", listConfig[0].stringParam()); + assertEquals("v3.2", listConfig[1].stringParam()); + assertEquals("v3.3", listConfig[2].stringParam()); + + WithoutAnnotationConfig subConfigWithoutAnnotation = cfg.subConfigWithoutAnnotation(); + assertEquals("v4", subConfigWithoutAnnotation.stringParam()); + } + + @Test(expected=ConfigurationResolveException.class) + public void testInvalidClassConversion() { + // test with class not supported for configuration mapping + get(null, Rectangle2D.class); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_Class() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.clazz(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_Byte() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.byteSingle(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_ByteArray() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.byteArray(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_Short() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.shortSingle(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_ShortArray() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.shortArray(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_Float() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.floatSingle(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_FloatArray() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.floatArray(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_Char() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.charSingle(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testIllegalTypes_CharArray() { + IllegalTypesConfig cfg = get(null, IllegalTypesConfig.class); + cfg.charArray(); + } + + private T get(Resource resource, Class clazz) { + return ConfigurationProxy.get(resource, clazz, childResolver(resource)); + } + + // simulate simple child resolver without involving ConfigurationResolver implementation + private ChildResolver childResolver(final Resource resource) { + return new ChildResolver() { + @Override + public T getChild(String configName, Class clazz) { + Resource child = resource!=null ? resource.getChild(configName) : null; + return ConfigurationProxy.get(child, clazz, childResolver(child)); + } + @Override + public Collection getChildren(String configName, Class clazz) { + List collection = new ArrayList<>(); + Resource childParent = resource!=null ? resource.getChild(configName) : null; + if (childParent != null) { + for (Resource child : childParent.getChildren()) { + T result = ConfigurationProxy.get(child, clazz, childResolver(child)); + if (result != null) { + collection.add(result); + } + } + } + return collection; + } + }; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverAdaptableTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverAdaptableTest.java new file mode 100644 index 000000000..226cff3ff --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverAdaptableTest.java @@ -0,0 +1,180 @@ +/* + * 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.caconfig.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.example.SimpleSlingModel; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Test {@link ConfigurationResolver} with custom adaptions (in this case: Sling Models) for reading the config. + */ +@SuppressWarnings("null") +public class ConfigurationResolverAdaptableTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResolver underTest; + + private Resource site1Page1; + + @Before + public void setUp() { + underTest = ConfigurationTestUtils.registerConfigurationResolver(context); + + context.addModelsForPackage("org.apache.sling.caconfig.example"); + + // content resources + context.build().resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/content/site1"); + site1Page1 = context.create().resource("/content/site1/page1"); + } + + @Test + public void testNonExistingConfig() { + SimpleSlingModel model = underTest.get(site1Page1).name("sampleName").asAdaptable(SimpleSlingModel.class); + assertNull(model); + } + + @Test + public void testNonExistingConfigCollection() { + Collection propsList = underTest.get(site1Page1).name("sampleList").asAdaptableCollection(SimpleSlingModel.class); + assertTrue(propsList.isEmpty()); + } + + @Test + public void testConfig() { + context.build().resource("/conf/content/site1/sling:configs/sampleName", + "stringParam", "configValue1", + "intParam", 111, + "boolParam", true); + + SimpleSlingModel model = underTest.get(site1Page1).name("sampleName").asAdaptable(SimpleSlingModel.class); + assertEquals("configValue1", model.getStringParam()); + assertEquals(111, model.getIntParam()); + assertEquals(true, model.getBoolParam()); + } + + @Test + public void testConfigCollection() { + context.build().resource("/conf/content/site1/sling:configs/sampleList") + .siblingsMode() + .resource("1", "stringParam", "configValue1.1") + .resource("2", "stringParam", "configValue1.2") + .resource("3", "stringParam", "configValue1.3"); + + Collection propsList = underTest.get(site1Page1).name("sampleList").asAdaptableCollection(SimpleSlingModel.class); + + Iterator propsIterator = propsList.iterator(); + assertEquals("configValue1.1", propsIterator.next().getStringParam()); + assertEquals("configValue1.2", propsIterator.next().getStringParam()); + assertEquals("configValue1.3", propsIterator.next().getStringParam()); + } + + @Test + public void testConfigWithDefaultValues() { + context.registerService(ConfigurationMetadataProvider.class, new DummyConfigurationMetadataProvider("sampleName", + ImmutableMap.of("stringParam", "defValue1", "intParam", 999), false)); + + context.build().resource("/conf/content/site1/sling:configs/sampleName", + "boolParam", true); + + SimpleSlingModel model = underTest.get(site1Page1).name("sampleName").asAdaptable(SimpleSlingModel.class); + assertEquals("defValue1", model.getStringParam()); + assertEquals(999, model.getIntParam()); + assertEquals(true, model.getBoolParam()); + } + + @Test + public void testConfigCollectionWithDefaultValues() { + context.registerService(ConfigurationMetadataProvider.class, new DummyConfigurationMetadataProvider("sampleList", + ImmutableMap.of("intParam", 999), true)); + + context.build().resource("/conf/content/site1/sling:configs/sampleList") + .siblingsMode() + .resource("1", "stringParam", "configValue1.1") + .resource("2", "stringParam", "configValue1.2") + .resource("3", "stringParam", "configValue1.3"); + + List propsList = ImmutableList.copyOf(underTest.get(site1Page1).name("sampleList").asAdaptableCollection(SimpleSlingModel.class)); + + assertEquals("configValue1.1", propsList.get(0).getStringParam()); + assertEquals(999, propsList.get(0).getIntParam()); + assertEquals("configValue1.2", propsList.get(1).getStringParam()); + assertEquals(999, propsList.get(1).getIntParam()); + assertEquals("configValue1.3", propsList.get(2).getStringParam()); + assertEquals(999, propsList.get(2).getIntParam()); + } + + @Test + public void testNonExistingContentResource() { + SimpleSlingModel model = underTest.get(null).name("sampleName").asAdaptable(SimpleSlingModel.class); + assertNull(model); + } + + @Test + public void testNonExistingContentResourceCollection() { + Collection propsList = underTest.get(null).name("sampleList").asAdaptableCollection(SimpleSlingModel.class); + assertTrue(propsList.isEmpty()); + } + + @Test(expected=IllegalArgumentException.class) + public void testNullConfigName() { + underTest.get(site1Page1).name(null).asAdaptable(SimpleSlingModel.class); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidConfigName() { + underTest.get(site1Page1).name("/a/b/c").asAdaptable(SimpleSlingModel.class); + } + + @Test(expected=ConfigurationResolveException.class) + public void testWithoutConfigName() { + underTest.get(site1Page1).asAdaptable(SimpleSlingModel.class); + } + + @Test + public void testAdaptToConfigurationBuilder() { + context.build().resource("/conf/content/site1/sling:configs/sampleName"); + + // make sure not endless loop occurs + ConfigurationBuilder model = underTest.get(site1Page1).name("sampleName").asAdaptable(ConfigurationBuilder.class); + assertNull(model); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverAnnotationClassTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverAnnotationClassTest.java new file mode 100644 index 000000000..beb2122b0 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverAnnotationClassTest.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.caconfig.impl; + +import static org.apache.sling.caconfig.impl.def.ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +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; + +import java.awt.geom.Rectangle2D; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.example.ListConfig; +import org.apache.sling.caconfig.example.ListDoubleNestedConfig; +import org.apache.sling.caconfig.example.ListNestedConfig; +import org.apache.sling.caconfig.example.NestedConfig; +import org.apache.sling.caconfig.example.SimpleConfig; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +/** + * Test {@link ConfigurationResolver} with annotation classes for reading the config. + */ +@SuppressWarnings("null") +public class ConfigurationResolverAnnotationClassTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResolver underTest; + + private Resource site1Page1; + + @Before + public void setUp() { + underTest = ConfigurationTestUtils.registerConfigurationResolver(context); + context.registerInjectActivateService(new ConfigurationBuilderAdapterFactory()); + + // content resources + context.build().resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/content/site1"); + site1Page1 = context.create().resource("/content/site1/page1"); + } + + @Test + public void testNonExistingConfig_Simple() { + SimpleConfig cfg = underTest.get(site1Page1).as(SimpleConfig.class); + + assertNull(cfg.stringParam()); + assertEquals(5, cfg.intParam()); + assertEquals(false, cfg.boolParam()); + + assertFalse(underTest.get(site1Page1).has(SimpleConfig.class)); + } + + @Test + public void testNonExistingConfig_List() { + Collection cfgList = underTest.get(site1Page1).asCollection(ListConfig.class); + assertTrue(cfgList.isEmpty()); + + assertFalse(underTest.get(site1Page1).has(ListConfig.class)); + } + + @Test + public void testNonExistingConfig_Nested() { + NestedConfig cfg = underTest.get(site1Page1).as(NestedConfig.class); + + assertNull(cfg.stringParam()); + assertNotNull(cfg.subConfig()); + assertNotNull(cfg.subListConfig()); + + assertFalse(underTest.get(site1Page1).has(NestedConfig.class)); + } + + + @Test + public void testConfig_Simple() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.SimpleConfig", + "stringParam", "configValue1", + "intParam", 111, + "boolParam", true); + + SimpleConfig cfg = underTest.get(site1Page1).as(SimpleConfig.class); + + assertEquals("configValue1", cfg.stringParam()); + assertEquals(111, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + + assertTrue(underTest.get(site1Page1).has(SimpleConfig.class)); + } + + @Test + public void testConfig_Simple_PropertyInheritance() { + context.build() + .resource("/conf/global/sling:configs/org.apache.sling.caconfig.example.SimpleConfig", + "stringParam", "configValue1", + "intParam", 111) + .resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.SimpleConfig", + "stringParam", "configValue2", + "intParam", 222, + "boolParam", true, + PROPERTY_CONFIG_PROPERTY_INHERIT, true); + + SimpleConfig cfg = underTest.get(site1Page1).as(SimpleConfig.class); + + assertEquals("configValue2", cfg.stringParam()); + assertEquals(222, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + + assertTrue(underTest.get(site1Page1).has(SimpleConfig.class)); + } + + @Test + public void testConfig_SimpleWithName() { + context.build().resource("/conf/content/site1/sling:configs/sampleName", + "stringParam", "configValue1.1", + "intParam", 1111, + "boolParam", true); + + SimpleConfig cfg = underTest.get(site1Page1).name("sampleName").as(SimpleConfig.class); + + assertEquals("configValue1.1", cfg.stringParam()); + assertEquals(1111, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + + assertTrue(underTest.get(site1Page1).name("sampleName").has(SimpleConfig.class)); + } + + @Test + public void testConfig_List() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListConfig") + .siblingsMode() + .resource("1", "stringParam", "value1") + .resource("2", "stringParam", "value2") + .resource("3", "stringParam", "value3"); + + Collection cfgList = underTest.get(site1Page1).asCollection(ListConfig.class); + + assertEquals(3, cfgList.size()); + Iterator cfgIterator = cfgList.iterator(); + assertEquals("value1", cfgIterator.next().stringParam()); + assertEquals("value2", cfgIterator.next().stringParam()); + assertEquals("value3", cfgIterator.next().stringParam()); + + assertTrue(underTest.get(site1Page1).has(ListConfig.class)); + } + + @Test + public void testConfig_List_Nested() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig") + .siblingsMode() + .resource("1", "stringParam", "value1") + .resource("2", "stringParam", "value2") + .resource("3", "stringParam", "value3"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig/1/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value11") + .resource("2", "stringParam", "value12"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig/2/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value21"); + + List cfgList = ImmutableList.copyOf(underTest.get(site1Page1).asCollection(ListNestedConfig.class)); + + assertEquals(3, cfgList.size()); + + ListNestedConfig config1 = cfgList.get(0); + assertEquals("value1", config1.stringParam()); + assertEquals(2, config1.subListConfig().length); + assertEquals("value11", config1.subListConfig()[0].stringParam()); + assertEquals("value12", config1.subListConfig()[1].stringParam()); + + ListNestedConfig config2 = cfgList.get(1); + assertEquals("value2", config2.stringParam()); + assertEquals(1, config2.subListConfig().length); + assertEquals("value21", config2.subListConfig()[0].stringParam()); + + ListNestedConfig config3 = cfgList.get(2); + assertEquals("value3", config3.stringParam()); + assertEquals(0, config3.subListConfig().length); + + assertTrue(underTest.get(site1Page1).has(ListNestedConfig.class)); + } + + @Test + public void testConfig_List_DoubleNested() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig") + .siblingsMode() + .resource("1", "stringParam", "value1") + .resource("2", "stringParam", "value2") + .resource("3", "stringParam", "value3"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/1/subListNestedConfig") + .siblingsMode() + .resource("1", "stringParam", "value11") + .resource("2", "stringParam", "value12"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/1/subListNestedConfig/1/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value111") + .resource("2", "stringParam", "value112"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/1/subListNestedConfig/2/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value121"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/2/subListNestedConfig") + .siblingsMode() + .resource("1", "stringParam", "value21"); + + List cfgList = ImmutableList.copyOf(underTest.get(site1Page1).asCollection(ListDoubleNestedConfig.class)); + + assertEquals(3, cfgList.size()); + + ListDoubleNestedConfig config1 = cfgList.get(0); + assertEquals("value1", config1.stringParam()); + assertEquals(2, config1.subListNestedConfig().length); + assertEquals("value11", config1.subListNestedConfig()[0].stringParam()); + assertEquals(2, config1.subListNestedConfig()[0].subListConfig().length); + assertEquals("value111", config1.subListNestedConfig()[0].subListConfig()[0].stringParam()); + assertEquals("value112", config1.subListNestedConfig()[0].subListConfig()[1].stringParam()); + assertEquals("value12", config1.subListNestedConfig()[1].stringParam()); + assertEquals(1, config1.subListNestedConfig()[1].subListConfig().length); + assertEquals("value121", config1.subListNestedConfig()[1].subListConfig()[0].stringParam()); + + ListDoubleNestedConfig config2 = cfgList.get(1); + assertEquals("value2", config2.stringParam()); + assertEquals(1, config2.subListNestedConfig().length); + assertEquals("value21", config2.subListNestedConfig()[0].stringParam()); + assertEquals(0, config2.subListNestedConfig()[0].subListConfig().length); + + ListDoubleNestedConfig config3 = cfgList.get(2); + assertEquals("value3", config3.stringParam()); + assertEquals(0, config3.subListNestedConfig().length); + + assertTrue(underTest.get(site1Page1).has(ListDoubleNestedConfig.class)); + } + + @Test + public void testConfig_List_CollectionPropertyInheritance() { + context.build() + .resource("/conf/global/sling:configs/org.apache.sling.caconfig.example.ListConfig") + .siblingsMode() + .resource("1", "stringParam", "configValue1.1", "intParam", "111") + .resource("2", "stringParam", "configValue1.2", "intParam", "222") + .resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListConfig", + PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .siblingsMode() + .resource("2", "stringParam", "configValue2.2", PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("3", "stringParam", "configValue2.3", "intParam", "333", PROPERTY_CONFIG_PROPERTY_INHERIT, true); + + List cfgList = ImmutableList.copyOf(underTest.get(site1Page1).asCollection(ListConfig.class)); + + assertEquals(3, cfgList.size()); + assertEquals("configValue2.2", cfgList.get(0).stringParam()); + assertEquals(222, cfgList.get(0).intParam()); + assertEquals("configValue2.3", cfgList.get(1).stringParam()); + assertEquals(333, cfgList.get(1).intParam()); + assertEquals("configValue1.1", cfgList.get(2).stringParam()); + assertEquals(111, cfgList.get(2).intParam()); + + assertTrue(underTest.get(site1Page1).has(ListConfig.class)); + } + + @Test + public void testConfig_Nested() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.NestedConfig", + "stringParam", "configValue3") + .siblingsMode() + .resource("subConfig", "stringParam", "configValue4", "intParam", 444, "boolParam", true) + .hierarchyMode() + .resource("subListConfig") + .siblingsMode() + .resource("1", "stringParam", "configValue2.1") + .resource("2", "stringParam", "configValue2.2") + .resource("3", "stringParam", "configValue2.3"); + + NestedConfig cfg = underTest.get(site1Page1).as(NestedConfig.class); + + assertEquals("configValue3", cfg.stringParam()); + + SimpleConfig subConfig = cfg.subConfig(); + assertEquals("configValue4", subConfig.stringParam()); + assertEquals(444, subConfig.intParam()); + assertEquals(true, subConfig.boolParam()); + + ListConfig[] listConfig = cfg.subListConfig(); + assertEquals(3, listConfig.length); + assertEquals("configValue2.1", listConfig[0].stringParam()); + assertEquals("configValue2.2", listConfig[1].stringParam()); + assertEquals("configValue2.3", listConfig[2].stringParam()); + + assertTrue(underTest.get(site1Page1).has(NestedConfig.class)); + } + + @Test + public void testConfig_Nested_PropertyInheritance() { + context.build() + .resource("/conf/global/sling:configs/org.apache.sling.caconfig.example.NestedConfig") + .resource("subConfig", "stringParam", "configValue1", "intParam", 111, "boolParam", true) + .resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.NestedConfig", + "stringParam", "configValue3") + .resource("subConfig", "stringParam", "configValue4", PROPERTY_CONFIG_PROPERTY_INHERIT, true); + + NestedConfig cfg = underTest.get(site1Page1).as(NestedConfig.class); + + assertEquals("configValue3", cfg.stringParam()); + + SimpleConfig subConfig = cfg.subConfig(); + assertEquals("configValue4", subConfig.stringParam()); + assertEquals(111, subConfig.intParam()); + assertEquals(true, subConfig.boolParam()); + + assertTrue(underTest.get(site1Page1).has(NestedConfig.class)); + } + + @Test(expected=ConfigurationResolveException.class) + public void testInvalidClassConversion() { + // test with class not supported for configuration mapping + underTest.get(site1Page1).as(Rectangle2D.class); + } + + @Test + public void testNonExistingContentResource_Simple() { + SimpleConfig cfg = underTest.get(null).as(SimpleConfig.class); + + assertNull(cfg.stringParam()); + assertEquals(5, cfg.intParam()); + assertEquals(false, cfg.boolParam()); + + assertFalse(underTest.get(null).has(NestedConfig.class)); + } + + @Test + public void testNonExistingContentResource_List() { + Collection cfgList = underTest.get(null).asCollection(ListConfig.class); + assertTrue(cfgList.isEmpty()); + assertFalse(underTest.get(null).has(ListConfig.class)); + } + + @Test + public void testConfigAdaptMultipleTimes() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.SimpleConfig", + "stringParam", "configValue1"); + context.build().resource("/conf/content/site1/sling:configs/config2", + "stringParam", "configValue2"); + + SimpleConfig cfg2 = site1Page1.adaptTo(ConfigurationBuilder.class).name("config2").as(SimpleConfig.class); + assertEquals("configValue2", cfg2.stringParam()); + + // make sure the config name from first call is not cached in the ConfigurationBuilder instance + SimpleConfig cfg = site1Page1.adaptTo(ConfigurationBuilder.class).as(SimpleConfig.class); + assertEquals("configValue1", cfg.stringParam()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverCustomPersistence2Test.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverCustomPersistence2Test.java new file mode 100644 index 000000000..ced83d784 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverCustomPersistence2Test.java @@ -0,0 +1,281 @@ +/* + * 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.caconfig.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Rectangle2D; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.example.ListConfig; +import org.apache.sling.caconfig.example.ListDoubleNestedConfig; +import org.apache.sling.caconfig.example.ListNestedConfig; +import org.apache.sling.caconfig.example.NestedConfig; +import org.apache.sling.caconfig.example.SimpleConfig; +import org.apache.sling.caconfig.management.impl.CustomConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.framework.Constants; + +import com.google.common.collect.ImmutableList; + +/** + * Test {@link ConfigurationResolver} with annotation classes for reading the config. + */ +@SuppressWarnings("null") +public class ConfigurationResolverCustomPersistence2Test { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResolver underTest; + + private Resource site1Page1; + + @Before + public void setUp() { + // custom config with defines alternative bucket name "settings" + underTest = ConfigurationTestUtils.registerConfigurationResolver(context, + "configBucketNames", "settings"); + + // custom strategy which redirects all config resources to a jcr:content subnode + context.registerService(ConfigurationPersistenceStrategy2.class, + new CustomConfigurationPersistenceStrategy2(), Constants.SERVICE_RANKING, 2000); + + // content resources + context.build().resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/content/site1"); + site1Page1 = context.create().resource("/content/site1/page1"); + } + + @Test + public void testNonExistingConfig_Simple() { + SimpleConfig cfg = underTest.get(site1Page1).as(SimpleConfig.class); + + assertNull(cfg.stringParam()); + assertEquals(5, cfg.intParam()); + assertEquals(false, cfg.boolParam()); + } + + @Test + public void testNonExistingConfig_List() { + Collection cfgList = underTest.get(site1Page1).asCollection(ListConfig.class); + assertTrue(cfgList.isEmpty()); + } + + @Test + public void testNonExistingConfig_Nested() { + NestedConfig cfg = underTest.get(site1Page1).as(NestedConfig.class); + + assertNull(cfg.stringParam()); + assertNotNull(cfg.subConfig()); + assertNotNull(cfg.subListConfig()); + } + + + @Test + public void testConfig_Simple() { + context.build().resource("/conf/content/site1/settings/org.apache.sling.caconfig.example.SimpleConfig/jcr:content", + "stringParam", "configValue1", + "intParam", 111, + "boolParam", true); + + SimpleConfig cfg = underTest.get(site1Page1).as(SimpleConfig.class); + + assertEquals("configValue1", cfg.stringParam()); + assertEquals(111, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + } + + @Test + public void testConfig_SimpleWithName() { + context.build().resource("/conf/content/site1/settings/sampleName/jcr:content", + "stringParam", "configValue1.1", + "intParam", 1111, + "boolParam", true); + + SimpleConfig cfg = underTest.get(site1Page1).name("sampleName").as(SimpleConfig.class); + + assertEquals("configValue1.1", cfg.stringParam()); + assertEquals(1111, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + } + + @Test + public void testConfig_List() { + context.build().resource("/conf/content/site1/settings/org.apache.sling.caconfig.example.ListConfig/jcr:content") + .siblingsMode() + .resource("1", "stringParam", "value1") + .resource("2", "stringParam", "value2") + .resource("3", "stringParam", "value3"); + + Collection cfgList = underTest.get(site1Page1).asCollection(ListConfig.class); + + assertEquals(3, cfgList.size()); + Iterator cfgIterator = cfgList.iterator(); + assertEquals("value1", cfgIterator.next().stringParam()); + assertEquals("value2", cfgIterator.next().stringParam()); + assertEquals("value3", cfgIterator.next().stringParam()); + } + + @Test + public void testConfig_List_Nested() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig/jcr:content") + .siblingsMode() + .resource("1", "stringParam", "value1") + .resource("2", "stringParam", "value2") + .resource("3", "stringParam", "value3"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig/jcr:content/1/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value11") + .resource("2", "stringParam", "value12"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig/jcr:content/2/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value21"); + + List cfgList = ImmutableList.copyOf(underTest.get(site1Page1).asCollection(ListNestedConfig.class)); + + assertEquals(3, cfgList.size()); + + ListNestedConfig config1 = cfgList.get(0); + assertEquals("value1", config1.stringParam()); + assertEquals(2, config1.subListConfig().length); + assertEquals("value11", config1.subListConfig()[0].stringParam()); + assertEquals("value12", config1.subListConfig()[1].stringParam()); + + ListNestedConfig config2 = cfgList.get(1); + assertEquals("value2", config2.stringParam()); + assertEquals(1, config2.subListConfig().length); + assertEquals("value21", config2.subListConfig()[0].stringParam()); + + ListNestedConfig config3 = cfgList.get(2); + assertEquals("value3", config3.stringParam()); + assertEquals(0, config3.subListConfig().length); + } + + @Test + public void testConfig_List_DoubleNested() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/jcr:content") + .siblingsMode() + .resource("1", "stringParam", "value1") + .resource("2", "stringParam", "value2") + .resource("3", "stringParam", "value3"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/jcr:content/1/subListNestedConfig") + .siblingsMode() + .resource("1", "stringParam", "value11") + .resource("2", "stringParam", "value12"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/jcr:content/1/subListNestedConfig/1/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value111") + .resource("2", "stringParam", "value112"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/jcr:content/1/subListNestedConfig/2/subListConfig") + .siblingsMode() + .resource("1", "stringParam", "value121"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/jcr:content/2/subListNestedConfig") + .siblingsMode() + .resource("1", "stringParam", "value21"); + + List cfgList = ImmutableList.copyOf(underTest.get(site1Page1).asCollection(ListDoubleNestedConfig.class)); + + assertEquals(3, cfgList.size()); + + ListDoubleNestedConfig config1 = cfgList.get(0); + assertEquals("value1", config1.stringParam()); + assertEquals(2, config1.subListNestedConfig().length); + assertEquals("value11", config1.subListNestedConfig()[0].stringParam()); + assertEquals(2, config1.subListNestedConfig()[0].subListConfig().length); + assertEquals("value111", config1.subListNestedConfig()[0].subListConfig()[0].stringParam()); + assertEquals("value112", config1.subListNestedConfig()[0].subListConfig()[1].stringParam()); + assertEquals("value12", config1.subListNestedConfig()[1].stringParam()); + assertEquals(1, config1.subListNestedConfig()[1].subListConfig().length); + assertEquals("value121", config1.subListNestedConfig()[1].subListConfig()[0].stringParam()); + + ListDoubleNestedConfig config2 = cfgList.get(1); + assertEquals("value2", config2.stringParam()); + assertEquals(1, config2.subListNestedConfig().length); + assertEquals("value21", config2.subListNestedConfig()[0].stringParam()); + assertEquals(0, config2.subListNestedConfig()[0].subListConfig().length); + + ListDoubleNestedConfig config3 = cfgList.get(2); + assertEquals("value3", config3.stringParam()); + assertEquals(0, config3.subListNestedConfig().length); + } + + @Test + public void testConfig_Nested() { + context.build().resource("/conf/content/site1/settings/org.apache.sling.caconfig.example.NestedConfig") + .resource("jcr:content", "stringParam", "configValue3") + .siblingsMode() + .resource("subConfig", "stringParam", "configValue4", "intParam", 444, "boolParam", true) + .hierarchyMode() + .resource("subListConfig") + .siblingsMode() + .resource("1", "stringParam", "configValue2.1") + .resource("2", "stringParam", "configValue2.2") + .resource("3", "stringParam", "configValue2.3"); + + NestedConfig cfg = underTest.get(site1Page1).as(NestedConfig.class); + + assertEquals("configValue3", cfg.stringParam()); + + SimpleConfig subConfig = cfg.subConfig(); + assertEquals("configValue4", subConfig.stringParam()); + assertEquals(444, subConfig.intParam()); + assertEquals(true, subConfig.boolParam()); + + ListConfig[] listConfig = cfg.subListConfig(); + assertEquals(3, listConfig.length); + assertEquals("configValue2.1", listConfig[0].stringParam()); + assertEquals("configValue2.2", listConfig[1].stringParam()); + assertEquals("configValue2.3", listConfig[2].stringParam()); + } + + @Test(expected=ConfigurationResolveException.class) + public void testInvalidClassConversion() { + // test with class not supported for configuration mapping + underTest.get(site1Page1).as(Rectangle2D.class); + } + + @Test + public void testNonExistingContentResource_Simple() { + SimpleConfig cfg = underTest.get(null).as(SimpleConfig.class); + + assertNull(cfg.stringParam()); + assertEquals(5, cfg.intParam()); + assertEquals(false, cfg.boolParam()); + } + + @Test + public void testNonExistingContentResource_List() { + Collection cfgList = underTest.get(null).asCollection(ListConfig.class); + assertTrue(cfgList.isEmpty()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverCustomPersistenceTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverCustomPersistenceTest.java new file mode 100644 index 000000000..ed845811d --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverCustomPersistenceTest.java @@ -0,0 +1,281 @@ +/* + * 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.caconfig.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.awt.geom.Rectangle2D; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.example.ListConfig; +import org.apache.sling.caconfig.example.ListDoubleNestedConfig; +import org.apache.sling.caconfig.example.ListNestedConfig; +import org.apache.sling.caconfig.example.NestedConfig; +import org.apache.sling.caconfig.example.SimpleConfig; +import org.apache.sling.caconfig.management.impl.CustomConfigurationPersistenceStrategy; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.framework.Constants; + +import com.google.common.collect.ImmutableList; + +/** + * Test {@link ConfigurationResolver} with annotation classes for reading the config. + */ +@SuppressWarnings("null") +public class ConfigurationResolverCustomPersistenceTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResolver underTest; + + private Resource site1Page1; + + @Before + public void setUp() { + // custom config with defines alternative bucket name "settings" + underTest = ConfigurationTestUtils.registerConfigurationResolver(context, + "configBucketNames", "settings"); + + // custom strategy which redirects all config resources to a jcr:content subnode + context.registerService(ConfigurationPersistenceStrategy2.class, + new CustomConfigurationPersistenceStrategy(), Constants.SERVICE_RANKING, 2000); + + // content resources + context.build().resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/content/site1"); + site1Page1 = context.create().resource("/content/site1/page1"); + } + + @Test + public void testNonExistingConfig_Simple() { + SimpleConfig cfg = underTest.get(site1Page1).as(SimpleConfig.class); + + assertNull(cfg.stringParam()); + assertEquals(5, cfg.intParam()); + assertEquals(false, cfg.boolParam()); + } + + @Test + public void testNonExistingConfig_List() { + Collection cfgList = underTest.get(site1Page1).asCollection(ListConfig.class); + assertTrue(cfgList.isEmpty()); + } + + @Test + public void testNonExistingConfig_Nested() { + NestedConfig cfg = underTest.get(site1Page1).as(NestedConfig.class); + + assertNull(cfg.stringParam()); + assertNotNull(cfg.subConfig()); + assertNotNull(cfg.subListConfig()); + } + + + @Test + public void testConfig_Simple() { + context.build().resource("/conf/content/site1/settings/org.apache.sling.caconfig.example.SimpleConfig/jcr:content", + "stringParam", "configValue1", + "intParam", 111, + "boolParam", true); + + SimpleConfig cfg = underTest.get(site1Page1).as(SimpleConfig.class); + + assertEquals("configValue1", cfg.stringParam()); + assertEquals(111, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + } + + @Test + public void testConfig_SimpleWithName() { + context.build().resource("/conf/content/site1/settings/sampleName/jcr:content", + "stringParam", "configValue1.1", + "intParam", 1111, + "boolParam", true); + + SimpleConfig cfg = underTest.get(site1Page1).name("sampleName").as(SimpleConfig.class); + + assertEquals("configValue1.1", cfg.stringParam()); + assertEquals(1111, cfg.intParam()); + assertEquals(true, cfg.boolParam()); + } + + @Test + public void testConfig_List() { + context.build().resource("/conf/content/site1/settings/org.apache.sling.caconfig.example.ListConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value1") + .resource("2/jcr:content", "stringParam", "value2") + .resource("3/jcr:content", "stringParam", "value3"); + + Collection cfgList = underTest.get(site1Page1).asCollection(ListConfig.class); + + assertEquals(3, cfgList.size()); + Iterator cfgIterator = cfgList.iterator(); + assertEquals("value1", cfgIterator.next().stringParam()); + assertEquals("value2", cfgIterator.next().stringParam()); + assertEquals("value3", cfgIterator.next().stringParam()); + } + + @Test + public void testConfig_List_Nested() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value1") + .resource("2/jcr:content", "stringParam", "value2") + .resource("3/jcr:content", "stringParam", "value3"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig/1/jcr:content/subListConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value11") + .resource("2/jcr:content", "stringParam", "value12"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListNestedConfig/2/jcr:content/subListConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value21"); + + List cfgList = ImmutableList.copyOf(underTest.get(site1Page1).asCollection(ListNestedConfig.class)); + + assertEquals(3, cfgList.size()); + + ListNestedConfig config1 = cfgList.get(0); + assertEquals("value1", config1.stringParam()); + assertEquals(2, config1.subListConfig().length); + assertEquals("value11", config1.subListConfig()[0].stringParam()); + assertEquals("value12", config1.subListConfig()[1].stringParam()); + + ListNestedConfig config2 = cfgList.get(1); + assertEquals("value2", config2.stringParam()); + assertEquals(1, config2.subListConfig().length); + assertEquals("value21", config2.subListConfig()[0].stringParam()); + + ListNestedConfig config3 = cfgList.get(2); + assertEquals("value3", config3.stringParam()); + assertEquals(0, config3.subListConfig().length); + } + + @Test + public void testConfig_List_DoubleNested() { + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value1") + .resource("2/jcr:content", "stringParam", "value2") + .resource("3/jcr:content", "stringParam", "value3"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/1/jcr:content/subListNestedConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value11") + .resource("2/jcr:content", "stringParam", "value12"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/1/jcr:content/subListNestedConfig/1/jcr:content/subListConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value111") + .resource("2/jcr:content", "stringParam", "value112"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/1/jcr:content/subListNestedConfig/2/jcr:content/subListConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value121"); + context.build().resource("/conf/content/site1/sling:configs/org.apache.sling.caconfig.example.ListDoubleNestedConfig/2/jcr:content/subListNestedConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "value21"); + + List cfgList = ImmutableList.copyOf(underTest.get(site1Page1).asCollection(ListDoubleNestedConfig.class)); + + assertEquals(3, cfgList.size()); + + ListDoubleNestedConfig config1 = cfgList.get(0); + assertEquals("value1", config1.stringParam()); + assertEquals(2, config1.subListNestedConfig().length); + assertEquals("value11", config1.subListNestedConfig()[0].stringParam()); + assertEquals(2, config1.subListNestedConfig()[0].subListConfig().length); + assertEquals("value111", config1.subListNestedConfig()[0].subListConfig()[0].stringParam()); + assertEquals("value112", config1.subListNestedConfig()[0].subListConfig()[1].stringParam()); + assertEquals("value12", config1.subListNestedConfig()[1].stringParam()); + assertEquals(1, config1.subListNestedConfig()[1].subListConfig().length); + assertEquals("value121", config1.subListNestedConfig()[1].subListConfig()[0].stringParam()); + + ListDoubleNestedConfig config2 = cfgList.get(1); + assertEquals("value2", config2.stringParam()); + assertEquals(1, config2.subListNestedConfig().length); + assertEquals("value21", config2.subListNestedConfig()[0].stringParam()); + assertEquals(0, config2.subListNestedConfig()[0].subListConfig().length); + + ListDoubleNestedConfig config3 = cfgList.get(2); + assertEquals("value3", config3.stringParam()); + assertEquals(0, config3.subListNestedConfig().length); + } + + @Test + public void testConfig_Nested() { + context.build().resource("/conf/content/site1/settings/org.apache.sling.caconfig.example.NestedConfig") + .resource("jcr:content", "stringParam", "configValue3") + .siblingsMode() + .resource("subConfig/jcr:content", "stringParam", "configValue4", "intParam", 444, "boolParam", true) + .hierarchyMode() + .resource("subListConfig") + .siblingsMode() + .resource("1/jcr:content", "stringParam", "configValue2.1") + .resource("2/jcr:content", "stringParam", "configValue2.2") + .resource("3/jcr:content", "stringParam", "configValue2.3"); + + NestedConfig cfg = underTest.get(site1Page1).as(NestedConfig.class); + + assertEquals("configValue3", cfg.stringParam()); + + SimpleConfig subConfig = cfg.subConfig(); + assertEquals("configValue4", subConfig.stringParam()); + assertEquals(444, subConfig.intParam()); + assertEquals(true, subConfig.boolParam()); + + ListConfig[] listConfig = cfg.subListConfig(); + assertEquals(3, listConfig.length); + assertEquals("configValue2.1", listConfig[0].stringParam()); + assertEquals("configValue2.2", listConfig[1].stringParam()); + assertEquals("configValue2.3", listConfig[2].stringParam()); + } + + @Test(expected=ConfigurationResolveException.class) + public void testInvalidClassConversion() { + // test with class not supported for configuration mapping + underTest.get(site1Page1).as(Rectangle2D.class); + } + + @Test + public void testNonExistingContentResource_Simple() { + SimpleConfig cfg = underTest.get(null).as(SimpleConfig.class); + + assertNull(cfg.stringParam()); + assertEquals(5, cfg.intParam()); + assertEquals(false, cfg.boolParam()); + } + + @Test + public void testNonExistingContentResource_List() { + Collection cfgList = underTest.get(null).asCollection(ListConfig.class); + assertTrue(cfgList.isEmpty()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverValueMapTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverValueMapTest.java new file mode 100644 index 000000000..ca2278a07 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationResolverValueMapTest.java @@ -0,0 +1,256 @@ +/* + * 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.caconfig.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.ConfigurationResolveException; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.impl.override.DummyConfigurationOverrideProvider; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Test {@link ConfigurationResolver} with ValueMap for reading the config. + */ +@SuppressWarnings("null") +public class ConfigurationResolverValueMapTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResolver underTest; + + private Resource site1Page1; + + @Before + public void setUp() { + underTest = ConfigurationTestUtils.registerConfigurationResolver(context); + + // content resources + context.build().resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/content/site1"); + site1Page1 = context.create().resource("/content/site1/page1"); + } + + @Test + public void testNonExistingConfigMap() { + ValueMap props = underTest.get(site1Page1).name("sampleName").asValueMap(); + + assertNull(props.get("stringParam", String.class)); + assertEquals(0, (int)props.get("intParam", 0)); + assertEquals(false, props.get("boolParam", false)); + + assertFalse(underTest.get(site1Page1).has("sampleName")); + } + + @Test + public void testNonExistingConfigCollection() { + Collection propsList = underTest.get(site1Page1).name("sampleList").asValueMapCollection(); + assertTrue(propsList.isEmpty()); + + assertFalse(underTest.get(site1Page1).has("sampleList")); + } + + @Test + public void testConfig() { + context.build().resource("/conf/content/site1/sling:configs/sampleName", + "stringParam", "configValue1", + "intParam", 111, + "boolParam", true); + + ValueMap props = underTest.get(site1Page1).name("sampleName").asValueMap(); + + assertEquals("configValue1", props.get("stringParam", String.class)); + assertEquals(111, (int)props.get("intParam", 0)); + assertEquals(true, props.get("boolParam", false)); + + assertTrue(underTest.get(site1Page1).has("sampleName")); + } + + @Test + public void testConfigCollection() { + context.build().resource("/conf/content/site1/sling:configs/sampleList") + .siblingsMode() + .resource("1", "stringParam", "configValue1.1") + .resource("2", "stringParam", "configValue1.2") + .resource("3", "stringParam", "configValue1.3"); + + Collection propsList = underTest.get(site1Page1).name("sampleList").asValueMapCollection(); + + Iterator propsIterator = propsList.iterator(); + assertEquals("configValue1.1", propsIterator.next().get("stringParam", String.class)); + assertEquals("configValue1.2", propsIterator.next().get("stringParam", String.class)); + assertEquals("configValue1.3", propsIterator.next().get("stringParam", String.class)); + + assertTrue(underTest.get(site1Page1).has("sampleList")); + } + + @Test + public void testConfigWithDefaultValues() { + context.registerService(ConfigurationMetadataProvider.class, new DummyConfigurationMetadataProvider("sampleName", + ImmutableMap.of("stringParam", "defValue1", "intParam", 999), false)); + + context.build().resource("/conf/content/site1/sling:configs/sampleName", + "boolParam", true); + + ValueMap props = underTest.get(site1Page1).name("sampleName").asValueMap(); + + assertEquals("defValue1", props.get("stringParam", String.class)); + assertEquals(999, (int)props.get("intParam", 0)); + assertEquals(true, props.get("boolParam", false)); + + assertTrue(underTest.get(site1Page1).has("sampleName")); + } + + @Test + public void testConfigCollectionWithDefaultValues() { + context.registerService(ConfigurationMetadataProvider.class, new DummyConfigurationMetadataProvider("sampleList", + ImmutableMap.of("intParam", 999), true)); + + context.build().resource("/conf/content/site1/sling:configs/sampleList") + .siblingsMode() + .resource("1", "stringParam", "configValue1.1") + .resource("2", "stringParam", "configValue1.2") + .resource("3", "stringParam", "configValue1.3"); + + List propsList = ImmutableList.copyOf(underTest.get(site1Page1).name("sampleList").asValueMapCollection()); + + assertEquals("configValue1.1", propsList.get(0).get("stringParam", String.class)); + assertEquals(999, (int)propsList.get(0).get("intParam", 0)); + assertEquals("configValue1.2", propsList.get(1).get("stringParam", String.class)); + assertEquals(999, (int)propsList.get(1).get("intParam", 0)); + assertEquals("configValue1.3", propsList.get(2).get("stringParam", String.class)); + assertEquals(999, (int)propsList.get(2).get("intParam", 0)); + + assertTrue(underTest.get(site1Page1).has("sampleList")); + } + + @Test + public void testConfigWithOverride() { + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "[/content]sampleName={\"stringParam\":\"override1\",\"intParam\":222}")); + + context.build().resource("/conf/content/site1/sling:configs/sampleName", + "stringParam", "configValue1", + "intParam", 111, + "boolParam", true); + + ValueMap props = underTest.get(site1Page1).name("sampleName").asValueMap(); + + assertEquals("override1", props.get("stringParam", String.class)); + assertEquals(222, (int)props.get("intParam", 0)); + assertEquals(false, props.get("boolParam", false)); + + assertTrue(underTest.get(site1Page1).has("sampleName")); + } + + /** + * Test override for context path on which no configuration exists below /conf - not even on the inheritance lookup paths (SLING-7016) + */ + @Test + public void testConfigWithOverride_NoExistingConfig() { + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "[/content]sampleName={\"stringParam\":\"override1\",\"intParam\":222}")); + + ValueMap props = underTest.get(site1Page1).name("sampleName").asValueMap(); + + assertEquals("override1", props.get("stringParam", String.class)); + assertEquals(222, (int)props.get("intParam", 0)); + assertEquals(false, props.get("boolParam", false)); + + assertFalse(underTest.get(site1Page1).has("sampleName")); + } + + @Test + public void testConfigCollectionWithOverride() { + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "[/content]sampleList/stringParam=\"override1\"")); + + context.build().resource("/conf/content/site1/sling:configs/sampleList") + .siblingsMode() + .resource("1", "stringParam", "configValue1.1") + .resource("2", "stringParam", "configValue1.2") + .resource("3", "stringParam", "configValue1.3"); + + Collection propsList = underTest.get(site1Page1).name("sampleList").asValueMapCollection(); + + Iterator propsIterator = propsList.iterator(); + assertEquals("override1", propsIterator.next().get("stringParam", String.class)); + assertEquals("override1", propsIterator.next().get("stringParam", String.class)); + assertEquals("override1", propsIterator.next().get("stringParam", String.class)); + + assertTrue(underTest.get(site1Page1).has("sampleList")); + } + + @Test + public void testNonExistingContentResource() { + ValueMap props = underTest.get(null).name("sampleName").asValueMap(); + + assertNull(props.get("stringParam", String.class)); + assertEquals(0, (int)props.get("intParam", 0)); + assertEquals(false, props.get("boolParam", false)); + + assertFalse(underTest.get(null).has("sampleName")); + } + + @Test + public void testNonExistingContentResourceCollection() { + Collection propsList = underTest.get(null).name("sampleList").asValueMapCollection(); + assertTrue(propsList.isEmpty()); + assertFalse(underTest.get(null).has("sampleList")); + } + + @Test(expected=IllegalArgumentException.class) + public void testNullConfigName() { + underTest.get(site1Page1).name(null).asValueMap(); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidConfigName() { + underTest.get(site1Page1).name("/a/b/c").asValueMap(); + } + + @Test(expected=IllegalArgumentException.class) + public void testInvalidConfigName2() { + underTest.get(site1Page1).name("../a/b/c").asValueMap(); + } + + @Test(expected=ConfigurationResolveException.class) + public void testWithoutConfigName() { + underTest.get(site1Page1).asValueMap(); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationTestUtils.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationTestUtils.java new file mode 100644 index 000000000..1c4042e8c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/ConfigurationTestUtils.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl; + +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.impl.def.DefaultConfigurationInheritanceStrategy; +import org.apache.sling.caconfig.impl.def.DefaultConfigurationPersistenceStrategy; +import org.apache.sling.caconfig.impl.metadata.ConfigurationMetadataProviderMultiplexerImpl; +import org.apache.sling.caconfig.impl.override.ConfigurationOverrideMultiplexerImpl; +import org.apache.sling.caconfig.management.impl.ConfigurationManagementSettingsImpl; +import org.apache.sling.caconfig.management.impl.ConfigurationPersistenceStrategyMultiplexerImpl; +import org.apache.sling.caconfig.resource.impl.ConfigurationResourceTestUtils; +import org.apache.sling.testing.mock.sling.junit.SlingContext; + +public final class ConfigurationTestUtils { + + private ConfigurationTestUtils() { + // static methods only + } + + /** + * Register all services for {@link ConfigurationResolver}. + * @param context Sling context + */ + public static ConfigurationResolver registerConfigurationResolver(SlingContext context, Object... properties) { + ConfigurationResourceTestUtils.registerConfigurationResourceResolver(context); + context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + context.registerInjectActivateService(new ConfigurationPersistenceStrategyMultiplexerImpl()); + context.registerInjectActivateService(new DefaultConfigurationInheritanceStrategy()); + context.registerInjectActivateService(new ConfigurationInheritanceStrategyMultiplexerImpl()); + context.registerInjectActivateService(new ConfigurationOverrideMultiplexerImpl()); + context.registerInjectActivateService(new ConfigurationMetadataProviderMultiplexerImpl()); + return context.registerInjectActivateService(new ConfigurationResolverImpl(), properties); + } + + /** + * Register all services for {@link ConfigurationResolver} + * without the default implementations of the multiplexed services. + * @param context Sling context + */ + public static ConfigurationResolver registerConfigurationResolverWithoutDefaultImpl(SlingContext context, Object... properties) { + ConfigurationResourceTestUtils.registerConfigurationResourceResolverWithoutDefaultImpl(context); + context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + context.registerInjectActivateService(new ConfigurationPersistenceStrategyMultiplexerImpl()); + context.registerInjectActivateService(new ConfigurationInheritanceStrategyMultiplexerImpl()); + context.registerInjectActivateService(new ConfigurationOverrideMultiplexerImpl()); + context.registerInjectActivateService(new ConfigurationMetadataProviderMultiplexerImpl()); + return context.registerInjectActivateService(new ConfigurationResolverImpl(), properties); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/DummyConfigurationMetadataProvider.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/DummyConfigurationMetadataProvider.java new file mode 100644 index 000000000..edb7af348 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/DummyConfigurationMetadataProvider.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.caconfig.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableSortedSet; + +class DummyConfigurationMetadataProvider implements ConfigurationMetadataProvider { + + private final String configName; + private final Map defaultValues; + private final boolean collection; + + public DummyConfigurationMetadataProvider(String configName, Map defaultValues, boolean collection) { + this.configName = configName; + this.defaultValues = defaultValues; + this.collection = collection; + } + + @Override + public @NotNull SortedSet getConfigurationNames() { + return ImmutableSortedSet.of(configName); + } + + @Override + public ConfigurationMetadata getConfigurationMetadata(String configName) { + if (!StringUtils.equals(this.configName, configName)) { + return null; + } + List> properties = new ArrayList<>(); + for (Map.Entry entry : defaultValues.entrySet()) { + properties.add(new PropertyMetadata<>(entry.getKey(), entry.getValue())); + } + return new ConfigurationMetadata(configName, properties, collection); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/ConfigurationResolverPropertyInheritanceTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/ConfigurationResolverPropertyInheritanceTest.java new file mode 100644 index 000000000..c157fc2b9 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/ConfigurationResolverPropertyInheritanceTest.java @@ -0,0 +1,221 @@ +/* + * 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.caconfig.impl.def; + +import static org.apache.sling.caconfig.impl.def.ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.ConfigurationResolver; +import org.apache.sling.caconfig.impl.ConfigurationTestUtils; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test {@link ConfigurationResolver} with property inheritance and merging. + */ +public class ConfigurationResolverPropertyInheritanceTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResolver underTest; + + private Resource site1Page1; + private Resource site2Page1; + + @Before + public void setUp() { + underTest = ConfigurationTestUtils.registerConfigurationResolver(context); + + // content resources that form a deeper hierarchy + context.build() + .resource("/content/tenant1", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1") + .resource("/content/tenant1/region1", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1/region1") + .resource("/content/tenant1/region1/site1", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1/region1/site1") + .resource("/content/tenant1/region1/site2", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1/region1/site2"); + site1Page1 = context.create().resource("/content/tenant1/region1/site1/page1"); + site2Page1 = context.create().resource("/content/tenant1/region1/site2/page1"); + } + + @Test + public void testInheritanceWithoutMerging() { + context.build() + .resource("/conf/global/sling:configs/test", "param1", "value1", "param2", "value2") + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test", "param1", "value1a") + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test", "param1", "value1b"); + + assertThat(underTest.get(site1Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1a"), + not(hasKey("param2")))); + assertThat(underTest.get(site2Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1b"), + not(hasKey("param2")))); + } + + @Test + public void testInheritanceMerging() { + context.build() + .resource("/conf/global/sling:configs/test", "param1", "value1", "param2", "value2") + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test", "param1", "value1a", + PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test", "param1", "value1b"); + + assertThat(underTest.get(site1Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1a"), + hasEntry("param2", (Object)"value2"))); + assertThat(underTest.get(site2Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1b"), + not(hasKey("param2")))); + } + + @Test + public void testInheritanceMergingMultipleLevels() { + context.build() + .resource("/conf/global/sling:configs/test", "param1", "value1", "param4", "value4") + .resource("/conf/brand1/tenant1/sling:configs/test", "param1", "value1a", "param3", "value3", + PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/sling:configs/test", "param1", "value1b", "param2", "value2", + PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test", "param1", "value1c", + PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test", "param1", "value1d"); + + assertThat(underTest.get(site1Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1c"), + hasEntry("param2", (Object)"value2"), + hasEntry("param3", (Object)"value3"), + hasEntry("param4", (Object)"value4"))); + assertThat(underTest.get(site2Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1d"), + not(hasKey("param2")), + not(hasKey("param3")), + not(hasKey("param4")))); + } + + /** + * Setting merging property only on parent has not effect => no inheritance. + */ + @Test + public void testInheritanceMergingOnParent() { + context.build() + .resource("/conf/global/sling:configs/test", "param1", "value1", "param2", "value2", + PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test", "param1", "value1a") + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test", "param1", "value1b"); + + assertThat(underTest.get(site1Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1a"), + not(hasKey("param2")))); + assertThat(underTest.get(site2Page1).name("test").asValueMap(), allOf( + hasEntry("param1", (Object)"value1b"), + not(hasKey("param2")))); + } + + @Test + public void testCollectionInheritanceWithoutMerging() { + context.build() + .resource("/conf/global/sling:configs/test") + .resource("item1", "param1", "value1", "param2", "value2") + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test") + .resource("item1", "param1", "value1a") + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test") + .resource("item1", "param1", "value1b"); + + assertThat(underTest.get(site1Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1a"), + not(hasKey("param2")))); + assertThat(underTest.get(site2Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1b"), + not(hasKey("param2")))); + } + + @Test + public void testCollectionInheritanceMerging() { + context.build() + .resource("/conf/global/sling:configs/test") + .resource("item1", "param1", "value1", "param2", "value2") + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test") + .resource("item1", "param1", "value1a", PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test") + .resource("item1", "param1", "value1b"); + + assertThat(underTest.get(site1Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1a"), + hasEntry("param2", (Object)"value2"))); + assertThat(underTest.get(site2Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1b"), + not(hasKey("param2")))); + } + + @Test + public void testCollectionInheritanceMergingMultipleLevels() { + context.build() + .resource("/conf/global/sling:configs/test") + .resource("item1", "param1", "value1", "param4", "value4") + .resource("/conf/brand1/tenant1/sling:configs/test") + .resource("item1", "param1", "value1a", "param3", "value3", PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/sling:configs/test") + .resource("item1", "param1", "value1b", "param2", "value2", PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test") + .resource("item1", "param1", "value1c", PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test") + .resource("item1", "param1", "value1d"); + + assertThat(underTest.get(site1Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1c"), + hasEntry("param2", (Object)"value2"), + hasEntry("param3", (Object)"value3"), + hasEntry("param4", (Object)"value4"))); + assertThat(underTest.get(site2Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1d"), + not(hasKey("param2")), + not(hasKey("param3")), + not(hasKey("param4")))); + } + + /** + * Setting merging property only on parent has not effect => no inheritance. + */ + @Test + public void testCollectionInheritanceMergingOnParent() { + context.build() + .resource("/conf/global/sling:configs/test", PROPERTY_CONFIG_PROPERTY_INHERIT, true) + .resource("item1", "param1", "value1", "param2", "value2") + .resource("/conf/brand1/tenant1/region1/site1/sling:configs/test") + .resource("item1", "param1", "value1a") + .resource("/conf/brand1/tenant1/region1/site2/sling:configs/test") + .resource("item1", "param1", "value1b"); + + assertThat(underTest.get(site1Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1a"), + not(hasKey("param2")))); + assertThat(underTest.get(site2Page1).name("test").asValueMapCollection().iterator().next(), allOf( + hasEntry("param1", (Object)"value1b"), + not(hasKey("param2")))); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationInheritanceStrategyTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationInheritanceStrategyTest.java new file mode 100644 index 000000000..ee6251c51 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationInheritanceStrategyTest.java @@ -0,0 +1,118 @@ +/* + * 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.caconfig.impl.def; + +import static org.apache.sling.caconfig.impl.def.ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Iterator; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +@SuppressWarnings("null") +public class DefaultConfigurationInheritanceStrategyTest { + + private static final String PROPERTY_CONFIG_PROPERTY_INHERIT_CUSTOM = "custom:configPropertyInherit"; + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationInheritanceStrategy underTest; + + @Test + public void testWithoutPropertyMerging() { + underTest = context.registerInjectActivateService(new DefaultConfigurationInheritanceStrategy()); + + Iterator resources = ImmutableList.of( + context.create().resource("/conf/resource1", "prop1", "value1a", "prop2", "value2a"), + context.create().resource("/conf/resource2", "prop2", "value2b", "prop3", "value3b"), + context.create().resource("/conf/resource3", "prop4", "value4b") + ).iterator(); + + Resource inherited = underTest.getResource(resources); + ValueMap props = inherited.getValueMap(); + + assertEquals("value1a", props.get("prop1", String.class)); + assertEquals("value2a", props.get("prop2", String.class)); + assertNull(props.get("prop3", String.class)); + assertNull(props.get("prop4", String.class)); + } + + @Test + public void testWithPropertyMerging() { + underTest = context.registerInjectActivateService(new DefaultConfigurationInheritanceStrategy(), + "configPropertyInheritancePropertyNames", PROPERTY_CONFIG_PROPERTY_INHERIT_CUSTOM); + Iterator resources = ImmutableList.of( + context.create().resource("/conf/resource1", "prop1", "value1a", "prop2", "value2a", PROPERTY_CONFIG_PROPERTY_INHERIT, true), + context.create().resource("/conf/resource2", "prop2", "value2b", "prop3", "value3b", PROPERTY_CONFIG_PROPERTY_INHERIT_CUSTOM, true), + context.create().resource("/conf/resource3", "prop4", "value4b") + ).iterator(); + + Resource inherited = underTest.getResource(resources); + ValueMap props = inherited.getValueMap(); + + assertEquals("value1a", props.get("prop1", String.class)); + assertEquals("value2a", props.get("prop2", String.class)); + assertEquals("value3b", props.get("prop3", String.class)); + assertEquals("value4b", props.get("prop4", String.class)); + } + + @Test + public void testWithPartialPropertyMerging() { + underTest = context.registerInjectActivateService(new DefaultConfigurationInheritanceStrategy()); + + Iterator resources = ImmutableList.of( + context.create().resource("/conf/resource1", "prop1", "value1a", "prop2", "value2a", PROPERTY_CONFIG_PROPERTY_INHERIT, true), + context.create().resource("/conf/resource2", "prop2", "value2b", "prop3", "value3b"), + context.create().resource("/conf/resource3", "prop4", "value4b") + ).iterator(); + + Resource inherited = underTest.getResource(resources); + ValueMap props = inherited.getValueMap(); + + assertEquals("value1a", props.get("prop1", String.class)); + assertEquals("value2a", props.get("prop2", String.class)); + assertEquals("value3b", props.get("prop3", String.class)); + assertNull(props.get("prop4", String.class)); + } + + @Test + public void testDisabled() { + underTest = context.registerInjectActivateService(new DefaultConfigurationInheritanceStrategy(), + "enabled", false); + + Iterator resources = ImmutableList.of( + context.create().resource("/conf/resource1", "prop1", "value1a", "prop2", "value2a", PROPERTY_CONFIG_PROPERTY_INHERIT, true), + context.create().resource("/conf/resource2", "prop2", "value2b", "prop3", "value3b", PROPERTY_CONFIG_PROPERTY_INHERIT, true), + context.create().resource("/conf/resource3", "prop4", "value4b") + ).iterator(); + + Resource inherited = underTest.getResource(resources); + assertNull(inherited); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationPersistenceStrategyTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationPersistenceStrategyTest.java new file mode 100644 index 000000000..ded22e083 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/def/DefaultConfigurationPersistenceStrategyTest.java @@ -0,0 +1,238 @@ +/* + * 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.caconfig.impl.def; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.management.impl.ConfigurationManagementSettingsImpl; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.testing.mock.sling.ResourceResolverType; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +@SuppressWarnings("null") +public class DefaultConfigurationPersistenceStrategyTest { + + @Rule + public SlingContext context = new SlingContext(ResourceResolverType.JCR_MOCK); + + @Before + public void setUp() { + context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + } + + @Test + public void testGetResource() { + ConfigurationPersistenceStrategy2 underTest = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + Resource resource = context.create().resource("/conf/test"); + assertSame(resource, underTest.getResource(resource)); + assertSame(resource, underTest.getCollectionParentResource(resource)); + assertSame(resource, underTest.getCollectionItemResource(resource)); + } + + @Test + public void testGetResourcePath() { + ConfigurationPersistenceStrategy2 underTest = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + String path = "/conf/test"; + assertEquals(path, underTest.getResourcePath(path)); + assertEquals(path, underTest.getCollectionParentResourcePath(path)); + assertEquals(path, underTest.getCollectionItemResourcePath(path)); + } + + @Test + public void testGetConfigName() throws Exception { + ConfigurationPersistenceStrategy2 underTest = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + String path = "test"; + assertEquals(path, underTest.getConfigName(path, null)); + assertEquals(path, underTest.getCollectionParentConfigName(path, null)); + assertEquals(path, underTest.getCollectionItemConfigName(path, null)); + } + + @Test + public void testPersistConfiguration() throws Exception { + ConfigurationPersistenceStrategy2 underTest = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + // store config data + assertTrue(underTest.persistConfiguration(context.resourceResolver(), "/conf/test", + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1", "prop2", 5)))); + context.resourceResolver().commit(); + + ValueMap props = context.resourceResolver().getResource("/conf/test").getValueMap(); + assertEquals("value1", props.get("prop1", String.class)); + assertEquals((Integer)5, props.get("prop2", Integer.class)); + + // remove config data + assertTrue(underTest.persistConfiguration(context.resourceResolver(), "/conf/test", + new ConfigurationPersistData(ImmutableMap.of()))); + context.resourceResolver().commit(); + + props = context.resourceResolver().getResource("/conf/test").getValueMap(); + assertNull(props.get("prop1", String.class)); + assertNull(props.get("prop2", Integer.class)); + + underTest.deleteConfiguration(context.resourceResolver(), "/conf/test"); + assertNull(context.resourceResolver().getResource("/conf/test")); + } + + @Test + public void testPersistConfigurationCollection() throws Exception { + ConfigurationPersistenceStrategy2 underTest = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + // store new config collection items + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1")).collectionItemName("item1"), + new ConfigurationPersistData(ImmutableMap.of("prop2", 5)).collectionItemName("item2")) + ).properties(ImmutableMap.of("prop1", "abc", "sling:resourceType", "/a/b/c")) + )); + context.resourceResolver().commit(); + + Resource resource = context.resourceResolver().getResource("/conf/test"); + assertThat(resource, ResourceMatchers.props("prop1", "abc", "sling:resourceType", "/a/b/c")); + assertThat(resource, ResourceMatchers.containsChildren("item1", "item2")); + + assertThat(resource.getChild("item1"), ResourceMatchers.props("prop1", "value1")); + assertThat(resource.getChild("item2"), ResourceMatchers.props("prop2", 5L)); + + // remove config collection items + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test", + new ConfigurationCollectionPersistData(ImmutableList.of()))); + context.resourceResolver().commit(); + + resource = context.resourceResolver().getResource("/conf/test"); + assertEquals(0, ImmutableList.copyOf(resource.getChildren()).size()); + + underTest.deleteConfiguration(context.resourceResolver(), "/conf/test"); + assertNull(context.resourceResolver().getResource("/conf/test")); + } + + @Test + public void testPersistConfigurationCollection_Nested() throws Exception { + ConfigurationPersistenceStrategy2 underTest = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + // store new config collection items + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1")).collectionItemName("item1"), + new ConfigurationPersistData(ImmutableMap.of("prop1", "value2")).collectionItemName("item2") + )) + )); + + // store nested items + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test/item1/subList", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(ImmutableMap.of("prop1", "value11")).collectionItemName("sub1"), + new ConfigurationPersistData(ImmutableMap.of("prop1", "value12")).collectionItemName("sub2") + )) + )); + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test/item2/subList", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(ImmutableMap.of("prop1", "value21")).collectionItemName("sub1") + )) + )); + + context.resourceResolver().commit(); + + + Resource resource = context.resourceResolver().getResource("/conf/test"); + assertThat(resource, ResourceMatchers.containsChildren("item1", "item2")); + + assertThat(resource.getChild("item1"), ResourceMatchers.props("prop1", "value1")); + assertThat(resource.getChild("item1/subList"), ResourceMatchers.containsChildren("sub1", "sub2")); + assertThat(resource.getChild("item1/subList/sub1"), ResourceMatchers.props("prop1", "value11")); + assertThat(resource.getChild("item1/subList/sub2"), ResourceMatchers.props("prop1", "value12")); + + assertThat(resource.getChild("item2"), ResourceMatchers.props("prop1", "value2")); + assertThat(resource.getChild("item2/subList"), ResourceMatchers.containsChildren("sub1")); + assertThat(resource.getChild("item2/subList/sub1"), ResourceMatchers.props("prop1", "value21")); + + + // update config collection items + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(ImmutableMap.of("prop1", "value2-new")).collectionItemName("item2"), + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1-new")).collectionItemName("item1"), + new ConfigurationPersistData(ImmutableMap.of("prop1", "value3-new")).collectionItemName("item3") + )) + )); + context.resourceResolver().commit(); + + resource = context.resourceResolver().getResource("/conf/test"); + assertThat(resource, ResourceMatchers.containsChildren("item1", "item2", "item3")); + + assertThat(resource.getChild("item1"), ResourceMatchers.props("prop1", "value1-new")); + assertThat(resource.getChild("item1/subList"), ResourceMatchers.containsChildren("sub1", "sub2")); + assertThat(resource.getChild("item1/subList/sub1"), ResourceMatchers.props("prop1", "value11")); + assertThat(resource.getChild("item1/subList/sub2"), ResourceMatchers.props("prop1", "value12")); + + assertThat(resource.getChild("item2"), ResourceMatchers.props("prop1", "value2-new")); + assertThat(resource.getChild("item2/subList"), ResourceMatchers.containsChildren("sub1")); + assertThat(resource.getChild("item2/subList/sub1"), ResourceMatchers.props("prop1", "value21")); + + assertThat(resource.getChild("item3"), ResourceMatchers.props("prop1", "value3-new")); + assertFalse(resource.getChild("item3").listChildren().hasNext()); + + + // remove config collection items + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test", + new ConfigurationCollectionPersistData(ImmutableList.of()))); + context.resourceResolver().commit(); + + resource = context.resourceResolver().getResource("/conf/test"); + assertEquals(0, ImmutableList.copyOf(resource.getChildren()).size()); + + underTest.deleteConfiguration(context.resourceResolver(), "/conf/test"); + assertNull(context.resourceResolver().getResource("/conf/test")); + } + + @Test + public void testDisabled() { + ConfigurationPersistenceStrategy2 underTest = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy(), + "enabled", false); + + Resource resource = context.create().resource("/conf/test"); + assertNull(underTest.getResource(resource)); + assertNull(underTest.getResourcePath(resource.getPath())); + + assertFalse(underTest.persistConfiguration(context.resourceResolver(), "/conf/test", + new ConfigurationPersistData(ImmutableMap.of()))); + assertFalse(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/test", + new ConfigurationCollectionPersistData(ImmutableList.of()))); + assertFalse(underTest.deleteConfiguration(context.resourceResolver(), "/conf/test")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProviderTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProviderTest.java new file mode 100644 index 000000000..1f6d0da91 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProviderTest.java @@ -0,0 +1,131 @@ +/* + * 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.caconfig.impl.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Set; + +import org.apache.sling.caconfig.annotation.Configuration; +import org.apache.sling.caconfig.example.AllTypesConfig; +import org.apache.sling.caconfig.example.MetadataSimpleConfig; +import org.apache.sling.testing.mock.osgi.junit.OsgiContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.framework.Bundle; + +public class AnnotationClassConfigurationMetadataProviderTest { + + @Rule + public OsgiContext context = new OsgiContext(); + + private AnnotationClassConfigurationMetadataProvider underTest; + + @Before + public void setUp() { + underTest = context.registerInjectActivateService(new AnnotationClassConfigurationMetadataProvider()); + } + + @Test + public void testGetConfigurationMetadata() { + + // no configuration metadata present + assertTrue(underTest.getConfigurationNames().isEmpty()); + + // simulate bundle deployment with annotation classes + Bundle dummyBundle = BundleEventUtil.startDummyBundle(context.bundleContext(), + MetadataSimpleConfig.class, AllTypesConfig.class); + + // validate config metadata is available + Set configNames = underTest.getConfigurationNames(); + assertEquals(2, configNames.size()); + assertTrue(configNames.contains("simpleConfig")); + assertTrue(configNames.contains(AllTypesConfig.class.getName())); + assertEquals("simpleConfig", underTest.getConfigurationMetadata("simpleConfig").getName()); + assertEquals(AllTypesConfig.class.getName(), underTest.getConfigurationMetadata(AllTypesConfig.class.getName()).getName()); + + // simulate bundle undeployment + BundleEventUtil.stopDummyBundle(dummyBundle); + + // no configuration metadata present + assertTrue(underTest.getConfigurationNames().isEmpty()); + } + + + @Test + public void testUnmappedConfigName() { + assertNull(underTest.getConfigurationMetadata("unkonwn")); + } + + // Define configuration annotation class mapped to name 'simpleConfig' already used by another class + @Configuration(name = "simpleConfig") + private @interface NameConflictMetadataSimpleConfig { + String stringParam(); + int intParam() default 5; + boolean boolParam(); + } + + @Test + public void testNameConflictSingleBundle() { + + // simulate bundle deployment with annotation classes + Bundle dummyBundle = BundleEventUtil.startDummyBundle(context.bundleContext(), + MetadataSimpleConfig.class, NameConflictMetadataSimpleConfig.class); + + // validate config metadata is available + Set configNames = underTest.getConfigurationNames(); + assertEquals(1, configNames.size()); // only 1 - annotation with conflicting name is ignored + assertTrue(configNames.contains("simpleConfig")); + assertEquals("simpleConfig", underTest.getConfigurationMapping("simpleConfig").getConfigName()); + + // simulate bundle undeployment + BundleEventUtil.stopDummyBundle(dummyBundle); + + // no configuration metadata present + assertTrue(underTest.getConfigurationNames().isEmpty()); + } + + @Test + public void testNameConflictAccrossBundles() { + + // simulate bundle deployment with annotation classes + Bundle dummyBundle1 = BundleEventUtil.startDummyBundle(context.bundleContext(), + MetadataSimpleConfig.class); + Bundle dummyBundle2 = BundleEventUtil.startDummyBundle(context.bundleContext(), + NameConflictMetadataSimpleConfig.class); + + // validate config metadata is available + Set configNames = underTest.getConfigurationNames(); + assertEquals(1, configNames.size()); // only 1 - annotation with conflicting name is ignored + assertTrue(configNames.contains("simpleConfig")); + assertEquals("simpleConfig", underTest.getConfigurationMapping("simpleConfig").getConfigName()); + + // simulate bundle undeployment + BundleEventUtil.stopDummyBundle(dummyBundle1); + BundleEventUtil.stopDummyBundle(dummyBundle2); + + // no configuration metadata present + assertTrue(underTest.getConfigurationNames().isEmpty()); + } + + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParserTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParserTest.java new file mode 100644 index 000000000..de2c60483 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParserTest.java @@ -0,0 +1,175 @@ +/* + * 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.caconfig.impl.metadata; + +import static org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.buildConfigurationMetadata; +import static org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.getConfigurationName; +import static org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.getPropertyName; +import static org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.isContextAwareConfig; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collection; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.example.AllTypesConfig; +import org.apache.sling.caconfig.example.ListConfig; +import org.apache.sling.caconfig.example.MetadataSimpleConfig; +import org.apache.sling.caconfig.example.NestedConfig; +import org.apache.sling.caconfig.example.SimpleConfig; +import org.apache.sling.caconfig.example.WithoutAnnotationConfig; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class AnnotationClassParserTest { + + @Test + public void testIsContextAwareConfig() { + assertTrue(isContextAwareConfig(SimpleConfig.class)); + assertFalse(isContextAwareConfig(WithoutAnnotationConfig.class)); + assertFalse(isContextAwareConfig(Object.class)); + } + + @Test + public void testGetConfigurationName() { + assertEquals(SimpleConfig.class.getName(), getConfigurationName(SimpleConfig.class)); + assertEquals("simpleConfig", getConfigurationName(MetadataSimpleConfig.class)); + assertNull(getConfigurationName(WithoutAnnotationConfig.class)); + assertNull(getConfigurationName(Object.class)); + } + + @Test + public void testGetPropertyName() { + // test all variants defined in OSGi spec as example + assertEquals("myProperty143", getPropertyName("myProperty143")); + assertEquals("new", getPropertyName("$new")); + assertEquals("my$prop", getPropertyName("my$$prop")); + assertEquals("dot.prop", getPropertyName("dot_prop")); + assertEquals(".secret", getPropertyName("_secret")); + assertEquals("another_prop", getPropertyName("another__prop")); + assertEquals("three_.prop", getPropertyName("three___prop")); + assertEquals("four._prop", getPropertyName("four_$__prop")); + assertEquals("five..prop", getPropertyName("five_$_prop")); + } + + @Test + public void testBuildConfigurationMetadata_Simple() { + ConfigurationMetadata metadata = buildConfigurationMetadata(MetadataSimpleConfig.class); + + assertEquals("simpleConfig", metadata.getName()); + assertEquals("Simple configuration", metadata.getLabel()); + assertEquals("This is a configuration example with additional metadata.", metadata.getDescription()); + assertEquals(ImmutableMap.of("param1", "value1", "param2", "123"), metadata.getProperties()); + assertFalse(metadata.isCollection()); + + List> propertyMetadataList = ImmutableList.copyOf(metadata.getPropertyMetadata().values()); + assertEquals(3, propertyMetadataList.size()); + + PropertyMetadata stringParam = propertyMetadataList.get(0); + assertEquals("String Param", stringParam.getLabel()); + assertEquals("Enter strings here.", stringParam.getDescription()); + assertEquals(ImmutableMap.of("p1", "v1"), stringParam.getProperties()); + assertNull(stringParam.getDefaultValue()); + + PropertyMetadata intParam = propertyMetadataList.get(1); + assertEquals("Integer Param", intParam.getLabel()); + assertNull(intParam.getDescription()); + assertTrue(intParam.getProperties().isEmpty()); + assertEquals(5, intParam.getDefaultValue()); + + PropertyMetadata boolParam = propertyMetadataList.get(2); + assertNull(boolParam.getLabel()); + assertNull(boolParam.getDescription()); + assertTrue(boolParam.getProperties().isEmpty()); + assertNull(boolParam.getDefaultValue()); + } + + @Test + public void testBuildConfigurationMetadata_List() { + ConfigurationMetadata metadata = buildConfigurationMetadata(ListConfig.class); + + assertEquals(ListConfig.class.getName(), metadata.getName()); + assertTrue(metadata.isCollection()); + } + + @Test + public void testBuildConfigurationMetadata_AllTypes() { + ConfigurationMetadata metadata = buildConfigurationMetadata(AllTypesConfig.class); + + assertEquals(AllTypesConfig.class.getName(), metadata.getName()); + assertNull(metadata.getLabel()); + assertNull(metadata.getDescription()); + assertTrue(metadata.getProperties().isEmpty()); + assertEquals(20, metadata.getPropertyMetadata().size()); + } + + @Test + public void testBuildConfigurationMetadata_Nested() { + ConfigurationMetadata metadata = buildConfigurationMetadata(NestedConfig.class); + + assertEquals(NestedConfig.class.getName(), metadata.getName()); + + Collection> propertyMetadataList = metadata.getPropertyMetadata().values(); + assertEquals(4, propertyMetadataList.size()); + + for (PropertyMetadata propertyMetadata : propertyMetadataList) { + if (StringUtils.equals(propertyMetadata.getName(), "stringParam")) { + assertEquals(String.class, propertyMetadata.getType()); + } + else if (StringUtils.equals(propertyMetadata.getName(), "subConfig")) { + assertEquals(ConfigurationMetadata.class, propertyMetadata.getType()); + + ConfigurationMetadata subConfigMetadata = propertyMetadata.getConfigurationMetadata(); + assertEquals("subConfig", subConfigMetadata.getName()); + assertEquals(3, subConfigMetadata.getPropertyMetadata().size()); + } + else if (StringUtils.equals(propertyMetadata.getName(), "subListConfig")) { + assertEquals(ConfigurationMetadata[].class, propertyMetadata.getType()); + + ConfigurationMetadata subListConfigMetadata = propertyMetadata.getConfigurationMetadata(); + assertEquals("subListConfig", subListConfigMetadata.getName()); + assertEquals(2, subListConfigMetadata.getPropertyMetadata().size()); + } + else if (StringUtils.equals(propertyMetadata.getName(), "subConfigWithoutAnnotation")) { + assertEquals(ConfigurationMetadata.class, propertyMetadata.getType()); + + ConfigurationMetadata subConfigWithoutAnnotationMetadata = propertyMetadata.getConfigurationMetadata(); + assertEquals("subConfigWithoutAnnotation", subConfigWithoutAnnotationMetadata.getName()); + assertEquals(1, subConfigWithoutAnnotationMetadata.getPropertyMetadata().size()); + } + else { + fail("Unexpected property name: " + propertyMetadata.getName()); + } + } + } + + @Test(expected = IllegalArgumentException.class) + public void testBuildConfigurationMetadata_IllegalClass() { + buildConfigurationMetadata(WithoutAnnotationConfig.class); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/BundleEventUtil.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/BundleEventUtil.java new file mode 100644 index 000000000..84edd307e --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/BundleEventUtil.java @@ -0,0 +1,279 @@ +/* + * 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.caconfig.impl.metadata; + +import static org.apache.sling.caconfig.impl.ConfigurationNameConstants.CONFIGURATION_CLASSES_HEADER; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.cert.X509Certificate; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Vector; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.sling.testing.mock.osgi.MockOsgi; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.Version; + +/** + * Helper methods for simulating events when deploying bundles with configuration annotation classes. + */ +final class BundleEventUtil { + + private static final AtomicLong BUNDLE_COUNTER = new AtomicLong(); + + private BundleEventUtil() { + // static methods only + } + + /** + * Simulate a bundle STARTED event with a given set of classes simulated to be found in the bundle's classpath. + */ + public static Bundle startDummyBundle(BundleContext bundleContext, Class... classes) { + DummyBundle bundle = new DummyBundle(bundleContext, classes); + bundle.setState(Bundle.ACTIVE); + BundleEvent event = new BundleEvent(BundleEvent.STARTED, bundle); + MockOsgi.sendBundleEvent(bundleContext, event); + return bundle; + } + + /** + * Simulate a bundle STARTED event with a given set of classes simulated to be found in the bundle's classpath. + */ + public static void stopDummyBundle(Bundle bundle) { + ((DummyBundle)bundle).setState(Bundle.RESOLVED); + BundleEvent event = new BundleEvent(BundleEvent.STOPPED, bundle); + MockOsgi.sendBundleEvent(bundle.getBundleContext(), event); + } + + private static class DummyBundle implements Bundle { + + private final BundleContext bundleContext; + private final Class[] classes; + private final Long bundleId; + private int state = Bundle.UNINSTALLED; + private final String classNames; + + public DummyBundle(BundleContext bundleContext, Class[] classes) { + this.bundleContext = bundleContext; + this.classes = classes; + this.bundleId = BUNDLE_COUNTER.incrementAndGet(); + + StringBuilder sb = new StringBuilder(); + for (Class clazz : classes) { + sb.append(clazz.getName()).append(","); + } + classNames = sb.toString(); + } + + @Override + public int getState() { + return this.state; + } + + public void setState(int state) { + this.state = state; + } + + @Override + public Dictionary getHeaders() { + Dictionary headers = new Hashtable<>(); + headers.put(CONFIGURATION_CLASSES_HEADER, classNames); + return headers; + } + + @Override + public Enumeration findEntries(String path, String filePattern, boolean recurse) { + Vector urls = new Vector(); // NOPMD + for (int i = 0; i < classes.length; i++) { + try { + urls.add(new URL("file:/" + classes[i].getName().replace('.', '/') + ".class")); + } + catch (MalformedURLException ex) { + throw new RuntimeException("Malformed URL.", ex); + } + } + return urls.elements(); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return getClass().getClassLoader().loadClass(name); + } + + @Override + public BundleContext getBundleContext() { + return bundleContext; + } + + @Override + public void start(int options) throws BundleException { + // do nothing + } + + @Override + public void start() throws BundleException { + // do nothing + } + + @Override + public void stop(int options) throws BundleException { + // do nothing + } + + @Override + public void stop() throws BundleException { + // do nothing + } + + @Override + public void update(InputStream input) throws BundleException { + // do nothing + } + + @Override + public void update() throws BundleException { + // do nothing + } + + @Override + public void uninstall() throws BundleException { + // do nothing + } + + @Override + public long getBundleId() { + return bundleId; + } + + @Override + public String getLocation() { + return null; + } + + @Override + public ServiceReference[] getRegisteredServices() { // NOPMD + return null; + } + + @Override + public ServiceReference[] getServicesInUse() { // NOPMD + return null; + } + + @Override + public boolean hasPermission(Object permission) { + return false; + } + + @Override + public URL getResource(String name) { + return null; + } + + @Override + public Dictionary getHeaders(String locale) { + return null; + } + + @Override + public String getSymbolicName() { + return "DummyBundle" + bundleId; + } + + @Override + public Enumeration getResources(String name) throws IOException { + return null; + } + + @Override + public Enumeration getEntryPaths(String path) { + return null; + } + + @Override + public URL getEntry(String path) { + return null; + } + + @Override + public long getLastModified() { + return 0; + } + + @Override + public int compareTo(Bundle obj) { + if (obj instanceof DummyBundle) { + return bundleId.compareTo(((DummyBundle)obj).bundleId); + } + return -1; + } + + @Override + public Map> getSignerCertificates(int signersType) { + return null; + } + + @Override + public Version getVersion() { + return null; + } + + @Override + public A adapt(Class type) { + return null; + } + + @Override + public File getDataFile(String filename) { + return null; + } + + @Override + public int hashCode() { + return bundleId.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof DummyBundle) { + return bundleId.equals(((DummyBundle)obj).bundleId); + } + return false; + } + + @Override + public String toString() { + return getSymbolicName(); + } + + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImplTest.java new file mode 100644 index 000000000..e5cbe3e5c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImplTest.java @@ -0,0 +1,115 @@ +/* + * 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.caconfig.impl.metadata; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.sling.caconfig.impl.def.DefaultConfigurationPersistenceStrategy; +import org.apache.sling.caconfig.management.impl.ConfigurationManagementSettingsImpl; +import org.apache.sling.caconfig.management.impl.ConfigurationPersistenceStrategyMultiplexerImpl; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; + +public class ConfigurationMetadataProviderMultiplexerImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationMetadataProviderMultiplexerImpl underTest; + + @Before + public void setUp() { + context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + context.registerInjectActivateService(new ConfigurationPersistenceStrategyMultiplexerImpl()); + context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + underTest = context.registerInjectActivateService(new ConfigurationMetadataProviderMultiplexerImpl()); + } + + @Test + public void testWithNoProvider() { + SortedSet configNames = underTest.getConfigurationNames(); + assertTrue(configNames.isEmpty()); + + ConfigurationMetadata configMetadata = underTest.getConfigurationMetadata("test1"); + assertNull(configMetadata); + } + + @Test + public void testWithOneProvider() { + registerConfigurationMetadataProvider("test1", "test2"); + + SortedSet configNames = underTest.getConfigurationNames(); + assertEquals(ImmutableSortedSet.of("test1", "test2"), configNames); + + ConfigurationMetadata configMetadata = underTest.getConfigurationMetadata("test1"); + assertEquals("test1", configMetadata.getName()); + + configMetadata = underTest.getConfigurationMetadata("test2"); + assertEquals("test2", configMetadata.getName()); + } + + @Test + public void testWithTwoProviders() { + registerConfigurationMetadataProvider("test1"); + registerConfigurationMetadataProvider("test2"); + + SortedSet configNames = underTest.getConfigurationNames(); + assertEquals(ImmutableSortedSet.of("test1", "test2"), configNames); + + ConfigurationMetadata configMetadata = underTest.getConfigurationMetadata("test1"); + assertEquals("test1", configMetadata.getName()); + + configMetadata = underTest.getConfigurationMetadata("test2"); + assertEquals("test2", configMetadata.getName()); + } + + private void registerConfigurationMetadataProvider(String... names) { + final Map metadata = new HashMap<>(); + for (String name : names) { + metadata.put(name, new ConfigurationMetadata(name, ImmutableList.>of(), false)); + } + context.registerService(ConfigurationMetadataProvider.class, new ConfigurationMetadataProvider() { + @Override + public @NotNull SortedSet getConfigurationNames() { + return new TreeSet<>(metadata.keySet()); + } + @Override + public ConfigurationMetadata getConfigurationMetadata(String configName) { + return metadata.get(configName); + } + }); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImplTest.java new file mode 100644 index 000000000..92b236fd5 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImplTest.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.caconfig.impl.override; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.Map; + +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.framework.Constants; + +import com.google.common.collect.ImmutableMap; + +public class ConfigurationOverrideMultiplexerImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationOverrideMultiplexerImpl underTest; + + @Before + public void setUp() { + underTest = context.registerInjectActivateService(new ConfigurationOverrideMultiplexerImpl()); + } + + @Test + public void testWithNoProviders() { + assertOverride("/a/b", "test", + ImmutableMap.of("param1", "initialValue"), + null); + } + + @Test + public void testWithMultipleProviders() { + + // 1st provider + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "test/globalParam1=\"globalValue1\"", + "[/a/b]test/param1=\"value1\""), Constants.SERVICE_RANKING, 200); + + // 2nd provider (may overwrite 1st one) + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "test/globalParam1=\"globalValue1a\"", + "[/a/b/c]test={\"param1\":\"value2\"}"), Constants.SERVICE_RANKING, 100); + + assertOverride("/a", "test", + ImmutableMap.of("param1", "initialValue"), + ImmutableMap.of("param1", "initialValue", "globalParam1", "globalValue1a")); + + assertOverride("/a/b", "test", + ImmutableMap.of("param1", "initialValue"), + ImmutableMap.of("param1", "value1", "globalParam1", "globalValue1a")); + + assertOverride("/a/b/c", "test", + ImmutableMap.of("param1", "initialValue"), + ImmutableMap.of("param1", "value2")); + + assertOverride("/a/b/c/d", "test", + ImmutableMap.of("param1", "initialValue"), + ImmutableMap.of("param1", "value2")); + + assertOverride("/a/b", "test2", + ImmutableMap.of("param1", "initialValue"), + null); + } + + private void assertOverride(String path, String configName, Map input, Map output) { + if (output == null) { + assertNull(underTest.overrideProperties(path, configName, input)); + } + else { + assertEquals(output, underTest.overrideProperties(path, configName, input)); + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/DummyConfigurationOverrideProvider.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/DummyConfigurationOverrideProvider.java new file mode 100644 index 000000000..5950a7aa4 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/DummyConfigurationOverrideProvider.java @@ -0,0 +1,42 @@ +/* + * 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.caconfig.impl.override; + +import java.util.Collection; +import java.util.List; + +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.jetbrains.annotations.NotNull; + +import com.google.common.collect.ImmutableList; + +public class DummyConfigurationOverrideProvider implements ConfigurationOverrideProvider { + + private final List overrideStrings; + + public DummyConfigurationOverrideProvider(String... overrideStrings) { + this.overrideStrings = ImmutableList.copyOf(overrideStrings); + } + + @Override + public @NotNull Collection getOverrideStrings() { + return overrideStrings; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OsgiConfigurationOverrideProviderTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OsgiConfigurationOverrideProviderTest.java new file mode 100644 index 000000000..3fff5a770 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OsgiConfigurationOverrideProviderTest.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.caconfig.impl.override; + +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Rule; +import org.junit.Test; + +public class OsgiConfigurationOverrideProviderTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Test + public void testEnabled() { + OsgiConfigurationOverrideProvider provider = context.registerInjectActivateService( + new OsgiConfigurationOverrideProvider(), + "enabled", true, + "overrides", new String[] { + "test/param1=value1", + "[/content]test/param2=value2" + }); + + Collection overrides = provider.getOverrideStrings(); + assertTrue(overrides.contains("test/param1=value1")); + assertTrue(overrides.contains("[/content]test/param2=value2")); + } + + @Test + public void testDisabled() { + OsgiConfigurationOverrideProvider provider = context.registerInjectActivateService( + new OsgiConfigurationOverrideProvider(), + "enabled", false, + "overrides", new String[] { + "test/param1=value1", + "[/content]test/param2=value2" + }); + + Collection overrides = provider.getOverrideStrings(); + assertTrue(overrides.isEmpty()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OverrideItemTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OverrideItemTest.java new file mode 100644 index 000000000..246de6959 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OverrideItemTest.java @@ -0,0 +1,59 @@ +/* + * 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.caconfig.impl.override; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +public class OverrideItemTest { + + private Map props; + private OverrideItem underTest; + + @Before + public void setUp() { + props = ImmutableMap.of("p1", "abc", "p2", 55); + underTest = new OverrideItem("/a/b", "c/d", props, true); + } + + @Test + public void testProperties() { + assertEquals("/a/b", underTest.getPath()); + assertEquals("c/d", underTest.getConfigName()); + assertEquals(props, underTest.getProperties()); + assertTrue(underTest.isAllProperties()); + } + + @Test + public void testMatchesPath() { + assertTrue(underTest.matchesPath("/a/b")); + assertTrue(underTest.matchesPath("/a/b/c/d")); + assertFalse(underTest.matchesPath("/a")); + assertFalse(underTest.matchesPath("/other")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OverrideStringParserTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OverrideStringParserTest.java new file mode 100644 index 000000000..0b5316f31 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/OverrideStringParserTest.java @@ -0,0 +1,225 @@ +/* + * 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.caconfig.impl.override; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +public class OverrideStringParserTest { + + private static final Map BASICTYPES_MAP = ImmutableMap.builder() + .put("param1", "value1") + .put("param2", "value2") + .put("param3", 555L) + .put("param4", 1.23d) + .put("param5", true) + .build(); + + private static final Map BASICTYPES_ARRAY_MAP = ImmutableMap.builder() + .put("param1", new String[] { "v1a", "v1b" }) + .put("param2", new String[] { "v2a", "v2b" }) + .put("param3", new Long[] { 555L, 666L }) + .put("param4", new Double[] { 1.23d, 2.34d }) + .put("param5", new Boolean[] { true, false }) + .put("param6", new String[0]) + .build(); + + @Test + public void testEmptyList() { + List result = parse(); + assertTrue(result.isEmpty()); + } + + @Test + public void testBasicTypes() { + List result = parse( + "configName/param1=\"value1\"", + "configName/param2=\"value2\"", + "configName/param3=555", + "configName/param4=1.23", + "configName/param5=true"); + + assertEquals(1, result.size()); + OverrideItem item = result.get(0); + assertNull(item.getPath()); + assertEquals("configName", item.getConfigName()); + assertEquals(BASICTYPES_MAP, item.getProperties()); + assertFalse(item.isAllProperties()); + } + + @Test + public void testBasicTypesArray() { + List result = parse( + "config.name/param1=[\"v1a\",\"v1b\"]", + "config.name/param2=[\"v2a\",\"v2b\"]", + "config.name/param3=[555,666]", + "config.name/param4=[1.23,2.34]", + "config.name/param5=[true,false]", + "config.name/param6=[]"); + + assertEquals(1, result.size()); + OverrideItem item = result.get(0); + assertNull(item.getPath()); + assertEquals("config.name", item.getConfigName()); + for (Map.Entry entry : item.getProperties().entrySet()) { + assertArrayEquals("array " + entry.getKey(), (Object[])BASICTYPES_ARRAY_MAP.get(entry.getKey()), (Object[])item.getProperties().get(entry.getKey())); + } + assertFalse(item.isAllProperties()); + } + + @Test + public void testBasicTypesJson() { + List result = parse( + "configName={\"param1\":\"value1\"," + + "\"param2\":\"value2\"," + + "\"param3\":555," + + "\"param4\":1.23," + + "\"param5\":true}"); + + assertEquals(1, result.size()); + OverrideItem item = result.get(0); + assertNull(item.getPath()); + assertEquals("configName", item.getConfigName()); + assertEquals(BASICTYPES_MAP, item.getProperties()); + assertTrue(item.isAllProperties()); + } + + @Test + public void testBasicTypesJsonArray() { + List result = parse( + "configName={\"param1\":[\"v1a\",\"v1b\"]," + + "\"param2\":[\"v2a\",\"v2b\"]," + + "\"param3\":[555,666]," + + "\"param4\":[1.23,2.34]," + + "\"param5\":[true,false]," + + "\"param6\":[]}"); + + assertEquals(1, result.size()); + OverrideItem item = result.get(0); + assertNull(item.getPath()); + assertEquals("configName", item.getConfigName()); + for (Map.Entry entry : item.getProperties().entrySet()) { + assertArrayEquals("array " + entry.getKey(), (Object[])BASICTYPES_ARRAY_MAP.get(entry.getKey()), (Object[])item.getProperties().get(entry.getKey())); + } + assertTrue(item.isAllProperties()); + } + + @Test + public void testWithPath() { + List result = parse( + "[/a/b]configName/sub1/param1=\"value1\"", + "configName/sub2/param2=\"value2\""); + + assertEquals(2, result.size()); + + OverrideItem item1 = result.get(0); + assertEquals("/a/b", item1.getPath()); + assertEquals("configName/sub1", item1.getConfigName()); + assertEquals("value1", item1.getProperties().get("param1")); + assertFalse(item1.isAllProperties()); + + OverrideItem item2 = result.get(1); + assertNull(item2.getPath()); + assertEquals("configName/sub2", item2.getConfigName()); + assertEquals("value2", item2.getProperties().get("param2")); + assertFalse(item2.isAllProperties()); + } + + @Test + public void testCombined() { + List result = parse( + "[/a/b]configName/param1=\"value1\"", + "configName/param2=\"value2\"", + "[/a/b]configName={\"param1\":\"value1\"," + + "\"param2\":\"value2\"," + + "\"param3\":555," + + "\"param4\":1.23," + + "\"param5\":true}"); + + assertEquals(3, result.size()); + + OverrideItem item1 = result.get(0); + assertEquals("/a/b", item1.getPath()); + assertEquals("configName", item1.getConfigName()); + assertEquals("value1", item1.getProperties().get("param1")); + assertFalse(item1.isAllProperties()); + + OverrideItem item2 = result.get(1); + assertNull(item2.getPath()); + assertEquals("configName", item2.getConfigName()); + assertEquals("value2", item2.getProperties().get("param2")); + assertFalse(item2.isAllProperties()); + + OverrideItem item3 = result.get(2); + assertEquals("/a/b", item3.getPath()); + assertEquals("configName", item3.getConfigName()); + assertEquals(BASICTYPES_MAP, item3.getProperties()); + assertTrue(item3.isAllProperties()); + } + + @Test + public void testInvalidSyntax() { + List result = parse( + "/configName/param1=\"value1\"", + "configName/../param1=\"value1\"", + "[/a/b]=\"value1\"", + "[/a/b]configName=\"value1\"", + "[/a/../b]configName/param1=\"value1\"", + "[]configName=\"value1\"", + "configName/param2:\"value2\"", + "configName/param3", + "configName/param3=", + "[[/a/b]]configName/param4=1.23", + "[a/b]configName/param5=true", + "configName/param1=null"); + + // all ignored + assertEquals(0, result.size()); + } + + @Test + public void testInvalidJson() { + List result = parse( + "configName1={param1:\"value1\"", + "configName1={\"param1/xyz\":\"value1\"}", + "configName1={param1:[\"value1\",123]}", + "configName2={param1:{\"subparam1\":\"value1\"}}", + "configName1={param1:null}", + "configName1={\"param1:\"value1\"}"); + + // all ignored + assertEquals(0, result.size()); + } + + private List parse(String... values) { + return ImmutableList.copyOf(OverrideStringParser.parse(ImmutableList.copyOf(values))); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/SystemPropertyConfigurationOverrideProviderTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/SystemPropertyConfigurationOverrideProviderTest.java new file mode 100644 index 000000000..8c5e8e9af --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/impl/override/SystemPropertyConfigurationOverrideProviderTest.java @@ -0,0 +1,70 @@ +/* + * 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.caconfig.impl.override; + +import static org.apache.sling.caconfig.impl.override.SystemPropertyConfigurationOverrideProvider.SYSTEM_PROPERTY_PREFIX; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class SystemPropertyConfigurationOverrideProviderTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Before + public void setUp() { + System.setProperty(SYSTEM_PROPERTY_PREFIX + "test/param1", "value1"); + System.setProperty(SYSTEM_PROPERTY_PREFIX + "[/content]test/param2", "value2"); + } + + @After + public void tearDown() { + System.clearProperty(SYSTEM_PROPERTY_PREFIX + "test/param1"); + System.clearProperty(SYSTEM_PROPERTY_PREFIX + "[/content]test/param2"); + } + + @Test + public void testEnabled() { + SystemPropertyConfigurationOverrideProvider provider = context.registerInjectActivateService( + new SystemPropertyConfigurationOverrideProvider(), + "enabled", true); + + Collection overrides = provider.getOverrideStrings(); + assertTrue(overrides.contains("test/param1=value1")); + assertTrue(overrides.contains("[/content]test/param2=value2")); + } + + @Test + public void testDisabled() { + SystemPropertyConfigurationOverrideProvider provider = context.registerInjectActivateService( + new SystemPropertyConfigurationOverrideProvider(), + "enabled", false); + + Collection overrides = provider.getOverrideStrings(); + assertTrue(overrides.isEmpty()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationCollectionDataImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationCollectionDataImplTest.java new file mode 100644 index 000000000..845d402bd --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationCollectionDataImplTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.Map; + +import org.apache.sling.caconfig.management.ConfigurationCollectionData; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +@RunWith(MockitoJUnitRunner.class) +public class ConfigurationCollectionDataImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Mock + private Collection items; + private ConfigurationManagementSettings configurationManagementSettings; + + @Before + public void setUp() { + configurationManagementSettings = context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + } + + @Test + public void testProperties() { + Map props = ImmutableMap.of("jcr:primaryType", "test", "prop1", "value1"); + ConfigurationCollectionData underTest = new ConfigurationCollectionDataImpl("name1", items, "/path1", props, configurationManagementSettings); + + assertEquals("name1", underTest.getConfigName()); + assertSame(items, underTest.getItems()); + assertEquals("/path1", underTest.getResourcePath()); + assertEquals(ImmutableMap.of("prop1", "value1"), underTest.getProperties()); + } + + @Test + public void testEmpty() { + ConfigurationCollectionData underTest = new ConfigurationCollectionDataImpl("name1", ImmutableList.of(), "/path1", null, configurationManagementSettings); + + assertEquals("name1", underTest.getConfigName()); + assertTrue(underTest.getItems().isEmpty()); + assertEquals("/path1", underTest.getResourcePath()); + assertEquals(ImmutableMap.of(ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT, true), underTest.getProperties()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImplTest.java new file mode 100644 index 000000000..3007d50cb --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImplTest.java @@ -0,0 +1,206 @@ +/* + * 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.caconfig.management.impl; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.impl.def.DefaultConfigurationPersistenceStrategy; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("null") +public class ConfigurationDataImplTest { + + @Rule + public SlingContext context = new SlingContext(); + @Mock + private Resource contextResource; + @Mock + private ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + @Mock + private ConfigurationManager configurationManager; + private ConfigurationManagementSettings configurationManagementSettings; + private ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + + private Resource configResource; + private ConfigurationMetadata configMetadata; + + @Before + public void setUp() { + configurationManagementSettings = context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + configurationPersistenceStrategy = context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + configResource = context.create().resource("/conf/test", + "prop1", "value1", + "prop4", true); + configMetadata = new ConfigurationMetadata("testName", ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("prop2", String.class), + new PropertyMetadata<>("prop3", 5), + new PropertyMetadata<>("propIntArray", new Integer[] { 1,2,3 })), + false); + } + + @Test + public void testWithResourceMetadata() { + ConfigurationData underTest = new ConfigurationDataImpl(configMetadata, configResource, configResource, null, + contextResource, "test", configurationManager, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, + true, "item1"); + + assertEquals("test", underTest.getConfigName()); + assertEquals("item1", underTest.getCollectionItemName()); + + assertEquals(configResource.getPath(), underTest.getResourcePath()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "prop4", "propIntArray"), underTest.getPropertyNames()); + + ValueMap values = underTest.getValues(); + assertEquals("value1", values.get("prop1", String.class)); + assertNull(values.get("prop2", String.class)); + assertNull(values.get("prop3", Integer.class)); + assertEquals(true, values.get("prop4", Boolean.class)); + + ValueMap effectiveValues = underTest.getEffectiveValues(); + assertEquals("value1", effectiveValues.get("prop1", String.class)); + assertNull(effectiveValues.get("prop2", String.class)); + assertEquals((Integer)5, effectiveValues.get("prop3", Integer.class)); + assertEquals(true, effectiveValues.get("prop4", Boolean.class)); + + ValueInfo prop1 = underTest.getValueInfo("prop1"); + assertEquals("prop1", prop1.getPropertyMetadata().getName()); + assertEquals("value1", prop1.getValue()); + assertEquals("value1", prop1.getEffectiveValue()); + + ValueInfo prop3 = underTest.getValueInfo("prop3"); + assertEquals("prop3", prop3.getPropertyMetadata().getName()); + assertNull(prop3.getValue()); + assertEquals((Integer)5, prop3.getEffectiveValue()); + + ValueInfo prop4 = underTest.getValueInfo("prop4"); + assertNull("prop4", prop4.getPropertyMetadata()); + assertEquals(true, prop4.getValue()); + assertEquals(true, prop4.getEffectiveValue()); + + ValueInfo propIntArray = underTest.getValueInfo("propIntArray"); + assertNull(propIntArray.getValue()); + assertArrayEquals(new Integer[] {1,2,3}, (Integer[])propIntArray.getEffectiveValue()); + } + + @Test + public void testWithResourceOnly() { + ConfigurationData underTest = new ConfigurationDataImpl(null, configResource, configResource, null, + contextResource, "test", configurationManager, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, + false, null); + + assertEquals("test", underTest.getConfigName()); + assertNull(underTest.getCollectionItemName()); + + assertEquals(ImmutableSet.of("prop1", "prop4"), underTest.getPropertyNames()); + + ValueMap values = underTest.getValues(); + assertEquals("value1", values.get("prop1", String.class)); + assertEquals(true, values.get("prop4", Boolean.class)); + + ValueMap effectiveValues = underTest.getEffectiveValues(); + assertEquals("value1", effectiveValues.get("prop1", String.class)); + assertEquals(true, effectiveValues.get("prop4", Boolean.class)); + + ValueInfo prop1 = underTest.getValueInfo("prop1"); + assertNull(prop1.getPropertyMetadata()); + assertEquals("value1", prop1.getValue()); + assertEquals("value1", prop1.getEffectiveValue()); + + ValueInfo prop4 = underTest.getValueInfo("prop4"); + assertNull("prop4", prop4.getPropertyMetadata()); + assertEquals(true, prop4.getValue()); + assertEquals(true, prop4.getEffectiveValue()); + } + + @Test + public void testMetadataOnly() { + ConfigurationData underTest = new ConfigurationDataImpl(configMetadata, + contextResource, "test", configurationManager, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, + false); + + assertEquals("test", underTest.getConfigName()); + assertNull(underTest.getCollectionItemName()); + + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "propIntArray"), underTest.getPropertyNames()); + + ValueMap values = underTest.getValues(); + assertTrue(values.isEmpty()); + + ValueMap effectiveValues = underTest.getEffectiveValues(); + assertEquals("defValue", effectiveValues.get("prop1", String.class)); + assertEquals((Integer)5, effectiveValues.get("prop3", Integer.class)); + + ValueInfo prop1 = underTest.getValueInfo("prop1"); + assertEquals("prop1", prop1.getPropertyMetadata().getName()); + assertNull(prop1.getValue()); + assertEquals("defValue", prop1.getEffectiveValue()); + + ValueInfo prop3 = underTest.getValueInfo("prop3"); + assertEquals("prop3", prop3.getPropertyMetadata().getName()); + assertNull(prop3.getValue()); + assertEquals((Integer)5, prop3.getEffectiveValue()); + } + + @Test + public void testIgnoreProperties() { + Resource resource = context.create().resource("/conf/testIgnoreProps", + "prop1", "value1", + "prop4", true, + "jcr:primaryType", "myType"); + + ConfigurationData underTest = new ConfigurationDataImpl(null, resource, resource, null, + contextResource, "test", configurationManager, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, + false, null); + + assertEquals(ImmutableSet.of("prop1", "prop4"), underTest.getPropertyNames()); + + assertNull(underTest.getValues().get("jcr:primaryType")); + assertNull(underTest.getEffectiveValues().get("jcr:primaryType")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagementSettingsImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagementSettingsImplTest.java new file mode 100644 index 000000000..cacb4de33 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagementSettingsImplTest.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.caconfig.management.impl; + +import static org.junit.Assert.assertEquals; + +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +public class ConfigurationManagementSettingsImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Test + public void testDefault() { + ConfigurationManagementSettings underTest = context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + + assertEquals(ImmutableSet.of(), underTest.getIgnoredPropertyNames(ImmutableSet.of())); + assertEquals(ImmutableSet.of(), underTest.getIgnoredPropertyNames(ImmutableSet.of("abc", "def"))); + assertEquals(ImmutableSet.of("jcr:xyz", "jcr:def"), underTest.getIgnoredPropertyNames(ImmutableSet.of("abc", "jcr:xyz", "jcr:def"))); + + assertEquals(ImmutableList.of("."), underTest.getConfigCollectionPropertiesResourceNames()); + } + + @Test + public void testCustomConfig() { + ConfigurationManagementSettings underTest = context.registerInjectActivateService(new ConfigurationManagementSettingsImpl(), + "ignorePropertyNameRegex", new String[] { "^.*e.*$", "^.*b.*$" }, + "configCollectionPropertiesResourceNames", new String[] { "a", "b" }); + + assertEquals(ImmutableSet.of(), underTest.getIgnoredPropertyNames(ImmutableSet.of())); + assertEquals(ImmutableSet.of("abc", "def"), underTest.getIgnoredPropertyNames(ImmutableSet.of("abc", "def"))); + assertEquals(ImmutableSet.of("abc", "jcr:def"), underTest.getIgnoredPropertyNames(ImmutableSet.of("abc", "jcr:xyz", "jcr:def"))); + + assertEquals(ImmutableList.of("a", "b"), underTest.getConfigCollectionPropertiesResourceNames()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplCustomPersistence2Test.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplCustomPersistence2Test.java new file mode 100644 index 000000000..11605e27c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplCustomPersistence2Test.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.caconfig.management.impl; + +import static org.apache.sling.caconfig.management.impl.CustomConfigurationPersistenceStrategy2.containsJcrContent; + +import java.util.Dictionary; +import java.util.Hashtable; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.Constants; +import org.osgi.service.cm.ConfigurationAdmin; + +/** + * Test {@link ConfigurationManagerImpl} with custom persistence. + */ +@RunWith(MockitoJUnitRunner.class) +public class ConfigurationManagerImplCustomPersistence2Test extends ConfigurationManagerImplTest { + + @Override + protected void provideCustomOsgiConfig() throws Exception { + // provide custom lookup resource name for collection properties + ConfigurationAdmin configAdmin = context.getService(ConfigurationAdmin.class); + org.osgi.service.cm.Configuration mgmtSettingsConfig = configAdmin.getConfiguration(ConfigurationManagementSettingsImpl.class.getName()); + Dictionary mgmtSettings = new Hashtable<>(); + mgmtSettings.put("configCollectionPropertiesResourceNames", new String[] { "colPropsResource", "." }); + mgmtSettingsConfig.update(mgmtSettings); + } + + @Before + public void setUpCustomPersistence() { + // custom strategy which redirects all config resources to a jcr:content subnode + context.registerService(ConfigurationPersistenceStrategy2.class, + new CustomConfigurationPersistenceStrategy2(), Constants.SERVICE_RANKING, 2000); + } + + @Override + protected String getConfigResolvePath(String path) { + if (containsJcrContent(path)) { + return replaceBucketName(path); + } + else { + return replaceBucketName(path) + "/jcr:content"; + } + } + + @Override + protected String getConfigPersistPath(String path) { + if (containsJcrContent(path)) { + return path; + } + else { + return path + "/jcr:content"; + } + } + + @Override + protected String getConfigCollectionParentResolvePath(String path) { + if (containsJcrContent(path)) { + return replaceBucketName(path); + } + else { + return replaceBucketName(path) + "/jcr:content"; + } + } + + @Override + protected String getConfigCollectionParentPersistPath(String path) { + if (containsJcrContent(path)) { + return path; + } + else { + return path + "/jcr:content"; + } + } + + @Override + protected String getConfigCollectionItemResolvePath(String path) { + return path; + } + + @Override + protected String getConfigCollectionItemPersistPath(String path) { + return path; + } + + private String replaceBucketName(String path) { + return StringUtils.replace(path, "/sling:configs/", "/settings/"); + } + + @Override + protected String[] getAlternativeBucketNames() { + return new String[] { "settings" }; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplCustomPersistenceTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplCustomPersistenceTest.java new file mode 100644 index 000000000..07f8b237f --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplCustomPersistenceTest.java @@ -0,0 +1,80 @@ +/* + * 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.caconfig.management.impl; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.osgi.framework.Constants; + +/** + * Test {@link ConfigurationManagerImpl} with custom persistence. + */ +@RunWith(MockitoJUnitRunner.class) +public class ConfigurationManagerImplCustomPersistenceTest extends ConfigurationManagerImplTest { + + @Before + public void setUpCustomPersistence() { + // custom strategy which redirects all config resources to a jcr:content subnode + context.registerService(ConfigurationPersistenceStrategy2.class, + new CustomConfigurationPersistenceStrategy(), Constants.SERVICE_RANKING, 2000); + } + + @Override + protected String getConfigResolvePath(String path) { + return replaceBucketName(path) + "/jcr:content"; + } + + @Override + protected String getConfigPersistPath(String path) { + return path + "/jcr:content"; + } + + @Override + protected String getConfigCollectionParentResolvePath(String path) { + return replaceBucketName(path); + } + + @Override + protected String getConfigCollectionParentPersistPath(String path) { + return path; + } + + @Override + protected String getConfigCollectionItemResolvePath(String path) { + return replaceBucketName(path) + "/jcr:content"; + } + + @Override + protected String getConfigCollectionItemPersistPath(String path) { + return path + "/jcr:content"; + } + + private String replaceBucketName(String path) { + return StringUtils.replace(path, "/sling:configs/", "/settings/"); + } + + @Override + protected String[] getAlternativeBucketNames() { + return new String[] { "settings" }; + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplNoDefaultTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplNoDefaultTest.java new file mode 100644 index 000000000..0e957d518 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplNoDefaultTest.java @@ -0,0 +1,147 @@ +/* + * 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.caconfig.management.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +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.mockito.Mockito.when; + +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.impl.ConfigurationTestUtils; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +/** + * Test {@link ConfigurationManagerImpl} with no default implementation of the multiplexed services. + */ +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("null") +public class ConfigurationManagerImplNoDefaultTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Mock + private ConfigurationMetadataProvider configurationMetadataProvider; + + private ConfigurationManager underTest; + + private Resource contextResourceNoConfig; + private ConfigurationMetadata configMetadata; + + private static final String CONFIG_NAME = "testConfig"; + private static final String CONFIG_COL_NAME = "testConfigCol"; + + @Before + public void setUp() { + context.registerService(ConfigurationMetadataProvider.class, configurationMetadataProvider); + ConfigurationTestUtils.registerConfigurationResolverWithoutDefaultImpl(context); + underTest = context.registerInjectActivateService(new ConfigurationManagerImpl()); + + contextResourceNoConfig = context.create().resource("/content/testNoConfig", + PROPERTY_CONFIG_REF, "/conf/testNoConfig"); + + configMetadata = new ConfigurationMetadata(CONFIG_NAME, ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("prop2", String.class), + new PropertyMetadata<>("prop3", 5)), + false); + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_NAME)).thenReturn(configMetadata); + + configMetadata = new ConfigurationMetadata(CONFIG_COL_NAME, ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("prop2", String.class), + new PropertyMetadata<>("prop3", 5)), + true); + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_COL_NAME)).thenReturn(configMetadata); + } + + protected String getConfigPropertiesPath(String path) { + return path; + } + + @Test + public void testGet_NoConfigResource() { + ConfigurationData configData = underTest.getConfiguration(contextResourceNoConfig, CONFIG_NAME); + assertNotNull(configData); + + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData.getPropertyNames()); + assertNull(configData.getValues().get("prop1", String.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData.getValueInfo("prop1").isInherited()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + } + + @Test + public void testGet_NoConfigResource_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_NAME)).thenReturn(null); + + ConfigurationData configData = underTest.getConfiguration(contextResourceNoConfig, CONFIG_NAME); + assertNull(configData); + } + + @Test + public void testGetCollection_NoConfigResources() { + List configDatas = ImmutableList.copyOf(underTest.getConfigurationCollection(contextResourceNoConfig, CONFIG_COL_NAME).getItems()); + assertEquals(0, configDatas.size()); + } + + @Test + public void testGetCollection_NoConfigResources_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_COL_NAME)).thenReturn(null); + + List configDatas = ImmutableList.copyOf(underTest.getConfigurationCollection(contextResourceNoConfig, CONFIG_COL_NAME).getItems()); + assertEquals(0, configDatas.size()); + } + + @Test + public void testNewCollectionItem() { + ConfigurationData newItem = underTest.newCollectionItem(contextResourceNoConfig, CONFIG_COL_NAME); + assertNotNull(newItem); + assertEquals((Integer)5, newItem.getEffectiveValues().get("prop3", 0)); + } + + @Test + public void testNewCollectionItem_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_COL_NAME)).thenReturn(null); + + ConfigurationData newItem = underTest.newCollectionItem(contextResourceNoConfig, CONFIG_COL_NAME); + assertNull(newItem); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplTest.java new file mode 100644 index 000000000..10591b1f0 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImplTest.java @@ -0,0 +1,891 @@ +/* + * 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.caconfig.management.impl; + +import static org.apache.sling.caconfig.impl.def.ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +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; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.impl.ConfigurationTestUtils; +import org.apache.sling.caconfig.impl.def.ConfigurationDefNameConstants; +import org.apache.sling.caconfig.impl.override.DummyConfigurationOverrideProvider; +import org.apache.sling.caconfig.management.ConfigurationCollectionData; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.ImmutableSortedSet; + +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("null") +public class ConfigurationManagerImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Mock + private ConfigurationMetadataProvider configurationMetadataProvider; + + private ConfigurationManager underTest; + + private Resource contextResource; + private Resource contextResourceLevel2; + private Resource contextResourceLevel3; + private Resource contextResourceNoConfig; + + private static final String CONFIG_NAME = "testConfig"; + private static final String CONFIG_COL_NAME = "testConfigCol"; + private static final String CONFIG_NESTED_NAME = "testConfigNested"; + + @Before + public void setUp() throws Exception { + provideCustomOsgiConfig(); + + context.registerService(ConfigurationMetadataProvider.class, configurationMetadataProvider); + ConfigurationTestUtils.registerConfigurationResolver(context, + "configBucketNames", getAlternativeBucketNames()); + underTest = context.registerInjectActivateService(new ConfigurationManagerImpl()); + + contextResource = context.create().resource("/content/test", + PROPERTY_CONFIG_REF, "/conf/test"); + contextResourceLevel2 = context.create().resource("/content/test/level2", + PROPERTY_CONFIG_REF, "/conf/test/level2"); + contextResourceLevel3 = context.create().resource("/content/test/level2/level3", + PROPERTY_CONFIG_REF, "/conf/test/level2/level3"); + contextResourceNoConfig = context.create().resource("/content/testNoConfig", + PROPERTY_CONFIG_REF, "/conf/testNoConfig"); + + context.create().resource(getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NAME), + "prop1", "value1", + "prop4", true); + context.create().resource(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/sling:configs/" + CONFIG_COL_NAME) + "/1"), + "prop1", "value1"); + context.create().resource(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/sling:configs/" + CONFIG_COL_NAME) + "/2"), + "prop4", true); + + // test fixture with resource collection inheritance on level 2 + context.create().resource(getConfigCollectionParentResolvePath("/conf/test/level2/sling:configs/" + CONFIG_COL_NAME), + PROPERTY_CONFIG_COLLECTION_INHERIT, true); + context.create().resource(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/level2/sling:configs/" + CONFIG_COL_NAME) + "/1"), + "prop1", "value1_level2"); + + // test fixture with property inheritance and resource collection inheritance on level 3 + context.create().resource(getConfigResolvePath("/conf/test/level2/level3/sling:configs/" + CONFIG_NAME), + "prop4", false, + "prop5", "value5_level3", + PROPERTY_CONFIG_PROPERTY_INHERIT, true); + context.create().resource(getConfigCollectionParentResolvePath("/conf/test/level2/level3/sling:configs/" + CONFIG_COL_NAME), + PROPERTY_CONFIG_COLLECTION_INHERIT, true); + context.create().resource(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/level2/level3/sling:configs/" + CONFIG_COL_NAME) + "/1"), + "prop4", false, + "prop5", "value5_level3", + PROPERTY_CONFIG_PROPERTY_INHERIT, true); + + // test fixture nested configuration + context.create().resource(getConfigResolvePath("/conf/test/level2/sling:configs/" + CONFIG_NESTED_NAME), + "prop1", "value1", + "prop4", true); + context.create().resource(getConfigResolvePath(getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NESTED_NAME) + "/propSub"), + "prop1", "propSubValue1", + "prop4", true); + context.create().resource(getConfigResolvePath(getConfigResolvePath(getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NESTED_NAME) + "/propSub") + "/propSubLevel2"), + "prop1", "propSubLevel2Value1", + "prop4", true); + context.create().resource(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath(getConfigResolvePath("/conf/test/level2/sling:configs/" + CONFIG_NESTED_NAME) + "/propSubList") + "/item1"), + "prop1", "propSubListValue1.1"); + context.create().resource(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath(getConfigResolvePath("/conf/test/level2/sling:configs/" + CONFIG_NESTED_NAME) + "/propSubList") + "/item2"), + "prop1", "propSubListValue1.2"); + context.create().resource(getConfigResolvePath(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath(getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NESTED_NAME) + "/propSubList") + "/item1") + "/propSub"), + "prop1", "propSubList1_proSubValue1", + "prop4", true); + + + // config metadata singleton config + ConfigurationMetadata configMetadata = new ConfigurationMetadata(CONFIG_NAME, ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("prop2", String.class), + new PropertyMetadata<>("prop3", 5)), + false); + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_NAME)).thenReturn(configMetadata); + + // config metadata config collection + ConfigurationMetadata configColMetadata = new ConfigurationMetadata(CONFIG_COL_NAME, ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("prop2", String.class), + new PropertyMetadata<>("prop3", 5)), + true); + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_COL_NAME)).thenReturn(configColMetadata); + + // config metadata nested config + /* + * testConfigNested + * | + * +- propSub + * | | + * | +- propSubLevel2 + * | + * +- propSubList + * | + * +- + * | + * +- propSub + * | + * +- propSubLevel2 + */ + ConfigurationMetadata propSubLevel2Metadata = new ConfigurationMetadata("propSubLevel2", ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValueLevel2")), + false); + ConfigurationMetadata propSubMetadata = new ConfigurationMetadata("propSub", ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("prop2", String.class), + new PropertyMetadata<>("prop3", 5), + new PropertyMetadata<>("propSubLevel2", ConfigurationMetadata.class).configurationMetadata(propSubLevel2Metadata)), + false); + ConfigurationMetadata propSubListMetadata = new ConfigurationMetadata("propSubList", ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValueSubList"), + new PropertyMetadata<>("propSub", ConfigurationMetadata.class).configurationMetadata(propSubMetadata)), + true); + ConfigurationMetadata configNestedMetadata = new ConfigurationMetadata(CONFIG_NESTED_NAME, ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("propSub", ConfigurationMetadata.class).configurationMetadata(propSubMetadata), + new PropertyMetadata<>("propSubList", ConfigurationMetadata[].class).configurationMetadata(propSubListMetadata)), + false); + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_NESTED_NAME)).thenReturn(configNestedMetadata); + + when(configurationMetadataProvider.getConfigurationNames()).thenReturn(ImmutableSortedSet.of(CONFIG_NAME, CONFIG_COL_NAME, CONFIG_NESTED_NAME)); + } + + protected void provideCustomOsgiConfig() throws Exception { + // may be overwritten by sublcasses + } + + protected String getConfigResolvePath(String path) { + return path; + } + + protected String getConfigPersistPath(String path) { + return path; + } + + protected String getConfigCollectionParentResolvePath(String path) { + return path; + } + + protected String getConfigCollectionParentPersistPath(String path) { + return path; + } + + protected String getConfigCollectionItemResolvePath(String path) { + return path; + } + + protected String getConfigCollectionItemPersistPath(String path) { + return path; + } + + protected String[] getAlternativeBucketNames() { + return new String[0]; + } + + @Test + public void testGetConfiguration() { + ConfigurationData configData = underTest.getConfiguration(contextResource, CONFIG_NAME); + assertNotNull(configData); + assertFalse(configData.isInherited()); + + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "prop4"), configData.getPropertyNames()); + assertEquals("value1", configData.getValues().get("prop1", String.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData.getValueInfo("prop1").isInherited()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + + assertFalse(configData.getValues().get(ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT, false)); + } + + @Test + public void testGetConfiguration_WithResourceInheritance() { + ConfigurationData configData = underTest.getConfiguration(contextResourceLevel2, CONFIG_NAME); + assertNotNull(configData); + assertTrue(configData.isInherited()); + + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "prop4"), configData.getPropertyNames()); + assertNull(configData.getValues().get("prop1", String.class)); + assertEquals("value1", configData.getEffectiveValues().get("prop1", String.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", 0)); + + String configPath = getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NAME); + assertEquals(configPath, configData.getValueInfo("prop1").getConfigSourcePath()); + assertTrue(configData.getValueInfo("prop1").isInherited()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + assertNull(configData.getValueInfo("prop3").getConfigSourcePath()); + + assertFalse(configData.getValues().get(ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT, false)); + } + + @Test + public void testGetConfiguration_WithPropertyInheritance() { + ConfigurationData configData = underTest.getConfiguration(contextResourceLevel3, CONFIG_NAME); + assertNotNull(configData); + assertFalse(configData.isInherited()); + + assertTrue(configData.getPropertyNames().containsAll(ImmutableSet.of("prop1", "prop2", "prop3", "prop4", "prop5"))); + assertNull(configData.getValues().get("prop1", String.class)); + assertNull(configData.getValues().get("prop2", String.class)); + assertNull(configData.getValues().get("prop3", Integer.class)); + assertFalse(configData.getValues().get("prop4", Boolean.class)); + assertEquals("value5_level3", configData.getValues().get("prop5", String.class)); + + assertEquals("value1", configData.getEffectiveValues().get("prop1", String.class)); + assertNull(configData.getEffectiveValues().get("prop2", String.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", 0)); + assertFalse(configData.getEffectiveValues().get("prop4", Boolean.class)); + assertEquals("value5_level3", configData.getEffectiveValues().get("prop5", String.class)); + + String configPath = getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NAME); + String configPathLevel3 = getConfigResolvePath("/conf/test/level2/level3/sling:configs/" + CONFIG_NAME); + assertTrue(configData.getValueInfo("prop1").isInherited()); + assertEquals(configPath, configData.getValueInfo("prop1").getConfigSourcePath()); + assertFalse(configData.getValueInfo("prop2").isInherited()); + assertNull(configData.getValueInfo("prop2").getConfigSourcePath()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + assertNull(configData.getValueInfo("prop3").getConfigSourcePath()); + assertFalse(configData.getValueInfo("prop4").isInherited()); + assertEquals(configPathLevel3, configData.getValueInfo("prop4").getConfigSourcePath()); + assertFalse(configData.getValueInfo("prop5").isInherited()); + assertEquals(configPathLevel3, configData.getValueInfo("prop5").getConfigSourcePath()); + + assertTrue(configData.getValues().get(ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT, false)); + } + + @Test + public void testGetConfiguration_WithOverride() { + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "[/content]" + CONFIG_NAME + "={\"prop1\":\"override1\"}")); + + ConfigurationData configData = underTest.getConfiguration(contextResource, CONFIG_NAME); + assertNotNull(configData); + assertFalse(configData.isInherited()); + assertTrue(configData.isOverridden()); + + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData.getPropertyNames()); + assertEquals("value1", configData.getValues().get("prop1", String.class)); + assertEquals("override1", configData.getEffectiveValues().get("prop1", String.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData.getValueInfo("prop1").isInherited()); + assertTrue(configData.getValueInfo("prop1").isOverridden()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + assertTrue(configData.getValueInfo("prop3").isOverridden()); + } + + /** + * Test override for context path on which no configuration exists below /conf - not even on the inheritance lookup paths (SLING-7016) + */ + @Test + public void testGetConfiguration_WithOverride_NoExistingConfig() throws PersistenceException { + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "[/content]" + CONFIG_NAME + "={\"prop1\":\"override1\"}")); + + // delete all existing config + ResourceResolver resolver = context.resourceResolver(); + Resource existingConf = resolver.getResource("/conf/test"); + resolver.delete(existingConf); + + ConfigurationData configData = underTest.getConfiguration(contextResource, CONFIG_NAME); + assertNotNull(configData); + assertFalse(configData.isInherited()); + assertTrue(configData.isOverridden()); + + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData.getPropertyNames()); + assertNull(configData.getValues().get("prop1", String.class)); + assertEquals("override1", configData.getEffectiveValues().get("prop1", String.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData.getValueInfo("prop1").isInherited()); + assertTrue(configData.getValueInfo("prop1").isOverridden()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + assertTrue(configData.getValueInfo("prop3").isOverridden()); + } + + @Test + public void testGetConfiguration_NoConfigResource() { + ConfigurationData configData = underTest.getConfiguration(contextResourceNoConfig, CONFIG_NAME); + assertNotNull(configData); + assertFalse(configData.isInherited()); + + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData.getPropertyNames()); + assertNull(configData.getValues().get("prop1", String.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData.getValueInfo("prop1").isInherited()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + } + + @Test + public void testGetConfiguration_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_NAME)).thenReturn(null); + + ConfigurationData configData = underTest.getConfiguration(contextResource, CONFIG_NAME); + assertNotNull(configData); + assertFalse(configData.isInherited()); + + assertEquals(ImmutableSet.of("prop1", "prop4"), configData.getPropertyNames()); + assertEquals("value1", configData.getValues().get("prop1", String.class)); + assertEquals((Integer)0, configData.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData.getValueInfo("prop1").isInherited()); + assertFalse(configData.getValueInfo("prop3").isInherited()); + } + + @Test + public void testGetConfiguration_NoConfigResource_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_NAME)).thenReturn(null); + + ConfigurationData configData = underTest.getConfiguration(contextResourceNoConfig, CONFIG_NAME); + assertNull(configData); + } + + @Test + public void testGetConfigurationCollection() { + ConfigurationCollectionData configCollectionData = underTest.getConfigurationCollection(contextResource, CONFIG_COL_NAME); + List configDatas = ImmutableList.copyOf(configCollectionData.getItems()); + assertEquals(2, configDatas.size()); + + ConfigurationData configData1 = configDatas.get(0); + assertFalse(configData1.isInherited()); + assertEquals("1", configData1.getCollectionItemName()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData1.getPropertyNames()); + assertEquals("value1", configData1.getValues().get("prop1", String.class)); + assertEquals((Integer)5, configData1.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData1.getValueInfo("prop1").isInherited()); + assertFalse(configData1.getValueInfo("prop3").isInherited()); + + ConfigurationData configData2 = configDatas.get(1); + assertFalse(configData2.isInherited()); + assertEquals("2", configData2.getCollectionItemName()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "prop4"), configData2.getPropertyNames()); + assertNull(configData2.getValues().get("prop1", String.class)); + assertEquals((Integer)5, configData2.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData2.getValueInfo("prop1").isInherited()); + assertFalse(configData2.getValueInfo("prop3").isInherited()); + + assertNull(configCollectionData.getProperties().get(ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT)); + } + + @Test + public void testGetConfigurationCollection_WithResourceCollectionInheritance() { + ConfigurationCollectionData configCollectionData = underTest.getConfigurationCollection(contextResourceLevel2, CONFIG_COL_NAME); + List configDatas = ImmutableList.copyOf(configCollectionData.getItems()); + assertEquals(2, configDatas.size()); + + ConfigurationData configData1 = configDatas.get(0); + assertFalse(configData1.isInherited()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData1.getPropertyNames()); + assertEquals("value1_level2", configData1.getValues().get("prop1", String.class)); + assertEquals("value1_level2", configData1.getEffectiveValues().get("prop1", String.class)); + assertEquals((Integer)5, configData1.getEffectiveValues().get("prop3", 0)); + + String configPath1 = getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/level2/sling:configs/" + CONFIG_COL_NAME) + "/1"); + assertFalse(configData1.getValueInfo("prop1").isInherited()); + assertEquals(configPath1, configData1.getValueInfo("prop1").getConfigSourcePath()); + assertFalse(configData1.getValueInfo("prop3").isInherited()); + assertNull(configData1.getValueInfo("prop3").getConfigSourcePath()); + + ConfigurationData configData2 = configDatas.get(1); + assertTrue(configData2.isInherited()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "prop4"), configData2.getPropertyNames()); + assertNull(configData2.getValues().get("prop1", String.class)); + assertEquals((Integer)5, configData2.getEffectiveValues().get("prop3", 0)); + + String configPath2 = getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/sling:configs/" + CONFIG_COL_NAME) + "/2"); + assertTrue(configData2.getValueInfo("prop4").isInherited()); + assertEquals(configPath2, configData2.getValueInfo("prop4").getConfigSourcePath()); + assertFalse(configData2.getValueInfo("prop3").isInherited()); + assertNull(configData2.getValueInfo("prop3").getConfigSourcePath()); + + assertTrue((Boolean)configCollectionData.getProperties().get(ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT)); + } + + @Test + public void testGetConfigurationCollection_WithResourceCollectionAndPropertyInheritance() { + ConfigurationCollectionData configCollectionData = underTest.getConfigurationCollection(contextResourceLevel3, CONFIG_COL_NAME); + List configDatas = ImmutableList.copyOf(configCollectionData.getItems()); + assertEquals(2, configDatas.size()); + + ConfigurationData configData1 = configDatas.get(0); + assertFalse(configData1.isInherited()); + assertTrue(configData1.getPropertyNames().containsAll(ImmutableSet.of("prop1", "prop2", "prop3", "prop4", "prop5"))); + + assertTrue(configData1.getPropertyNames().containsAll(ImmutableSet.of("prop1", "prop2", "prop3", "prop4", "prop5"))); + assertNull(configData1.getValues().get("prop1", String.class)); + assertNull(configData1.getValues().get("prop2", String.class)); + assertNull(configData1.getValues().get("prop3", Integer.class)); + assertFalse(configData1.getValues().get("prop4", Boolean.class)); + assertEquals("value5_level3", configData1.getValues().get("prop5", String.class)); + + assertEquals("value1_level2", configData1.getEffectiveValues().get("prop1", String.class)); + assertNull(configData1.getEffectiveValues().get("prop2", String.class)); + assertEquals((Integer)5, configData1.getEffectiveValues().get("prop3", 0)); + assertFalse(configData1.getEffectiveValues().get("prop4", Boolean.class)); + assertEquals("value5_level3", configData1.getEffectiveValues().get("prop5", String.class)); + + String configPathLevel2 = getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/level2/sling:configs/" + CONFIG_COL_NAME) + "/1"); + String configPathLevel3 = getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/level2/level3/sling:configs/" + CONFIG_COL_NAME) + "/1"); + assertTrue(configData1.getValueInfo("prop1").isInherited()); + assertEquals(configPathLevel2, configData1.getValueInfo("prop1").getConfigSourcePath()); + assertFalse(configData1.getValueInfo("prop2").isInherited()); + assertNull(configData1.getValueInfo("prop2").getConfigSourcePath()); + assertFalse(configData1.getValueInfo("prop3").isInherited()); + assertNull(configData1.getValueInfo("prop3").getConfigSourcePath()); + assertFalse(configData1.getValueInfo("prop4").isInherited()); + assertEquals(configPathLevel3, configData1.getValueInfo("prop4").getConfigSourcePath()); + assertFalse(configData1.getValueInfo("prop5").isInherited()); + assertEquals(configPathLevel3, configData1.getValueInfo("prop5").getConfigSourcePath()); + + ConfigurationData configData2 = configDatas.get(1); + assertFalse(configData1.isInherited()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "prop4"), configData2.getPropertyNames()); + assertNull(configData2.getValues().get("prop1", String.class)); + assertEquals((Integer)5, configData2.getEffectiveValues().get("prop3", 0)); + + String configPath2 = getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath("/conf/test/sling:configs/" + CONFIG_COL_NAME) + "/2"); + assertTrue(configData2.getValueInfo("prop4").isInherited()); + assertEquals(configPath2, configData2.getValueInfo("prop4").getConfigSourcePath()); + assertFalse(configData2.getValueInfo("prop3").isInherited()); + assertNull(configData2.getValueInfo("prop3").getConfigSourcePath()); + + assertTrue((Boolean)configCollectionData.getProperties().get(ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT)); + assertTrue(configData1.getValues().get(ConfigurationDefNameConstants.PROPERTY_CONFIG_PROPERTY_INHERIT, false)); + } + + @Test + public void testGetConfigurationCollection_WithOverride() { + context.registerService(ConfigurationOverrideProvider.class, new DummyConfigurationOverrideProvider( + "[/content]" + CONFIG_COL_NAME + "/prop1=\"override1\"")); + + List configDatas = ImmutableList.copyOf(underTest.getConfigurationCollection(contextResource, CONFIG_COL_NAME).getItems()); + assertEquals(2, configDatas.size()); + + ConfigurationData configData1 = configDatas.get(0); + assertFalse(configData1.isInherited()); + assertFalse(configData1.isOverridden()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData1.getPropertyNames()); + assertEquals("value1", configData1.getValues().get("prop1", String.class)); + assertEquals("override1", configData1.getEffectiveValues().get("prop1", String.class)); + assertEquals((Integer)5, configData1.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData1.getValueInfo("prop1").isInherited()); + assertTrue(configData1.getValueInfo("prop1").isOverridden()); + assertFalse(configData1.getValueInfo("prop3").isInherited()); + assertFalse(configData1.getValueInfo("prop3").isOverridden()); + + ConfigurationData configData2 = configDatas.get(1); + assertFalse(configData2.isInherited()); + assertFalse(configData2.isOverridden()); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3", "prop4"), configData2.getPropertyNames()); + assertNull(configData2.getValues().get("prop1", String.class)); + assertEquals("override1", configData2.getEffectiveValues().get("prop1", String.class)); + assertEquals((Integer)5, configData2.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData2.getValueInfo("prop1").isInherited()); + assertTrue(configData2.getValueInfo("prop1").isOverridden()); + assertFalse(configData2.getValueInfo("prop3").isInherited()); + assertFalse(configData2.getValueInfo("prop3").isOverridden()); + } + + @Test + public void testGetConfigurationCollection_NoConfigResources() { + List configDatas = ImmutableList.copyOf(underTest.getConfigurationCollection(contextResourceNoConfig, CONFIG_COL_NAME).getItems()); + assertEquals(0, configDatas.size()); + } + + @Test + public void testGetConfigurationCollection_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_COL_NAME)).thenReturn(null); + + List configDatas = ImmutableList.copyOf(underTest.getConfigurationCollection(contextResource, CONFIG_COL_NAME).getItems()); + assertEquals(2, configDatas.size()); + + ConfigurationData configData1 = configDatas.get(0); + assertFalse(configData1.isInherited()); + assertEquals(ImmutableSet.of("prop1"), configData1.getPropertyNames()); + assertEquals("value1", configData1.getValues().get("prop1", String.class)); + assertEquals((Integer)0, configData1.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData1.getValueInfo("prop1").isInherited()); + assertFalse(configData1.getValueInfo("prop3").isInherited()); + + ConfigurationData configData2 = configDatas.get(1); + assertFalse(configData2.isInherited()); + assertEquals(ImmutableSet.of("prop4"), configData2.getPropertyNames()); + assertNull(configData2.getValues().get("prop1", String.class)); + assertEquals((Integer)0, configData2.getEffectiveValues().get("prop3", 0)); + + assertFalse(configData2.getValueInfo("prop1").isInherited()); + assertFalse(configData2.getValueInfo("prop3").isInherited()); + } + + @Test + public void testGetConfigurationCollection_NoConfigResources_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_COL_NAME)).thenReturn(null); + + List configDatas = ImmutableList.copyOf(underTest.getConfigurationCollection(contextResourceNoConfig, CONFIG_COL_NAME).getItems()); + assertEquals(0, configDatas.size()); + } + + @Test + public void testPersistConfiguration() throws Exception { + underTest.persistConfiguration(contextResourceNoConfig, CONFIG_NAME, + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1"))); + context.resourceResolver().commit(); + + String configPath = getConfigPersistPath("/conf/testNoConfig/sling:configs/" + CONFIG_NAME); + ValueMap props = context.resourceResolver().getResource(configPath).getValueMap(); + assertEquals("value1", props.get("prop1")); + } + + @Test + public void testPersistConfigurationCollection() throws Exception { + underTest.persistConfigurationCollection(contextResourceNoConfig, CONFIG_COL_NAME, + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1")).collectionItemName("0"), + new ConfigurationPersistData(ImmutableMap.of("prop2", 5)).collectionItemName("1")) + ).properties(ImmutableMap.of("sling:configCollectionInherit", true)) + ); + context.resourceResolver().commit(); + + String configPath0 = getConfigCollectionItemPersistPath(getConfigCollectionParentPersistPath("/conf/testNoConfig/sling:configs/" + CONFIG_COL_NAME) + "/0"); + ValueMap props0 = context.resourceResolver().getResource(configPath0).getValueMap(); + assertEquals("value1", props0.get("prop1")); + + String configPath1 = getConfigCollectionItemPersistPath(getConfigCollectionParentPersistPath("/conf/testNoConfig/sling:configs/" + CONFIG_COL_NAME) + "/1"); + ValueMap props1 = context.resourceResolver().getResource(configPath1).getValueMap(); + assertEquals((Integer)5, props1.get("prop2")); + + ConfigurationCollectionData colData = underTest.getConfigurationCollection(contextResourceNoConfig, CONFIG_COL_NAME); + assertEquals(true, colData.getProperties().get("sling:configCollectionInherit")); + } + + @Test + public void testNewCollectionItem() { + ConfigurationData newItem = underTest.newCollectionItem(contextResource, CONFIG_COL_NAME); + assertNotNull(newItem); + assertEquals((Integer)5, newItem.getEffectiveValues().get("prop3", 0)); + } + + @Test + public void testNewCollectionItem_NoConfigMetadata() { + when(configurationMetadataProvider.getConfigurationMetadata(CONFIG_COL_NAME)).thenReturn(null); + + ConfigurationData newItem = underTest.newCollectionItem(contextResource, CONFIG_COL_NAME); + assertNull(newItem); + } + + @Test + public void testGetConfigurationConfigurationNames() { + assertEquals(ImmutableSortedSet.of(CONFIG_NAME, CONFIG_COL_NAME, CONFIG_NESTED_NAME), underTest.getConfigurationNames()); + } + + @Test + public void testGetConfigurationConfigurationMetadata() { + assertEquals(CONFIG_NAME, underTest.getConfigurationMetadata(CONFIG_NAME).getName()); + } + + @Test + public void testGetConfigurationNested() { + ConfigurationData configData = underTest.getConfiguration(contextResourceLevel2, CONFIG_NESTED_NAME); + assertNotNull(configData); + + // root level + assertEquals(ImmutableSet.of("prop1", "propSub", "propSubList", "prop4"), configData.getPropertyNames()); + assertEquals("value1", configData.getValues().get("prop1", String.class)); + assertEquals("value1", configData.getEffectiveValues().get("prop1", String.class)); + assertTrue(configData.getValues().get("prop4", false)); + assertTrue(configData.getEffectiveValues().get("prop4", false)); + + assertEquals(ConfigurationMetadata.class, configData.getValueInfo("propSub").getPropertyMetadata().getType()); + assertEquals(ConfigurationMetadata[].class, configData.getValueInfo("propSubList").getPropertyMetadata().getType()); + + // propSub + ConfigurationData subData = configData.getValues().get("propSub", ConfigurationData.class); + ConfigurationData subDataEffective = configData.getEffectiveValues().get("propSub", ConfigurationData.class); + assertNotNull(subData); + assertNotNull(subDataEffective); + + assertTrue(ConfigurationData.class.isAssignableFrom(configData.getValueInfo("propSub").getValue().getClass())); + assertTrue(ConfigurationData.class.isAssignableFrom(configData.getValueInfo("propSub").getEffectiveValue().getClass())); + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getValue().getClass())); + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getEffectiveValue().getClass())); + + assertNull(subData.getValues().get("prop1", String.class)); + assertEquals("propSubValue1", subData.getEffectiveValues().get("prop1", String.class)); + assertFalse(subData.getValues().get("prop4", false)); + assertTrue(subData.getEffectiveValues().get("prop4", false)); + + // propSub/propSubLevel2 + ConfigurationData subDataLevel2 = subData.getValues().get("propSubLevel2", ConfigurationData.class); + ConfigurationData subDataLevel2Effective = subData.getEffectiveValues().get("propSubLevel2", ConfigurationData.class); + assertNotNull(subDataLevel2); + assertNotNull(subDataLevel2Effective); + + assertTrue(ConfigurationData.class.isAssignableFrom(subData.getValueInfo("propSubLevel2").getValue().getClass())); + assertTrue(ConfigurationData.class.isAssignableFrom(subData.getValueInfo("propSubLevel2").getEffectiveValue().getClass())); + + assertNull(subDataLevel2.getValues().get("prop1", String.class)); + assertEquals("propSubLevel2Value1", subDataLevel2.getEffectiveValues().get("prop1", String.class)); + assertFalse(subDataLevel2.getValues().get("prop4", false)); + assertTrue(subDataLevel2.getEffectiveValues().get("prop4", false)); + + // propSubList + ConfigurationData[] subListData = configData.getValues().get("propSubList", ConfigurationData[].class); + ConfigurationData[] subListDataEffective = configData.getEffectiveValues().get("propSubList", ConfigurationData[].class); + assertNotNull(subListData); + assertNotNull(subListDataEffective); + + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getValue().getClass())); + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getEffectiveValue().getClass())); + + assertEquals(2, subListData.length); + assertEquals("propSubListValue1.1", subListData[0].getValues().get("prop1", String.class)); + assertEquals("propSubListValue1.1", subListData[0].getEffectiveValues().get("prop1", String.class)); + assertEquals("propSubListValue1.2", subListData[1].getValues().get("prop1", String.class)); + assertEquals("propSubListValue1.2", subListData[1].getEffectiveValues().get("prop1", String.class)); + + ConfigurationData subListDataItem1Sub = subListData[0].getValues().get("propSub", ConfigurationData.class); + ConfigurationData subListDataItem1SubEffecive = subListData[0].getEffectiveValues().get("propSub", ConfigurationData.class); + assertNotNull(subListDataItem1Sub); + assertNotNull(subListDataItem1SubEffecive); + + assertNull(subListDataItem1Sub.getValues().get("prop1", String.class)); + assertEquals("propSubList1_proSubValue1", subListDataItem1Sub.getEffectiveValues().get("prop1", String.class)); + assertFalse(subListDataItem1Sub.getValues().get("prop4", false)); + assertTrue(subListDataItem1Sub.getEffectiveValues().get("prop4", false)); + } + + @Test + public void testGetConfigurationNested_NoConfigResource() { + ConfigurationData configData = underTest.getConfiguration(contextResourceNoConfig, CONFIG_NESTED_NAME); + assertNotNull(configData); + + assertEquals(ImmutableSet.of("prop1", "propSub", "propSubList"), configData.getPropertyNames()); + assertNull(configData.getValues().get("prop1", String.class)); + assertEquals("defValue", configData.getEffectiveValues().get("prop1", String.class)); + + assertEquals(ConfigurationMetadata.class, configData.getValueInfo("propSub").getPropertyMetadata().getType()); + assertEquals(ConfigurationMetadata[].class, configData.getValueInfo("propSubList").getPropertyMetadata().getType()); + + ConfigurationData subData = configData.getValues().get("propSub", ConfigurationData.class); + ConfigurationData subDataEffective = configData.getEffectiveValues().get("propSub", ConfigurationData.class); + assertNotNull(subData); + assertNotNull(subDataEffective); + + assertTrue(ConfigurationData.class.isAssignableFrom(configData.getValueInfo("propSub").getValue().getClass())); + assertTrue(ConfigurationData.class.isAssignableFrom(configData.getValueInfo("propSub").getEffectiveValue().getClass())); + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getValue().getClass())); + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getEffectiveValue().getClass())); + + assertNull(subData.getValues().get("prop1", String.class)); + assertEquals("defValue", subData.getEffectiveValues().get("prop1", String.class)); + + ConfigurationData[] subListData = configData.getValues().get("propSubList", ConfigurationData[].class); + ConfigurationData[] subListDataEffective = configData.getEffectiveValues().get("propSubList", ConfigurationData[].class); + assertNotNull(subListData); + assertNotNull(subListDataEffective); + + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getValue().getClass())); + assertTrue(ConfigurationData[].class.isAssignableFrom(configData.getValueInfo("propSubList").getEffectiveValue().getClass())); + + assertEquals(0, subListData.length); + } + + @Test + public void testGetConfigurationNames() { + assertEquals(ImmutableSortedSet.of(CONFIG_NAME, CONFIG_COL_NAME, CONFIG_NESTED_NAME), underTest.getConfigurationNames()); + } + + @Test + public void testGetConfigurationMetadata() { + ConfigurationMetadata configMetadata = underTest.getConfigurationMetadata(CONFIG_NAME); + assertNotNull(configMetadata); + assertEquals(CONFIG_NAME, configMetadata.getName()); + } + + @Test + public void testGetConfigurationMetadata_Nested() { + ConfigurationMetadata configMetadata = underTest.getConfigurationMetadata(CONFIG_NESTED_NAME); + assertNotNull(configMetadata); + assertEquals(CONFIG_NESTED_NAME, configMetadata.getName()); + } + + @Test + public void testGetConfigurationMetadata_Nested_Sub() { + ConfigurationMetadata configMetadataSub = underTest.getConfigurationMetadata(getConfigResolvePath(CONFIG_NESTED_NAME) + "/propSub"); + assertNotNull(configMetadataSub); + assertEquals("propSub", configMetadataSub.getName()); + } + + @Test + public void testGetConfigurationMetadata_Nested_SubLevel2() { + ConfigurationMetadata configMetadataSubLevel2 = underTest.getConfigurationMetadata(getConfigResolvePath(getConfigResolvePath(CONFIG_NESTED_NAME) + + "/propSub") + "/propSubLevel2"); + assertNotNull(configMetadataSubLevel2); + assertEquals("propSubLevel2", configMetadataSubLevel2.getName()); + } + + @Test + public void testGetConfigurationMetadata_Nested_SubList() { + ConfigurationMetadata configMetadataSubList = underTest.getConfigurationMetadata(getConfigResolvePath(CONFIG_NESTED_NAME) + "/propSubList"); + assertNotNull(configMetadataSubList); + assertEquals("propSubList", configMetadataSubList.getName()); + } + + @Test + public void testGetConfigurationMetadata_Nested_SubList_Sub() throws Exception { + // delete resource already existing in test fixture to test with non-existing resource but existing collection item as parent + context.resourceResolver().delete(context.resourceResolver().getResource( + getConfigResolvePath(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath(getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NESTED_NAME) + + "/propSubList") + "/item1") + "/propSub"))); + + ConfigurationMetadata subListDataItem1Sub = underTest.getConfigurationMetadata(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath(getConfigResolvePath(CONFIG_NESTED_NAME) + + "/propSubList") + "/item1") + "/propSub"); + assertNotNull(subListDataItem1Sub); + assertEquals("propSub", subListDataItem1Sub.getName()); + } + + @Test + public void testGetConfigurationMetadata_Nested_SubList_SubLevel2() throws Exception { + // delete resource already existing in test fixture to test with non-existing resource but existing collection item as parent + context.resourceResolver().delete(context.resourceResolver().getResource( + getConfigResolvePath(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath(getConfigResolvePath("/conf/test/sling:configs/" + CONFIG_NESTED_NAME) + + "/propSubList") + "/item1") + "/propSub"))); + + ConfigurationMetadata subListDataItem1SubLevel2 = underTest.getConfigurationMetadata(getConfigResolvePath(getConfigCollectionItemResolvePath(getConfigCollectionParentResolvePath(getConfigResolvePath(CONFIG_NESTED_NAME) + + "/propSubList") + "/item1") + "/propSub") + "/propSubLevel2"); + assertNotNull(subListDataItem1SubLevel2); + assertEquals("propSubLevel2", subListDataItem1SubLevel2.getName()); + } + + @Test + public void testNewCollectionItem_Nested_SubList() { + ConfigurationData configData = underTest.newCollectionItem(contextResource, getConfigResolvePath(CONFIG_NESTED_NAME) + "/propSubList"); + assertEquals(getConfigResolvePath(CONFIG_NESTED_NAME) + "/propSubList", configData.getConfigName()); + + assertNull(configData.getValues().get("prop1", String.class)); + assertEquals("defValueSubList", configData.getEffectiveValues().get("prop1", String.class)); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetPersistenceResourcePath() { + assertEquals(getConfigResolvePath("/a/b/c"), underTest.getPersistenceResourcePath("/a/b/c")); + assertEquals(getConfigResolvePath("a/b"), underTest.getPersistenceResourcePath("a/b")); + } + + @Test + public void testPersistConfiguration_Nested() throws Exception { + underTest.persistConfiguration(contextResourceLevel2, getConfigResolvePath(getConfigResolvePath(CONFIG_NESTED_NAME) + + "/propSub") + "/propSubLevel2", + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1_persist"))); + context.resourceResolver().commit(); + + ConfigurationData configData = underTest.getConfiguration(contextResourceLevel2, CONFIG_NESTED_NAME); + ConfigurationData subData = configData.getValues().get("propSub", ConfigurationData.class); + ConfigurationData subDataLevel2 = subData.getValues().get("propSubLevel2", ConfigurationData.class); + + assertEquals("value1_persist", subDataLevel2.getValues().get("prop1", String.class)); + assertEquals("value1_persist", subDataLevel2.getEffectiveValues().get("prop1", String.class)); + assertFalse(subDataLevel2.getValues().get("prop4", false)); + assertFalse(subDataLevel2.getEffectiveValues().get("prop4", false)); + } + + @Test + public void testPersistConfigurationCollection_Nested() throws Exception { + underTest.persistConfigurationCollection(contextResourceLevel2, getConfigResolvePath(CONFIG_NESTED_NAME) + "/propSubList", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(ImmutableMap.of("prop1", "value1_persist")).collectionItemName("item1"), + new ConfigurationPersistData(ImmutableMap.of("prop1", "value2_persist")).collectionItemName("item2"), + new ConfigurationPersistData(ImmutableMap.of("prop1", "value3_persist")).collectionItemName("item3")) + )); + context.resourceResolver().commit(); + + ConfigurationData configData = underTest.getConfiguration(contextResourceLevel2, CONFIG_NESTED_NAME); + ConfigurationData[] subListData = configData.getValues().get("propSubList", ConfigurationData[].class); + + assertEquals(3, subListData.length); + assertEquals("value1_persist", subListData[0].getValues().get("prop1", String.class)); + assertEquals("value2_persist", subListData[1].getValues().get("prop1", String.class)); + assertEquals("value3_persist", subListData[2].getValues().get("prop1", String.class)); + } + + @Test + public void testDeleteConfiguration() throws Exception { + underTest.deleteConfiguration(contextResource, CONFIG_NAME); + + ConfigurationData configData = underTest.getConfiguration(contextResource, CONFIG_NAME); + assertEquals(ImmutableSet.of("prop1", "prop2", "prop3"), configData.getPropertyNames()); + + assertNull(configData.getValues().get("prop1", String.class)); + assertEquals("defValue", configData.getEffectiveValues().get("prop1", String.class)); + assertNull(configData.getValues().get("prop2", String.class)); + assertNull(configData.getEffectiveValues().get("prop2", String.class)); + assertNull(configData.getValues().get("prop3", Integer.class)); + assertEquals((Integer)5, configData.getEffectiveValues().get("prop3", Integer.class)); + } + + @Test + public void testDeleteConfigurationCollection() throws Exception { + underTest.deleteConfiguration(contextResource, CONFIG_COL_NAME); + + ConfigurationCollectionData configCollectionData = underTest.getConfigurationCollection(contextResource, CONFIG_COL_NAME); + List configDatas = ImmutableList.copyOf(configCollectionData.getItems()); + assertEquals(0, configDatas.size()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationPersistenceStrategyMultiplexerImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationPersistenceStrategyMultiplexerImplTest.java new file mode 100644 index 000000000..1370f789c --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ConfigurationPersistenceStrategyMultiplexerImplTest.java @@ -0,0 +1,195 @@ +/* + * 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.caconfig.management.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.caconfig.impl.ConfigurationPersistenceStrategyBridge; +import org.apache.sling.caconfig.impl.def.DefaultConfigurationPersistenceStrategy; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.framework.Constants; + +import com.google.common.collect.ImmutableList; + +public class ConfigurationPersistenceStrategyMultiplexerImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationPersistenceStrategyMultiplexer underTest; + + private Resource resource1; + private Resource resource2; + + @Before + public void setUp() { + context.registerInjectActivateService(new ConfigurationManagementSettingsImpl()); + underTest = context.registerInjectActivateService(new ConfigurationPersistenceStrategyMultiplexerImpl()); + context.registerInjectActivateService(new ConfigurationPersistenceStrategyBridge()); + resource1 = context.create().resource("/conf/test1"); + resource2 = context.create().resource("/conf/test2"); + } + + @Test + public void testWithNoStrategies() { + assertNull(underTest.getResource(resource1)); + assertNull(underTest.getResourcePath(resource1.getPath())); + assertFalse(underTest.persistConfiguration(context.resourceResolver(), "/conf/test1", new ConfigurationPersistData(resource1.getValueMap()))); + assertFalse(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/testCol", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(resource1.getValueMap()).collectionItemName(resource1.getName()), + new ConfigurationPersistData(resource2.getValueMap()).collectionItemName(resource2.getName()))))); + assertFalse(underTest.deleteConfiguration(context.resourceResolver(), "/conf/test1")); + } + + @Test + public void testWithDefaultStrategy() { + context.registerInjectActivateService(new DefaultConfigurationPersistenceStrategy()); + + assertSame(resource1, underTest.getResource(resource1)); + assertEquals(resource1.getPath(), underTest.getResourcePath(resource1.getPath())); + assertTrue(underTest.persistConfiguration(context.resourceResolver(), "/conf/test1", new ConfigurationPersistData(resource1.getValueMap()))); + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/testCol", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(resource1.getValueMap()).collectionItemName(resource1.getName()), + new ConfigurationPersistData(resource2.getValueMap()).collectionItemName(resource2.getName()))))); + assertTrue(underTest.deleteConfiguration(context.resourceResolver(), "/conf/test1")); + } + + @SuppressWarnings("deprecation") + @Test + public void testMultipleStrategies() { + + // strategy 1 (using old ConfigurationPersistenceStrategy with bridge to ConfigurationPersistenceStrategy2) + context.registerService(org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy.class, + new org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy() { + @Override + public Resource getResource(@NotNull Resource resource) { + return resource2; + } + @Override + public String getResourcePath(@NotNull String resourcePath) { + return resource2.getPath(); + } + @Override + public boolean persistConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath, + @NotNull ConfigurationPersistData data) { + return true; + } + @Override + public boolean persistConfigurationCollection(@NotNull ResourceResolver resourceResolver, + @NotNull String configResourceCollectionParentPath, @NotNull ConfigurationCollectionPersistData data) { + return false; + } + @Override + public boolean deleteConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath) { + return false; + } + }, Constants.SERVICE_RANKING, 2000); + + // strategy 2 + context.registerService(ConfigurationPersistenceStrategy2.class, new ConfigurationPersistenceStrategy2() { + @Override + public Resource getResource(@NotNull Resource resource) { + return resource1; + } + @Override + public Resource getCollectionParentResource(@NotNull Resource resource) { + return resource1; + } + @Override + public Resource getCollectionItemResource(@NotNull Resource resource) { + return resource1; + } + @Override + public String getResourcePath(@NotNull String resourcePath) { + return resource1.getPath(); + } + @Override + public String getCollectionParentResourcePath(@NotNull String resourcePath) { + return resource1.getPath(); + } + @Override + public String getCollectionItemResourcePath(@NotNull String resourcePath) { + return resource1.getPath(); + } + @Override + public String getConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + return resource1.getPath(); + } + @Override + public String getCollectionParentConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + return resource1.getPath(); + } + @Override + public String getCollectionItemConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + return resource1.getPath(); + } + @Override + public boolean persistConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath, + @NotNull ConfigurationPersistData data) { + return false; + } + @Override + public boolean persistConfigurationCollection(@NotNull ResourceResolver resourceResolver, + @NotNull String configResourceCollectionParentPath, @NotNull ConfigurationCollectionPersistData data) { + return true; + } + @Override + public boolean deleteConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath) { + return true; + } + }, Constants.SERVICE_RANKING, 1000); + + assertSame(resource2, underTest.getResource(resource1)); + assertSame(resource1, underTest.getCollectionParentResource(resource1)); + assertSame(resource2, underTest.getCollectionItemResource(resource1)); + assertEquals(resource2.getPath(), underTest.getResourcePath(resource1.getPath())); + assertEquals(resource1.getPath(), underTest.getCollectionParentResourcePath(resource1.getPath())); + assertEquals(resource2.getPath(), underTest.getCollectionItemResourcePath(resource1.getPath())); + assertEquals(resource2.getPath(), underTest.getConfigName(resource1.getPath(), null)); + assertEquals(resource1.getPath(), underTest.getCollectionParentConfigName(resource1.getPath(), null)); + assertEquals(resource2.getPath(), underTest.getCollectionItemConfigName(resource1.getPath(), null)); + assertEquals(ImmutableList.of(resource2.getPath(), resource1.getPath()), ImmutableList.copyOf(underTest.getAllConfigNames(resource1.getPath()))); + assertEquals(ImmutableList.of(resource1.getPath()), ImmutableList.copyOf(underTest.getAllCollectionParentConfigNames(resource1.getPath()))); + assertEquals(ImmutableList.of(resource2.getPath(), resource1.getPath()), ImmutableList.copyOf(underTest.getAllCollectionItemConfigNames(resource1.getPath()))); + assertTrue(underTest.persistConfiguration(context.resourceResolver(), "/conf/test1", new ConfigurationPersistData(resource1.getValueMap()))); + assertTrue(underTest.persistConfigurationCollection(context.resourceResolver(), "/conf/testCol", + new ConfigurationCollectionPersistData(ImmutableList.of( + new ConfigurationPersistData(resource1.getValueMap()).collectionItemName(resource1.getName()), + new ConfigurationPersistData(resource2.getValueMap()).collectionItemName(resource2.getName()))))); + assertTrue(underTest.deleteConfiguration(context.resourceResolver(), "/conf/test1")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ContextPathStrategyMultiplexerImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ContextPathStrategyMultiplexerImplTest.java new file mode 100644 index 000000000..7a33a3da7 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/ContextPathStrategyMultiplexerImplTest.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.apache.sling.caconfig.resource.impl.util.ContextResourceTestUtil.toContextResourceIterator; +import static org.apache.sling.caconfig.resource.impl.util.ContextResourceTestUtil.toResourceIterator; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.resource.impl.def.DefaultContextPathStrategy; +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.apache.sling.hamcrest.ResourceIteratorMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class ContextPathStrategyMultiplexerImplTest { + + @Rule + public SlingContext context = new SlingContext(); + + private ContextPathStrategyMultiplexerImpl underTest; + + private Resource site1Page1; + private Resource site2Page1; + + @Before + public void setUp() { + underTest = context.registerInjectActivateService(new ContextPathStrategyMultiplexerImpl()); + + // content resources that form a deeper hierarchy + context.build() + .resource("/content/tenant1", PROPERTY_CONFIG_REF, "/conf/tenant1") + .resource("/content/tenant1/region1", PROPERTY_CONFIG_REF, "/conf/tenant1/region1") + .resource("/content/tenant1/region1/site1", PROPERTY_CONFIG_REF, "/conf/tenant1/region1/site1") + .resource("/content/tenant1/region1/site2", PROPERTY_CONFIG_REF, "/conf/tenant1/region1/site2"); + site1Page1 = context.create().resource("/content/tenant1/region1/site1/page1"); + site2Page1 = context.create().resource("/content/tenant1/region1/site2/page1"); + } + + @Test + public void testWithNoStrategies() { + assertFalse(underTest.findContextResources(site1Page1).hasNext()); + } + + @Test + public void testWithDefaultStrategy() { + context.registerInjectActivateService(new DefaultContextPathStrategy()); + + assertThat(toResourceIterator(underTest.findContextResources(site1Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1/site1", + "/content/tenant1/region1", + "/content/tenant1")); + + assertThat(toResourceIterator(underTest.findContextResources(site2Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1/site2", + "/content/tenant1/region1", + "/content/tenant1")); + } + + @Test + public void testWithNonoverlappingStrategies() { + registerContextPathStrategy("/content/tenant1"); + registerContextPathStrategy("/content/tenant1/region1/site1", "/content/tenant1/region1"); + + assertThat(toResourceIterator(underTest.findContextResources(site1Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1/site1", + "/content/tenant1/region1", + "/content/tenant1")); + } + + @Test + public void testWithOverlappingStrategies() { + registerContextPathStrategy("/content/tenant1", "/content/tenant1/region1"); + registerContextPathStrategy("/content/tenant1/region1/site1", "/content/tenant1/region1"); + + assertThat(toResourceIterator(underTest.findContextResources(site1Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1/site1", + "/content/tenant1/region1", + "/content/tenant1")); + } + + private void registerContextPathStrategy(String... paths) { + final List resources = new ArrayList<>(); + for (String path : paths) { + Resource resource = context.resourceResolver().getResource(path); + if (resource != null) { + resources.add(resource); + } + } + context.registerService(ContextPathStrategy.class, new ContextPathStrategy() { + @Override + public @NotNull Iterator findContextResources(@NotNull Resource resource) { + return toContextResourceIterator(resources.iterator()); + } + }); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/CustomConfigurationPersistenceStrategy.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/CustomConfigurationPersistenceStrategy.java new file mode 100644 index 000000000..d21d742d2 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/CustomConfigurationPersistenceStrategy.java @@ -0,0 +1,193 @@ +/* + * 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.caconfig.management.impl; + +import static org.junit.Assert.assertNotNull; + +import java.util.HashSet; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.JcrConstants; +import org.apache.sling.api.resource.ModifiableValueMap; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceException; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This is a variant of {@link org.apache.sling.caconfig.impl.def.DefaultConfigurationPersistenceStrategy} + * which reads and stores data from a sub-resources named "jcr:content". + */ +public class CustomConfigurationPersistenceStrategy implements ConfigurationPersistenceStrategy2 { + + private static final String DEFAULT_RESOURCE_TYPE = JcrConstants.NT_UNSTRUCTURED; + private static final String CHILD_NODE_NAME = JcrConstants.JCR_CONTENT; + + @Override + public Resource getResource(@NotNull Resource resource) { + assertNotNull(resource); + return resource.getChild(CHILD_NODE_NAME); + } + + @Override + public Resource getCollectionParentResource(@NotNull Resource resource) { + assertNotNull(resource); + return resource; + } + + @Override + public Resource getCollectionItemResource(@NotNull Resource resource) { + assertNotNull(resource); + return resource.getChild(CHILD_NODE_NAME); + } + + @Override + public String getResourcePath(@NotNull String resourcePath) { + assertNotNull(resourcePath); + return resourcePath + "/" + CHILD_NODE_NAME; + } + + @Override + public String getCollectionParentResourcePath(@NotNull String resourcePath) { + assertNotNull(resourcePath); + return resourcePath; + } + + @Override + public String getCollectionItemResourcePath(@NotNull String resourcePath) { + assertNotNull(resourcePath); + return resourcePath + "/" + CHILD_NODE_NAME; + } + + @Override + public String getConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + assertNotNull(configName); + return configName + "/" + CHILD_NODE_NAME; + } + + @Override + public String getCollectionParentConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + assertNotNull(configName); + return configName; + } + + @Override + public String getCollectionItemConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + assertNotNull(configName); + return configName + "/" + CHILD_NODE_NAME; + } + + @Override + public boolean persistConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath, + @NotNull ConfigurationPersistData data) { + getOrCreateResource(resourceResolver, configResourcePath + "/" + CHILD_NODE_NAME, data.getProperties()); + commit(resourceResolver); + return true; + } + + @Override + public boolean persistConfigurationCollection(@NotNull ResourceResolver resourceResolver, @NotNull String configResourceCollectionParentPath, + @NotNull ConfigurationCollectionPersistData data) { + Resource configResourceParent = getOrCreateResource(resourceResolver, configResourceCollectionParentPath, ValueMap.EMPTY); + + // delete existing children and create new ones + deleteChildren(configResourceParent); + for (ConfigurationPersistData item : data.getItems()) { + String path = configResourceParent.getPath() + "/" + item.getCollectionItemName() + "/" + CHILD_NODE_NAME; + getOrCreateResource(resourceResolver, path, item.getProperties()); + } + + // if resource collection parent properties are given replace them as well + if (data.getProperties() != null) { + replaceProperties(configResourceParent, data.getProperties()); + } + + commit(resourceResolver); + return true; + } + + @Override + public boolean deleteConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath) { + Resource resource = resourceResolver.getResource(configResourcePath); + if (resource != null) { + try { + resourceResolver.delete(resource); + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to delete configuration at " + configResourcePath, ex); + } + } + commit(resourceResolver); + return true; + } + + private Resource getOrCreateResource(ResourceResolver resourceResolver, String path, Map properties) { + try { + Resource resource = ResourceUtil.getOrCreateResource(resourceResolver, path, DEFAULT_RESOURCE_TYPE, DEFAULT_RESOURCE_TYPE, false); + replaceProperties(resource, properties); + return resource; + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to persist configuration to " + path, ex); + } + } + + private void deleteChildren(Resource resource) { + ResourceResolver resourceResolver = resource.getResourceResolver(); + try { + for (Resource child : resource.getChildren()) { + resourceResolver.delete(child); + } + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to remove children from " + resource.getPath(), ex); + } + } + + @SuppressWarnings("null") + private void replaceProperties(Resource resource, Map properties) { + ModifiableValueMap modValueMap = resource.adaptTo(ModifiableValueMap.class); + // remove all existing properties that do not have jcr: namespace + for (String propertyName : new HashSet<>(modValueMap.keySet())) { + if (StringUtils.startsWith(propertyName, "jcr:")) { + continue; + } + modValueMap.remove(propertyName); + } + modValueMap.putAll(properties); + } + + private void commit(ResourceResolver resourceResolver) { + try { + resourceResolver.commit(); + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to save configuration: " + ex.getMessage(), ex); + } + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/CustomConfigurationPersistenceStrategy2.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/CustomConfigurationPersistenceStrategy2.java new file mode 100644 index 000000000..06af08c7f --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/CustomConfigurationPersistenceStrategy2.java @@ -0,0 +1,235 @@ +/* + * 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.caconfig.management.impl; + +import static org.junit.Assert.assertNotNull; + +import java.util.HashSet; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.jackrabbit.JcrConstants; +import org.apache.sling.api.resource.ModifiableValueMap; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceException; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This is a variant of {@link org.apache.sling.caconfig.impl.def.DefaultConfigurationPersistenceStrategy} + * which reads and stores data from a sub-resources named "jcr:content". + * + * Difference to {@link CustomConfigurationPersistenceStrategy}: + * - For configuration collections jcr:content is added only for the parent, not for each item + * - For nested configuration jcr:content is not duplicated in the path + */ +public class CustomConfigurationPersistenceStrategy2 implements ConfigurationPersistenceStrategy2 { + + private static final String DEFAULT_RESOURCE_TYPE = JcrConstants.NT_UNSTRUCTURED; + private static final String CHILD_NODE_NAME = JcrConstants.JCR_CONTENT; + private static final Pattern JCR_CONTENT_PATTERN = Pattern.compile("(.*/)?" + Pattern.quote(CHILD_NODE_NAME) + "(/.*)?"); + + @Override + public Resource getResource(@NotNull Resource resource) { + assertNotNull(resource); + if (containsJcrContent(resource.getPath())) { + return resource; + } + else { + return resource.getChild(CHILD_NODE_NAME); + } + } + + @Override + public Resource getCollectionParentResource(@NotNull Resource resource) { + assertNotNull(resource); + if (containsJcrContent(resource.getPath())) { + return resource; + } + else { + return resource.getChild(CHILD_NODE_NAME); + } + } + + @Override + public Resource getCollectionItemResource(@NotNull Resource resource) { + assertNotNull(resource); + return resource; + } + + @Override + public String getResourcePath(@NotNull String resourcePath) { + assertNotNull(resourcePath); + if (containsJcrContent(resourcePath)) { + return resourcePath; + } + else { + return resourcePath + "/" + CHILD_NODE_NAME; + } + } + + @Override + public String getCollectionParentResourcePath(@NotNull String resourcePath) { + assertNotNull(resourcePath); + if (containsJcrContent(resourcePath)) { + return resourcePath; + } + else { + return resourcePath + "/" + CHILD_NODE_NAME; + } + } + + @Override + public String getCollectionItemResourcePath(@NotNull String resourcePath) { + assertNotNull(resourcePath); + return resourcePath; + } + + @Override + public String getConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + assertNotNull(configName); + if (containsJcrContent(configName)) { + return configName; + } + else { + return configName + "/" + CHILD_NODE_NAME; + } + } + + @Override + public String getCollectionParentConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + assertNotNull(configName); + if (containsJcrContent(configName)) { + return configName; + } + else { + return configName + "/" + CHILD_NODE_NAME; + } + } + + @Override + public String getCollectionItemConfigName(@NotNull String configName, @Nullable String relatedConfigPath) { + assertNotNull(configName); + return configName; + } + + @Override + public boolean persistConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath, + @NotNull ConfigurationPersistData data) { + getOrCreateResource(resourceResolver, getResourcePath(configResourcePath), data.getProperties()); + commit(resourceResolver); + return true; + } + + @Override + public boolean persistConfigurationCollection(@NotNull ResourceResolver resourceResolver, @NotNull String configResourceCollectionParentPath, + @NotNull ConfigurationCollectionPersistData data) { + String parentPath = getCollectionParentResourcePath(configResourceCollectionParentPath); + Resource configResourceParent = getOrCreateResource(resourceResolver, parentPath, ValueMap.EMPTY); + + // delete existing children and create new ones + deleteChildren(configResourceParent); + for (ConfigurationPersistData item : data.getItems()) { + String path = configResourceParent.getPath() + "/" + item.getCollectionItemName(); + getOrCreateResource(resourceResolver, path, item.getProperties()); + } + + // if resource collection parent properties are given replace them as well + if (data.getProperties() != null) { + Resource propsResource = getOrCreateResource(resourceResolver, parentPath + "/colPropsResource", ValueMap.EMPTY); + replaceProperties(propsResource, data.getProperties()); + } + + commit(resourceResolver); + return true; + } + + @Override + public boolean deleteConfiguration(@NotNull ResourceResolver resourceResolver, @NotNull String configResourcePath) { + Resource resource = resourceResolver.getResource(configResourcePath); + if (resource != null) { + try { + resourceResolver.delete(resource); + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to delete configuration at " + configResourcePath, ex); + } + } + commit(resourceResolver); + return true; + } + + private Resource getOrCreateResource(ResourceResolver resourceResolver, String path, Map properties) { + try { + Resource resource = ResourceUtil.getOrCreateResource(resourceResolver, path, DEFAULT_RESOURCE_TYPE, DEFAULT_RESOURCE_TYPE, false); + replaceProperties(resource, properties); + return resource; + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to persist configuration to " + path, ex); + } + } + + private void deleteChildren(Resource resource) { + ResourceResolver resourceResolver = resource.getResourceResolver(); + try { + for (Resource child : resource.getChildren()) { + resourceResolver.delete(child); + } + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to remove children from " + resource.getPath(), ex); + } + } + + @SuppressWarnings("null") + private void replaceProperties(Resource resource, Map properties) { + ModifiableValueMap modValueMap = resource.adaptTo(ModifiableValueMap.class); + // remove all existing properties that do not have jcr: namespace + for (String propertyName : new HashSet<>(modValueMap.keySet())) { + if (StringUtils.startsWith(propertyName, "jcr:")) { + continue; + } + modValueMap.remove(propertyName); + } + modValueMap.putAll(properties); + } + + private void commit(ResourceResolver resourceResolver) { + try { + resourceResolver.commit(); + } + catch (PersistenceException ex) { + throw new ConfigurationPersistenceException("Unable to save configuration: " + ex.getMessage(), ex); + } + } + + static boolean containsJcrContent(String path) { + return JCR_CONTENT_PATTERN.matcher(path).matches(); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/console/CAConfigInventoryPrinterTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/console/CAConfigInventoryPrinterTest.java new file mode 100644 index 000000000..c3ee8283e --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/management/impl/console/CAConfigInventoryPrinterTest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl.console; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.apache.commons.lang3.StringUtils; +import org.apache.felix.inventory.Format; +import org.apache.sling.caconfig.impl.ConfigurationTestUtils; +import org.apache.sling.caconfig.impl.def.DefaultConfigurationInheritanceStrategy; +import org.apache.sling.caconfig.impl.def.DefaultConfigurationPersistenceStrategy; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedSet; + +@RunWith(MockitoJUnitRunner.class) +public class CAConfigInventoryPrinterTest { + + private static final String SAMPLE_CONFIG_NAME = "sample.config.Name"; + private static final String SAMPLE_OVERRIDE_STRING = "[/sample]override/string='abc'"; + + @Rule + public SlingContext context = new SlingContext(); + + @Mock + private ConfigurationMetadataProvider configurationMetadataProvider; + @Mock + private ConfigurationOverrideProvider configurationOverrideProvider; + + private CAConfigInventoryPrinter underTest; + + @Before + public void setUp() { + context.registerService(ConfigurationMetadataProvider.class, configurationMetadataProvider); + context.registerService(ConfigurationOverrideProvider.class, configurationOverrideProvider); + ConfigurationTestUtils.registerConfigurationResolver(context); + underTest = context.registerInjectActivateService(new CAConfigInventoryPrinter()); + + ConfigurationMetadata configMetadata = new ConfigurationMetadata(SAMPLE_CONFIG_NAME, ImmutableList.>of( + new PropertyMetadata<>("prop1", "defValue"), + new PropertyMetadata<>("prop2", String.class), + new PropertyMetadata<>("prop3", 5)), + false); + when(configurationMetadataProvider.getConfigurationMetadata(SAMPLE_CONFIG_NAME)).thenReturn(configMetadata); + when(configurationMetadataProvider.getConfigurationNames()).thenReturn(ImmutableSortedSet.of(SAMPLE_CONFIG_NAME)); + + when(configurationOverrideProvider.getOverrideStrings()).thenReturn(ImmutableList.of(SAMPLE_OVERRIDE_STRING)); + } + + @Test + public void testPrintConfiguration() { + StringWriter sw = new StringWriter(); + underTest.print(new PrintWriter(sw), Format.TEXT, false); + String result = sw.toString(); + + // test existance of some strategy names + assertTrue(StringUtils.contains(result, DefaultConfigurationInheritanceStrategy.class.getName())); + assertTrue(StringUtils.contains(result, DefaultConfigurationPersistenceStrategy.class.getName())); + + // ensure config metadata + assertTrue(StringUtils.contains(result, SAMPLE_CONFIG_NAME)); + + // ensure overrides strings + assertTrue(StringUtils.contains(result, SAMPLE_OVERRIDE_STRING)); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImplTest.java new file mode 100644 index 000000000..c723cc0db --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImplTest.java @@ -0,0 +1,108 @@ +/* + * 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.caconfig.resource.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.resource.ConfigurationResourceResolver; +import org.apache.sling.hamcrest.ResourceCollectionMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +@SuppressWarnings("null") +public class ConfigurationResourceResolverImplTest { + + private static final String BUCKET = "sling:test"; + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResourceResolver underTest; + + private Resource site1Page1; + private Resource site2Page1; + + @Before + public void setUp() { + underTest = ConfigurationResourceTestUtils.registerConfigurationResourceResolver(context); + + // content resources + context.build() + .resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/site1") + .resource("/content/site2", PROPERTY_CONFIG_REF, "/conf/site2"); + site1Page1 = context.create().resource("/content/site1/page1"); + site2Page1 = context.create().resource("/content/site2/page1"); + + // configuration + context.build() + .resource("/conf/site1/sling:test/test") + .resource("/conf/site1/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("c") + .resource("/conf/site2/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .siblingsMode() + .resource("c") + .resource("d") + .resource("/apps/conf/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("a") + .resource("/libs/conf/sling:test/test") + .resource("/libs/conf/sling:test/feature") + .resource("b"); + } + + @Test + public void testGetResource() { + assertEquals("/conf/site1/sling:test/test", underTest.getResource(site1Page1, BUCKET, "test").getPath()); + assertEquals("/libs/conf/sling:test/test", underTest.getResource(site2Page1, BUCKET, "test").getPath()); + } + + @Test + public void testGetResourceCollection() { + assertThat(underTest.getResourceCollection(site1Page1, BUCKET, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c", + "/apps/conf/sling:test/feature/a", + "/libs/conf/sling:test/feature/b")); + + assertThat(underTest.getResourceCollection(site2Page1, BUCKET, "feature"), ResourceCollectionMatchers.paths( + "/conf/site2/sling:test/feature/c", + "/conf/site2/sling:test/feature/d", + "/apps/conf/sling:test/feature/a", + "/libs/conf/sling:test/feature/b" )); + } + + @Test + public void testGetContextPath() { + assertEquals("/content/site1", underTest.getContextPath(site1Page1)); + assertEquals("/content/site2", underTest.getContextPath(site2Page1)); + } + + @Test + public void testGetAllContextPaths() { + assertEquals(ImmutableList.of("/content/site1"), underTest.getAllContextPaths(site1Page1)); + assertEquals(ImmutableList.of("/content/site2"), underTest.getAllContextPaths(site2Page1)); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolvingStrategyMultiplexerImplTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolvingStrategyMultiplexerImplTest.java new file mode 100644 index 000000000..c3e4998d8 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolvingStrategyMultiplexerImplTest.java @@ -0,0 +1,226 @@ +/* + * 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.caconfig.resource.impl; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.impl.ContextPathStrategyMultiplexerImpl; +import org.apache.sling.caconfig.resource.impl.def.DefaultConfigurationResourceResolvingStrategy; +import org.apache.sling.caconfig.resource.impl.def.DefaultContextPathStrategy; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.hamcrest.ResourceCollectionMatchers; +import org.apache.sling.hamcrest.ResourceMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.osgi.framework.Constants; + +import com.google.common.base.Function; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; + +@SuppressWarnings("null") +public class ConfigurationResourceResolvingStrategyMultiplexerImplTest { + + private static final String BUCKET = "sling:test"; + private static final Collection BUCKETS = Collections.singleton(BUCKET); + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResourceResolvingStrategyMultiplexerImpl underTest; + + private Resource site1Page1; + + @Before + public void setUp() { + context.registerInjectActivateService(new DefaultContextPathStrategy()); + context.registerInjectActivateService(new ContextPathStrategyMultiplexerImpl()); + underTest = context.registerInjectActivateService(new ConfigurationResourceResolvingStrategyMultiplexerImpl()); + + // content resources + context.build() + .resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/site1") + .resource("/content/site2", PROPERTY_CONFIG_REF, "/conf/site2"); + site1Page1 = context.create().resource("/content/site1/page1"); + + // configuration + context.build() + .resource("/conf/site1/sling:test/test") + .resource("/conf/site1/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("c") + .resource("/conf/site2/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .siblingsMode() + .resource("c") + .resource("d") + .resource("/apps/conf/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("a") + .resource("/libs/conf/sling:test/test") + .resource("/libs/conf/sling:test/feature") + .resource("b"); + } + + @Test + public void testWithNoStrategies() { + assertNull(underTest.getResource(site1Page1, BUCKETS, "test")); + assertNull(underTest.getResourceCollection(site1Page1, BUCKETS, "feature")); + + assertNull(underTest.getResourceInheritanceChain(site1Page1, BUCKETS, "test")); + assertNull(underTest.getResourceCollectionInheritanceChain(site1Page1, BUCKETS, "feature")); + + assertNull(underTest.getResourcePath(site1Page1, BUCKET, "test")); + assertNull(underTest.getResourceCollectionParentPath(site1Page1, BUCKET, "feature")); + } + + @Test + public void testWithDefaultStrategy() { + context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + assertThat(underTest.getResource(site1Page1, BUCKETS, "test"), ResourceMatchers.path("/conf/site1/sling:test/test")); + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c", + "/apps/conf/sling:test/feature/a", + "/libs/conf/sling:test/feature/b")); + + assertThat(first(underTest.getResourceInheritanceChain(site1Page1, BUCKETS, "test")), ResourceMatchers.path("/conf/site1/sling:test/test")); + assertThat(first(underTest.getResourceCollectionInheritanceChain(site1Page1, BUCKETS, "feature")), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c", + "/apps/conf/sling:test/feature/a", + "/libs/conf/sling:test/feature/b")); + + assertEquals("/conf/site1/sling:test/test", underTest.getResourcePath(site1Page1, BUCKET, "test")); + assertEquals("/conf/site1/sling:test/feature", underTest.getResourceCollectionParentPath(site1Page1, BUCKET, "feature")); + } + + @Test + public void testMultipleStrategies() { + + // strategy 1 + context.registerService(ConfigurationResourceResolvingStrategy.class, new ConfigurationResourceResolvingStrategy() { + @Override + public Resource getResource(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + return context.resourceResolver().getResource("/conf/site1/sling:test/test"); + } + @Override + public Collection getResourceCollection(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + return ImmutableList.copyOf(context.resourceResolver().getResource("/conf/site1/sling:test/feature").listChildren()); + } + @Override + public Iterator getResourceInheritanceChain(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + return Iterators.singletonIterator(getResource(resource, bucketNames, configName)); + } + @Override + public Collection> getResourceCollectionInheritanceChain(@NotNull Resource resource, + @NotNull Collection bucketNames, @NotNull String configName) { + return Collections2.transform(getResourceCollection(resource, bucketNames, configName), new Function>() { + @Override + public Iterator apply(@Nullable Resource input) { + return Iterators.singletonIterator(input); + } + }); + } + @Override + public String getResourcePath(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + return "/conf/site1/sling:test/test"; + } + @Override + public String getResourceCollectionParentPath(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + return "/conf/site1/sling:test/feature"; + } + }, Constants.SERVICE_RANKING, 2000); + + // strategy 2 + context.registerService(ConfigurationResourceResolvingStrategy.class, new ConfigurationResourceResolvingStrategy() { + @Override + public Resource getResource(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + return context.resourceResolver().getResource("/libs/conf/sling:test/test"); + } + @Override + public Collection getResourceCollection(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + return ImmutableList.copyOf(context.resourceResolver().getResource("/libs/conf/sling:test/feature").listChildren()); + } + @Override + public Iterator getResourceInheritanceChain(@NotNull Resource resource, @NotNull Collection bucketNames, @NotNull String configName) { + return Iterators.singletonIterator(getResource(resource, bucketNames, configName)); + } + @Override + public Collection> getResourceCollectionInheritanceChain(@NotNull Resource resource, + @NotNull Collection bucketNames, @NotNull String configName) { + return Collections2.transform(getResourceCollection(resource, bucketNames, configName), new Function>() { + @Override + public Iterator apply(@Nullable Resource input) { + return Iterators.singletonIterator(input); + } + }); + } + @Override + public String getResourcePath(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + return null; + } + @Override + public String getResourceCollectionParentPath(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) { + return null; + } + }, Constants.SERVICE_RANKING, 1000); + + assertThat(underTest.getResource(site1Page1, BUCKETS, "test"), ResourceMatchers.path("/conf/site1/sling:test/test")); + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c")); + + assertThat(first(underTest.getResourceInheritanceChain(site1Page1, BUCKETS, "test")), ResourceMatchers.path("/conf/site1/sling:test/test")); + assertThat(first(underTest.getResourceCollectionInheritanceChain(site1Page1, BUCKETS, "feature")), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c")); + + assertEquals("/conf/site1/sling:test/test", underTest.getResourcePath(site1Page1, BUCKET, "test")); + assertEquals("/conf/site1/sling:test/feature", underTest.getResourceCollectionParentPath(site1Page1, BUCKET, "feature")); + } + + private Resource first(Iterator resources) { + if (resources.hasNext()) { + return resources.next(); + } + else { + return null; + } + } + + private Collection first(Collection> resources) { + return Collections2.transform(underTest.getResourceCollectionInheritanceChain(site1Page1, BUCKETS, "feature"), + new Function, Resource>() { + @Override + public Resource apply(@Nullable Iterator input) { + return input.next(); + } + }); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceTestUtils.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceTestUtils.java new file mode 100644 index 000000000..2874214df --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceTestUtils.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.caconfig.resource.impl; + +import org.apache.sling.caconfig.management.impl.ContextPathStrategyMultiplexerImpl; +import org.apache.sling.caconfig.resource.ConfigurationResourceResolver; +import org.apache.sling.caconfig.resource.impl.def.DefaultConfigurationResourceResolvingStrategy; +import org.apache.sling.caconfig.resource.impl.def.DefaultContextPathStrategy; +import org.apache.sling.testing.mock.sling.junit.SlingContext; + +public final class ConfigurationResourceTestUtils { + + private ConfigurationResourceTestUtils() { + // static methods only + } + + /** + * Register all services for {@link ConfigurationResourceResolver}. + * @param context Sling context + */ + public static ConfigurationResourceResolver registerConfigurationResourceResolver(SlingContext context) { + context.registerInjectActivateService(new DefaultContextPathStrategy()); + context.registerInjectActivateService(new ContextPathStrategyMultiplexerImpl()); + context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + context.registerInjectActivateService(new ConfigurationResourceResolvingStrategyMultiplexerImpl()); + return context.registerInjectActivateService(new ConfigurationResourceResolverImpl()); + } + + /** + * Register all services for {@link ConfigurationResourceResolver} + * without the default implementations of the multiplexed services. + * @param context Sling context + */ + public static ConfigurationResourceResolver registerConfigurationResourceResolverWithoutDefaultImpl(SlingContext context) { + context.registerInjectActivateService(new ContextPathStrategyMultiplexerImpl()); + context.registerInjectActivateService(new ConfigurationResourceResolvingStrategyMultiplexerImpl()); + return context.registerInjectActivateService(new ConfigurationResourceResolverImpl()); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategyHierarchyTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategyHierarchyTest.java new file mode 100644 index 000000000..5fb28b4a4 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategyHierarchyTest.java @@ -0,0 +1,232 @@ +/* + * 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.caconfig.resource.impl.def; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.impl.ContextPathStrategyMultiplexerImpl; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.hamcrest.ResourceCollectionMatchers; +import org.apache.sling.hamcrest.ResourceIteratorMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +/** + * Tests with content and configurations that form a deeper nested hierarchy. + */ +@SuppressWarnings("null") +public class DefaultConfigurationResourceResolvingStrategyHierarchyTest { + + private static final String BUCKET = "sling:test"; + private static final Collection BUCKETS = Collections.singleton(BUCKET); + private static final String PROPERTY_CONFIG_COLLECTION_INHERIT_CUSTOM = "custom:configCollectionInherit"; + + @Rule + public SlingContext context = new SlingContext(); + + private ConfigurationResourceResolvingStrategy underTest; + + private Resource site1Page1; + private Resource site2Page1; + + @Before + public void setUp() { + context.registerInjectActivateService(new DefaultContextPathStrategy()); + context.registerInjectActivateService(new ContextPathStrategyMultiplexerImpl()); + underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy(), + "configCollectionInheritancePropertyNames", PROPERTY_CONFIG_COLLECTION_INHERIT_CUSTOM); + + // content resources that form a deeper hierarchy + context.build() + .resource("/content/tenant1", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1") + .resource("/content/tenant1/region1", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1/region1") + .resource("/content/tenant1/region1/site1", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1/region1/site1") + .resource("/content/tenant1/region1/site2", PROPERTY_CONFIG_REF, "/conf/brand1/tenant1/region1/site2"); + site1Page1 = context.create().resource("/content/tenant1/region1/site1/page1"); + site2Page1 = context.create().resource("/content/tenant1/region1/site2/page1"); + } + + @Test + public void testGetResource() { + context.build() + .resource("/conf/brand1/tenant1/region1/site1/sling:test/cfgSite1") + .resource("/conf/brand1/tenant1/region1/sling:test/cfgRegion1") + .resource("/conf/brand1/tenant1/sling:test/cfgTenant1") + .resource("/conf/brand1/tenant1/sling:test/test") + .resource("/conf/brand1/sling:test/cfgBrand1") + .resource("/conf/global/sling:test/cfgGlobal") + .resource("/conf/global/sling:test/test") + .resource("/apps/conf/sling:test/cfgAppsGlobal") + .resource("/apps/conf/sling:test/test") + .resource("/libs/conf/sling:test/cfgLibsGlobal") + .resource("/libs/conf/sling:test/test"); + + assertEquals("/conf/brand1/tenant1/region1/site1/sling:test/cfgSite1", underTest.getResource(site1Page1, BUCKETS, "cfgSite1").getPath()); + assertEquals("/conf/brand1/tenant1/region1/sling:test/cfgRegion1", underTest.getResource(site1Page1, BUCKETS, "cfgRegion1").getPath()); + assertEquals("/conf/brand1/tenant1/sling:test/cfgTenant1", underTest.getResource(site1Page1, BUCKETS, "cfgTenant1").getPath()); + assertEquals("/conf/brand1/sling:test/cfgBrand1", underTest.getResource(site1Page1, BUCKETS, "cfgBrand1").getPath()); + assertEquals("/conf/global/sling:test/cfgGlobal", underTest.getResource(site1Page1, BUCKETS, "cfgGlobal").getPath()); + assertEquals("/apps/conf/sling:test/cfgAppsGlobal", underTest.getResource(site1Page1, BUCKETS, "cfgAppsGlobal").getPath()); + assertEquals("/libs/conf/sling:test/cfgLibsGlobal", underTest.getResource(site1Page1, BUCKETS, "cfgLibsGlobal").getPath()); + assertEquals("/conf/brand1/tenant1/sling:test/test", underTest.getResource(site1Page1, BUCKETS, "test").getPath()); + + assertNull(underTest.getResource(site2Page1, BUCKETS, "cfgSite1")); + assertEquals("/conf/brand1/tenant1/region1/sling:test/cfgRegion1", underTest.getResource(site2Page1, BUCKETS, "cfgRegion1").getPath()); + assertEquals("/conf/brand1/tenant1/sling:test/cfgTenant1", underTest.getResource(site2Page1, BUCKETS, "cfgTenant1").getPath()); + assertEquals("/conf/brand1/sling:test/cfgBrand1", underTest.getResource(site2Page1, BUCKETS, "cfgBrand1").getPath()); + assertEquals("/conf/global/sling:test/cfgGlobal", underTest.getResource(site2Page1, BUCKETS, "cfgGlobal").getPath()); + assertEquals("/apps/conf/sling:test/cfgAppsGlobal", underTest.getResource(site2Page1, BUCKETS, "cfgAppsGlobal").getPath()); + assertEquals("/libs/conf/sling:test/cfgLibsGlobal", underTest.getResource(site2Page1, BUCKETS, "cfgLibsGlobal").getPath()); + assertEquals("/conf/brand1/tenant1/sling:test/test", underTest.getResource(site2Page1, BUCKETS, "test").getPath()); + } + + @Test + public void testGetResourceInheritanceChain() { + context.build() + .resource("/conf/brand1/tenant1/region1/site1/sling:test/test") + .resource("/conf/brand1/tenant1/sling:test/test") + .resource("/conf/global/sling:test/test") + .resource("/apps/conf/sling:test/test") + .resource("/libs/conf/sling:test/test"); + + assertThat(underTest.getResourceInheritanceChain(site1Page1, BUCKETS, "test"), ResourceIteratorMatchers.paths( + "/conf/brand1/tenant1/region1/site1/sling:test/test", + "/conf/brand1/tenant1/sling:test/test", + "/conf/global/sling:test/test", + "/apps/conf/sling:test/test", + "/libs/conf/sling:test/test")); + + assertThat(underTest.getResourceInheritanceChain(site2Page1, BUCKETS, "test"), ResourceIteratorMatchers.paths( + "/conf/brand1/tenant1/sling:test/test", + "/conf/global/sling:test/test", + "/apps/conf/sling:test/test", + "/libs/conf/sling:test/test")); + } + + @Test + public void testGetResourceCollectionWithInheritance() { + context.build() + .resource("/conf/brand1/tenant1/region1/site1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("site1") + .resource("/conf/brand1/tenant1/region1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("region1") + .resource("/conf/brand1/tenant1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("tenant1") + .resource("/conf/brand1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT_CUSTOM, true).resource("brand1") + .resource("/conf/global/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT_CUSTOM, true).resource("confGlobal") + .resource("/apps/conf/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("appsGlobal") + .resource("/libs/conf/sling:test/cfgCol/libsGlobal1") + .resource("/libs/conf/sling:test/cfgCol/libsGlobal2"); + + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "cfgCol"), ResourceCollectionMatchers.paths( + "/conf/brand1/tenant1/region1/site1/sling:test/cfgCol/site1", + "/conf/brand1/tenant1/region1/sling:test/cfgCol/region1", + "/conf/brand1/tenant1/sling:test/cfgCol/tenant1", + "/conf/brand1/sling:test/cfgCol/brand1", + "/conf/global/sling:test/cfgCol/confGlobal", + "/apps/conf/sling:test/cfgCol/appsGlobal", + "/libs/conf/sling:test/cfgCol/libsGlobal1", + "/libs/conf/sling:test/cfgCol/libsGlobal2")); + + assertThat(underTest.getResourceCollection(site2Page1, BUCKETS, "cfgCol"), ResourceCollectionMatchers.paths( + "/conf/brand1/tenant1/region1/sling:test/cfgCol/region1", + "/conf/brand1/tenant1/sling:test/cfgCol/tenant1", + "/conf/brand1/sling:test/cfgCol/brand1", + "/conf/global/sling:test/cfgCol/confGlobal", + "/apps/conf/sling:test/cfgCol/appsGlobal", + "/libs/conf/sling:test/cfgCol/libsGlobal1", + "/libs/conf/sling:test/cfgCol/libsGlobal2")); + } + + @Test + public void testGetResourceCollectionInheritanceChain() { + context.build() + .resource("/conf/brand1/tenant1/region1/site1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .siblingsMode() + .resource("item1") + .resource("item2") + .resource("/conf/brand1/tenant1/region1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT_CUSTOM, true) + .siblingsMode() + .resource("item1") + .resource("item3") + .resource("/conf/brand1/tenant1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .siblingsMode() + .resource("item4") + .resource("/conf/global/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .siblingsMode() + .resource("item1") + .resource("/libs/conf/sling:test/cfgCol") + .siblingsMode() + .resource("item2") + .resource("item3"); + + List> resources = ImmutableList.copyOf(underTest.getResourceCollectionInheritanceChain(site1Page1, BUCKETS, "cfgCol")); + assertEquals(4, resources.size()); + + assertThat(resources.get(0), ResourceIteratorMatchers.paths( + "/conf/brand1/tenant1/region1/site1/sling:test/cfgCol/item1", + "/conf/brand1/tenant1/region1/sling:test/cfgCol/item1", + "/conf/global/sling:test/cfgCol/item1")); + assertThat(resources.get(1), ResourceIteratorMatchers.paths( + "/conf/brand1/tenant1/region1/site1/sling:test/cfgCol/item2", + "/libs/conf/sling:test/cfgCol/item2")); + assertThat(resources.get(2), ResourceIteratorMatchers.paths( + "/conf/brand1/tenant1/region1/sling:test/cfgCol/item3", + "/libs/conf/sling:test/cfgCol/item3")); + assertThat(resources.get(3), ResourceIteratorMatchers.paths( + "/conf/brand1/tenant1/sling:test/cfgCol/item4")); + } + + @Test + public void testGetResourceCollectionContentConfigRefInheritanceAndConfigResourceInheritance() { + context.build() + .resource("/content/level1", PROPERTY_CONFIG_REF, "/conf/a1/a2") + .resource("/content/level1/level2", PROPERTY_CONFIG_REF, "/conf/b1/b2") + .resource("/conf/a1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("a1") + .resource("/conf/a1/a2/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT_CUSTOM, true).resource("a1_a2") + .resource("/conf/b1/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("b1") + .resource("/conf/b1/b2/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT_CUSTOM, true).resource("b1_b2") + .resource("/conf/global/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("confGlobal") + .resource("/apps/conf/sling:test/cfgCol", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("appsGlobal") + .resource("/libs/conf/sling:test/cfgCol/libsGlobal"); + + Resource level1_2 = context.resourceResolver().getResource("/content/level1/level2"); + + assertThat(underTest.getResourceCollection(level1_2, BUCKETS, "cfgCol"), ResourceCollectionMatchers.paths( + "/conf/b1/b2/sling:test/cfgCol/b1_b2", + "/conf/b1/sling:test/cfgCol/b1", + "/conf/a1/a2/sling:test/cfgCol/a1_a2", + "/conf/a1/sling:test/cfgCol/a1", + "/conf/global/sling:test/cfgCol/confGlobal", + "/apps/conf/sling:test/cfgCol/appsGlobal", + "/libs/conf/sling:test/cfgCol/libsGlobal")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategyTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategyTest.java new file mode 100644 index 000000000..e2245416e --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultConfigurationResourceResolvingStrategyTest.java @@ -0,0 +1,269 @@ +/* + * 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.caconfig.resource.impl.def; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_COLLECTION_INHERIT; +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; + +import java.util.Collection; +import java.util.Collections; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.impl.ContextPathStrategyMultiplexerImpl; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.hamcrest.ResourceCollectionMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +@SuppressWarnings("null") +public class DefaultConfigurationResourceResolvingStrategyTest { + + private static final String BUCKET = "sling:test"; + private static final Collection BUCKETS = Collections.singleton(BUCKET); + + @Rule + public SlingContext context = new SlingContext(); + + private Resource site1Page1; + private Resource site2Page1; + + private Resource subPage; + private Resource deepPage; + + @Before + public void setUp() { + context.registerInjectActivateService(new DefaultContextPathStrategy()); + context.registerInjectActivateService(new ContextPathStrategyMultiplexerImpl()); + + // content resources + context.build() + .resource("/content/site1", PROPERTY_CONFIG_REF, "/conf/site1") + .resource("/content/site2", PROPERTY_CONFIG_REF, "/conf/site2") + .resource("/content/mainsite", PROPERTY_CONFIG_REF, "/conf/main") + .resource("/content/mainsite/sub", PROPERTY_CONFIG_REF, "sub") + .resource("/content/mainsite/sub/some/where/deep", PROPERTY_CONFIG_REF, "sub/deep"); + site1Page1 = context.create().resource("/content/site1/page1"); + site2Page1 = context.create().resource("/content/site2/page1"); + subPage = context.create().resource("/content/mainsite/sub/page1"); + deepPage = context.create().resource("/content/mainsite/sub/some/where/deep/page1"); + } + + @Test + public void testGetResource() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/conf/site1/sling:test/test") + .resource("/libs/conf/sling:test/test"); + + assertEquals("/conf/site1/sling:test/test", underTest.getResource(site1Page1, BUCKETS, "test").getPath()); + assertEquals("/libs/conf/sling:test/test", underTest.getResource(site2Page1, BUCKETS, "test").getPath()); + } + + @Test + public void testRelativeConfPropery() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/conf/main/sub/sling:test/test") + .resource("/conf/main/sub/deep/sling:test/test"); + + assertEquals("/conf/main/sub/sling:test/test", underTest.getResource(subPage, BUCKETS, "test").getPath()); + assertEquals("/conf/main/sub/deep/sling:test/test", underTest.getResource(deepPage, BUCKETS, "test").getPath()); + } + + /** + * Default resource inheritance without customizing inheritance. + * => no resource list merging. + */ + @Test + public void testGetResourceCollection_NoInheritProps() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/conf/site1/sling:test/feature/c") + .resource("/conf/site2/sling:test/feature/c") + .resource("/conf/site2/sling:test/feature/d") + .resource("/apps/conf/sling:test/feature/a") + .resource("/libs/conf/sling:test/feature/b"); + + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c")); + + assertThat(underTest.getResourceCollection(site2Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site2/sling:test/feature/c", + "/conf/site2/sling:test/feature/d")); + } + + /** + * Default resource inheritance without customizing inheritance, but with now resources inner-most context. + * => inherit from next context level. + */ + @Test + public void testGetResourceCollection_NoInheritProps_InheritParent() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/apps/conf/sling:test/feature/a") + .resource("/libs/conf/sling:test/feature/a") + .resource("/libs/conf/sling:test/feature/b"); + + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/apps/conf/sling:test/feature/a")); + + assertThat(underTest.getResourceCollection(site2Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/apps/conf/sling:test/feature/a")); + } + + /** + * Resource inheritance with enabling list merging on inner-most context level. + * => merge resource lists from next level + */ + @Test + public void testGetResourceCollection_Inherit1Level() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/conf/site1/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("/conf/site1/sling:test/feature/c") + .resource("/conf/site2/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("/conf/site2/sling:test/feature/c") + .resource("/conf/site2/sling:test/feature/d") + .resource("/apps/conf/sling:test/feature/a") + .resource("/libs/conf/sling:test/feature/b"); + + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c", + "/apps/conf/sling:test/feature/a")); + + assertThat(underTest.getResourceCollection(site2Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site2/sling:test/feature/c", + "/conf/site2/sling:test/feature/d", + "/apps/conf/sling:test/feature/a")); + } + + /** + * Resource inheritance with enabling list merging on all levels. + * => merge resource lists from all levels + */ + @Test + public void testGetResourceCollection_InheritMultipleLevels() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/conf/site1/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("c") + .resource("/conf/site2/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .siblingsMode() + .resource("c") + .resource("d") + .resource("/apps/conf/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true) + .resource("a") + .resource("/libs/conf/sling:test/feature") + .resource("b"); + + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c", + "/apps/conf/sling:test/feature/a", + "/libs/conf/sling:test/feature/b")); + + assertThat(underTest.getResourceCollection(site2Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site2/sling:test/feature/c", + "/conf/site2/sling:test/feature/d", + "/apps/conf/sling:test/feature/a", + "/libs/conf/sling:test/feature/b")); + } + + /** + * Resource inheritance with enabling list merging on a parent context level. + * => no inheritance takes place + */ + @Test + public void testGetResourceCollection_PropsParent() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/conf/site1/sling:test/feature/c") + .resource("/conf/site2/sling:test/feature/c") + .resource("/conf/site2/sling:test/feature/d") + .resource("/apps/conf/sling:test/feature/a") + .resource("/libs/conf/sling:test/feature", PROPERTY_CONFIG_COLLECTION_INHERIT, true).resource("b"); + + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/c")); + + assertThat(underTest.getResourceCollection(site2Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site2/sling:test/feature/c", + "/conf/site2/sling:test/feature/d")); + } + + /** + * Ensure jcr:content nodes are not included in resource collection. + */ + @Test + public void testGetResourceCollection_SkipJcrContent() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + + // build config resources + context.build() + .resource("/conf/site1/sling:test/feature/a") + .resource("/conf/site1/sling:test/feature/b") + .resource("/conf/site2/sling:test/feature/jcr:content"); + + assertThat(underTest.getResourceCollection(site1Page1, BUCKETS, "feature"), ResourceCollectionMatchers.paths( + "/conf/site1/sling:test/feature/a", + "/conf/site1/sling:test/feature/b")); + } + + @Test + public void testGetResourcePath() throws Exception { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + assertEquals("/conf/site1/sling:test/test", underTest.getResourcePath(site1Page1, BUCKET, "test")); + } + + @Test + public void testGetResourceCollectionParentPath() throws Exception { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy()); + assertEquals("/conf/site1/sling:test/feature", underTest.getResourceCollectionParentPath(site1Page1, BUCKET, "feature")); + } + + @Test + public void testDisabled() { + ConfigurationResourceResolvingStrategy underTest = context.registerInjectActivateService(new DefaultConfigurationResourceResolvingStrategy(), + "enabled", false); + + assertNull(underTest.getResource(site1Page1, BUCKETS, "test")); + assertNull(underTest.getResourceCollection(site1Page1, BUCKETS, "feature")); + assertNull(underTest.getResourcePath(site1Page1, BUCKET, "test")); + assertNull(underTest.getResourceCollectionParentPath(site1Page1, BUCKET, "feature")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultContextPathStrategyTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultContextPathStrategyTest.java new file mode 100644 index 000000000..25bd9e695 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/def/DefaultContextPathStrategyTest.java @@ -0,0 +1,109 @@ +/* + * 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.caconfig.resource.impl.def; + +import static org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants.PROPERTY_CONFIG_REF; +import static org.apache.sling.caconfig.resource.impl.util.ContextResourceTestUtil.toConfigRefIterator; +import static org.apache.sling.caconfig.resource.impl.util.ContextResourceTestUtil.toResourceIterator; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; +import org.apache.sling.hamcrest.ResourceIteratorMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class DefaultContextPathStrategyTest { + + @Rule + public SlingContext context = new SlingContext(); + + private Resource site1Page1; + private Resource site2Page1; + + @Before + public void setUp() { + // content resources that form a deeper hierarchy + context.build() + .resource("/content/tenant1", PROPERTY_CONFIG_REF, "/conf/tenant1") + .resource("/content/tenant1/region1", PROPERTY_CONFIG_REF, "/conf/tenant1/region1") + .resource("/content/tenant1/region1/site1", PROPERTY_CONFIG_REF, "/conf/tenant1/region1/site1") + .resource("/content/tenant1/region1/site2", PROPERTY_CONFIG_REF, "/conf/tenant1/region1/site2"); + site1Page1 = context.create().resource("/content/tenant1/region1/site1/page1"); + site2Page1 = context.create().resource("/content/tenant1/region1/site2/page1"); + } + + @Test + public void testFindContextPaths() { + ContextPathStrategy underTest = context.registerInjectActivateService(new DefaultContextPathStrategy()); + + assertThat(toResourceIterator(underTest.findContextResources(site1Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1/site1", + "/content/tenant1/region1", + "/content/tenant1")); + assertThat(ImmutableList.copyOf(toConfigRefIterator(underTest.findContextResources(site1Page1))), Matchers.contains( + "/conf/tenant1/region1/site1", + "/conf/tenant1/region1", + "/conf/tenant1")); + + assertThat(toResourceIterator(underTest.findContextResources(site2Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1/site2", + "/content/tenant1/region1", + "/content/tenant1")); + assertThat(ImmutableList.copyOf(toConfigRefIterator(underTest.findContextResources(site2Page1))), Matchers.contains( + "/conf/tenant1/region1/site2", + "/conf/tenant1/region1", + "/conf/tenant1")); + } + + @Test + public void testDisabled() { + ContextPathStrategy underTest = context.registerInjectActivateService(new DefaultContextPathStrategy(), + "enabled", false); + + assertFalse(underTest.findContextResources(site1Page1).hasNext()); + assertFalse(underTest.findContextResources(site2Page1).hasNext()); + } + + @Test + public void testConfigRefResourceNames() { + ContextPathStrategy underTest = context.registerInjectActivateService(new DefaultContextPathStrategy(), + "configRefResourceNames", new String[] { "jcr:content" }); + + context.build() + .resource("/content/tenant1/region1/jcr:content", PROPERTY_CONFIG_REF, "/conf/tenant1/region1"); + + assertThat(toResourceIterator(underTest.findContextResources(site1Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1")); + assertThat(ImmutableList.copyOf(toConfigRefIterator(underTest.findContextResources(site1Page1))), Matchers.contains( + "/conf/tenant1/region1")); + + assertThat(toResourceIterator(underTest.findContextResources(site2Page1)), ResourceIteratorMatchers.paths( + "/content/tenant1/region1")); + assertThat(ImmutableList.copyOf(toConfigRefIterator(underTest.findContextResources(site2Page1))), Matchers.contains( + "/conf/tenant1/region1")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ConfigNameUtilTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ConfigNameUtilTest.java new file mode 100644 index 000000000..67b2b4725 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ConfigNameUtilTest.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.caconfig.resource.impl.util; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class ConfigNameUtilTest { + + @Test + public void testIsValid() { + assertTrue(ConfigNameUtil.isValid("a")); + assertTrue(ConfigNameUtil.isValid("a/b")); + assertTrue(ConfigNameUtil.isValid("a/b/c")); + assertTrue(ConfigNameUtil.isValid("a/jcr:content/b/c")); + + assertTrue(ConfigNameUtil.isValid(ImmutableList.of())); + assertTrue(ConfigNameUtil.isValid(ImmutableList.of("a"))); + assertTrue(ConfigNameUtil.isValid(ImmutableList.of("a", "a/b", "a/b/c"))); + + assertFalse(ConfigNameUtil.isValid((String)null)); + assertFalse(ConfigNameUtil.isValid("")); + assertFalse(ConfigNameUtil.isValid("/a")); + assertFalse(ConfigNameUtil.isValid("/a/b/c")); + assertFalse(ConfigNameUtil.isValid("a/b/../c")); + + assertFalse(ConfigNameUtil.isValid((Collection)null)); + assertFalse(ConfigNameUtil.isValid(ImmutableList.of("a", "/a"))); + } + + @Test + public void testGetAllPartialConfigNameVariations() { + assertArrayEquals(new String[0], ConfigNameUtil.getAllPartialConfigNameVariations("")); + assertArrayEquals(new String[0], ConfigNameUtil.getAllPartialConfigNameVariations("a")); + assertArrayEquals(new String[] {"a"}, ConfigNameUtil.getAllPartialConfigNameVariations("a/b")); + assertArrayEquals(new String[] {"a","a/b"}, ConfigNameUtil.getAllPartialConfigNameVariations("a/b/c")); + assertArrayEquals(new String[] {"a","a/b","a/b/c"}, ConfigNameUtil.getAllPartialConfigNameVariations("a/b/c/d")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ContextResourceTestUtil.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ContextResourceTestUtil.java new file mode 100644 index 000000000..fac1b5840 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ContextResourceTestUtil.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.resource.impl.util; + +import java.util.Iterator; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.Transformer; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.resource.spi.ContextResource; + +public final class ContextResourceTestUtil { + + private ContextResourceTestUtil() { + // static methods only + } + + @SuppressWarnings("unchecked") + public static Iterator toResourceIterator(Iterator contextResources) { + return IteratorUtils.transformedIterator(contextResources, new Transformer() { + @Override + public Object transform(Object input) { + return ((ContextResource)input).getResource(); + } + }); + } + + @SuppressWarnings("unchecked") + public static Iterator toConfigRefIterator(Iterator contextResources) { + return IteratorUtils.transformedIterator(contextResources, new Transformer() { + @Override + public Object transform(Object input) { + return ((ContextResource)input).getConfigRef(); + } + }); + } + + @SuppressWarnings("unchecked") + public static Iterator toContextResourceIterator(Iterator resources) { + return IteratorUtils.transformedIterator(resources, new Transformer() { + @Override + public Object transform(Object input) { + Resource resource = (Resource)input; + return new ContextResource(resource, "/conf-test" + resource.getPath(), 0); + } + }); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/MapUtilTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/MapUtilTest.java new file mode 100644 index 000000000..b60ca1c16 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/MapUtilTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.resource.impl.util; + +import static org.apache.sling.caconfig.resource.impl.util.MapUtil.traceOutput; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.google.common.collect.ImmutableMap; + +public class MapUtilTest { + + @Test + public void testTraceOutput() { + assertEquals("{}", traceOutput(ImmutableMap.of())); + + assertEquals("{prop1: 'aa', prop2: 5, prop3: true}", traceOutput(ImmutableMap.of( + "prop1", "aa", + "prop2", 5, + "prop3", true + ))); + + assertEquals("{prop1: ['aa','bb'], prop2: [5,10], prop3: true}", traceOutput(ImmutableMap.of( + "prop1", new String[] { "aa", "bb" }, + "prop2", new Integer[] { 5, 10 }, + "prop3", true + ))); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/PathEliminateDuplicatesIteratorTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/PathEliminateDuplicatesIteratorTest.java new file mode 100644 index 000000000..1e696f4a3 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/PathEliminateDuplicatesIteratorTest.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.resource.impl.util; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class PathEliminateDuplicatesIteratorTest { + + @Test + public void testIterator() { + List paths = ImmutableList.of( + "/conf/a", + "/conf/a/b", + "/conf/a", + "/conf/a/b/c"); + + List result = ImmutableList.copyOf(new PathEliminateDuplicatesIterator(paths.iterator())); + assertEquals(ImmutableList.of( + "/conf/a", + "/conf/a/b", + "/conf/a/b/c"), result); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/PathParentExpandIteratorTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/PathParentExpandIteratorTest.java new file mode 100644 index 000000000..f240896fa --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/PathParentExpandIteratorTest.java @@ -0,0 +1,50 @@ +/* + * 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.caconfig.resource.impl.util; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class PathParentExpandIteratorTest { + + @Test + public void testIterator() { + List list = ImmutableList.of( + "/conf/a/b/c", + "/conf/a/b", + "/conf/x/y/z"); + + List result = ImmutableList.copyOf(new PathParentExpandIterator("/conf", list.iterator())); + assertEquals(ImmutableList.of( + "/conf/a/b/c", + "/conf/a/b", + "/conf/a", + "/conf/a/b", + "/conf/a", + "/conf/x/y/z", + "/conf/x/y", + "/conf/x"), result); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ResourceEliminateDuplicatesIteratorTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ResourceEliminateDuplicatesIteratorTest.java new file mode 100644 index 000000000..65f7dd8eb --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ResourceEliminateDuplicatesIteratorTest.java @@ -0,0 +1,66 @@ +/* + * 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.caconfig.resource.impl.util; + +import static org.apache.sling.caconfig.resource.impl.util.ContextResourceTestUtil.toResourceIterator; +import static org.junit.Assert.assertThat; + +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.apache.sling.hamcrest.ResourceIteratorMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +@SuppressWarnings("null") +public class ResourceEliminateDuplicatesIteratorTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Test + public void testIterator() { + context.build() + .resource("/content/a") + .resource("/content/a/b") + .resource("/content/a/b/c"); + + ResourceResolver rr = context.resourceResolver(); + List list = ImmutableList.of( + new ContextResource(rr.getResource("/content/a"), "/conf/a", 0), + new ContextResource(rr.getResource("/content/a/b"), "/conf/a/b", 0), + new ContextResource(rr.getResource("/content/a"), "/conf/a", 0), + new ContextResource(rr.getResource("/content/a"), null, 0), + new ContextResource(rr.getResource("/content/a/b/c"), "/conf/a/b", 0)); + + Iterator result = toResourceIterator(new ResourceEliminateDuplicatesIterator(list.iterator())); + assertThat(result, ResourceIteratorMatchers.paths( + "/content/a", + "/content/a/b", + "/content/a", + "/content/a/b/c")); + } + +} diff --git a/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ResourcePathCollatingIteratorTest.java b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ResourcePathCollatingIteratorTest.java new file mode 100644 index 000000000..793735aa7 --- /dev/null +++ b/Java-base/sling-org-apache-sling-caconfig-impl/src/src/test/java/org/apache/sling/caconfig/resource/impl/util/ResourcePathCollatingIteratorTest.java @@ -0,0 +1,125 @@ +/* + * 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.caconfig.resource.impl.util; + +import static org.apache.sling.caconfig.resource.impl.util.ContextResourceTestUtil.toContextResourceIterator; +import static org.apache.sling.caconfig.resource.impl.util.ContextResourceTestUtil.toResourceIterator; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +import java.util.Iterator; +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.caconfig.resource.spi.ContextResource; +import org.apache.sling.hamcrest.ResourceIteratorMatchers; +import org.apache.sling.testing.mock.sling.junit.SlingContext; +import org.junit.Rule; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +@SuppressWarnings("null") +public class ResourcePathCollatingIteratorTest { + + @Rule + public SlingContext context = new SlingContext(); + + @Test + public void testIterator() { + context.build() + .resource("/content/a") + .resource("/content/a/b") + .resource("/content/a/b/c") + .resource("/content/a/b/c/d"); + + ResourceResolver rr = context.resourceResolver(); + List list1 = ImmutableList.of( + rr.getResource("/content/a/b/c/d"), + rr.getResource("/content/a")); + List list2 = ImmutableList.of( + rr.getResource("/content/a/b/c"), + rr.getResource("/content/a/b"), + rr.getResource("/content/a")); + + Iterator result = toResourceIterator(new ResourcePathCollatingIterator(ImmutableList.of( + toContextResourceIterator(list1.iterator()), toContextResourceIterator(list2.iterator())))); + assertThat(result, ResourceIteratorMatchers.paths( + "/content/a/b/c/d", + "/content/a/b/c", + "/content/a/b", + "/content/a", + "/content/a")); + } + + @Test + public void testWithConfigRef() { + context.build() + .resource("/content/a") + .resource("/content/a/b") + .resource("/content/a/b/c") + .resource("/content/a/b/c/d"); + + ResourceResolver rr = context.resourceResolver(); + List list1 = ImmutableList.of( + new ContextResource(rr.getResource("/content/a"), "/conf/z", 0)); + List list2 = ImmutableList.of( + new ContextResource(rr.getResource("/content/a"), "/conf/a", 0)); + + Iterator result = new ResourcePathCollatingIterator(ImmutableList.of(list1.iterator(), list2.iterator())); + ContextResource item1 = result.next(); + ContextResource item2 = result.next(); + assertFalse(result.hasNext()); + + assertEquals("/content/a", item1.getResource().getPath()); + assertEquals("/conf/a", item1.getConfigRef()); + + assertEquals("/content/a", item2.getResource().getPath()); + assertEquals("/conf/z", item2.getConfigRef()); + } + + @Test + public void testWithConfigRefAndServiceRanking() { + context.build() + .resource("/content/a") + .resource("/content/a/b") + .resource("/content/a/b/c") + .resource("/content/a/b/c/d"); + + ResourceResolver rr = context.resourceResolver(); + List list1 = ImmutableList.of( + new ContextResource(rr.getResource("/content/a"), "/conf/z", 500)); + List list2 = ImmutableList.of( + new ContextResource(rr.getResource("/content/a"), "/conf/a", 100)); + + Iterator result = new ResourcePathCollatingIterator(ImmutableList.of(list1.iterator(), list2.iterator())); + ContextResource item1 = result.next(); + ContextResource item2 = result.next(); + assertFalse(result.hasNext()); + + assertEquals("/content/a", item1.getResource().getPath()); + assertEquals("/conf/z", item1.getConfigRef()); + + assertEquals("/content/a", item2.getResource().getPath()); + assertEquals("/conf/a", item2.getConfigRef()); + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/buggy.java new file mode 100644 index 000000000..8500c50df --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/buggy.java @@ -0,0 +1,124 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.impl.metadata; + +import java.util.Collections; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentSkipListMap; + +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.jetbrains.annotations.NotNull; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.util.tracker.BundleTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Detects configuration annotation classes deployed by any bundle via OSGi extender pattern. + */ +@Component(immediate = true, service = ConfigurationMetadataProvider.class) +public class AnnotationClassConfigurationMetadataProvider implements ConfigurationMetadataProvider { + + private BundleTracker bundleTracker; + private ConcurrentSkipListMap bundleMappings = new ConcurrentSkipListMap<>(); + + private static final Logger log = LoggerFactory.getLogger(AnnotationClassConfigurationMetadataProvider.class); + + @Activate + private void activate(BundleContext bundleContext) { + ConfigClassBundleTackerCustomizer bundlerTrackerCustomizer = new ConfigClassBundleTackerCustomizer(this); + bundleTracker = new BundleTracker(bundleContext, Bundle.ACTIVE, bundlerTrackerCustomizer); + bundleTracker.open(); + } + + @Deactivate + private void deactivate() { + bundleTracker.close(); + bundleTracker = null; + } + + @Override + public @NotNull SortedSet getConfigurationNames() { + SortedSet allConfigNames = new TreeSet(); + for (BundleConfigurationMapping bundleMapping : bundleMappings.values()) { + allConfigNames.addAll(bundleMapping.getConfigurationNames()); + } + return Collections.unmodifiableSortedSet(allConfigNames); + } + + @Override +public org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata getConfigurationMetadata(java.lang.String configName) { + org.apache.sling.caconfig.impl.metadata.ConfigurationMapping mapping = getConfigurationMapping(configName); + { + return /* NPEX_NULL_EXP */ + mapping.getConfigMetadata(); + } +} + + /** + * Get configuration mapping for given config name. + * On the way check for config name mapping conflicts accross bundles and log a warning if found. + * Is difficult to do this beforehand due to the lazy initialization of the bundle config mappings and the + * dynamic behavior of coming and going bundles with configuration classes. + * @param configName Configuration name + * @return Configuration mapping or null if none found + */ + @SuppressWarnings("null") + ConfigurationMapping getConfigurationMapping(String configName) { + ConfigurationMapping matchingConfigMapping = null; + BundleConfigurationMapping matchingBundleMapping = null; + for (BundleConfigurationMapping bundleMapping : bundleMappings.values()) { + ConfigurationMapping configMapping = bundleMapping.getConfigurationMapping(configName); + if (configMapping != null) { + if (matchingConfigMapping == null) { + matchingConfigMapping = configMapping; + matchingBundleMapping = bundleMapping; + } + else { + // conflict in name mapping across bundles found + log.warn("Configuration name conflict: Both configuration classes {} (Bundle {}) " + + "and {} (Bundle {}) define the configuration name '{}', ignoring the latter.", + matchingConfigMapping.getConfigClass().getName(), + matchingBundleMapping.getBundle().getSymbolicName(), + configMapping.getConfigClass().getName(), + bundleMapping.getBundle().getSymbolicName(), + configName); + } + } + } + return matchingConfigMapping; + } + + void addBundeMapping(BundleConfigurationMapping bundleMapping) { + log.debug("Add bundle mapping: {}", bundleMapping); + bundleMappings.put(bundleMapping.getBundle(), bundleMapping); + } + + void removeBundleMapping(BundleConfigurationMapping bundleMapping) { + log.debug("Remove bundle mapping: {}", bundleMapping); + bundleMappings.remove(bundleMapping.getBundle()); + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/metadata.json new file mode 100644 index 000000000..a029e4346 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74", + "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/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProvider.java", + "line": 76, + "npe_method": "getConfigurationMetadata", + "deref_field": "mapping", + "npe_class": "AnnotationClassConfigurationMetadataProvider", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "AnnotationClassConfigurationMetadataProvider_74" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/npe.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/npe.json new file mode 100644 index 000000000..d96b28e13 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_74/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProvider.java", + "line": 76, + "npe_method": "getConfigurationMetadata", + "deref_field": "mapping", + "npe_class": "AnnotationClassConfigurationMetadataProvider" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/buggy.java new file mode 100644 index 000000000..32b722c1e --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/buggy.java @@ -0,0 +1,127 @@ +/* + * 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.caconfig.impl.metadata; + +import java.util.Collections; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentSkipListMap; + +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.jetbrains.annotations.NotNull; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.util.tracker.BundleTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Detects configuration annotation classes deployed by any bundle via OSGi extender pattern. + */ +@Component(immediate = true, service = ConfigurationMetadataProvider.class) +public class AnnotationClassConfigurationMetadataProvider implements ConfigurationMetadataProvider { + + private BundleTracker bundleTracker; + private ConcurrentSkipListMap bundleMappings = new ConcurrentSkipListMap<>(); + + private static final Logger log = LoggerFactory.getLogger(AnnotationClassConfigurationMetadataProvider.class); + + @Activate + private void activate(BundleContext bundleContext) { + ConfigClassBundleTackerCustomizer bundlerTrackerCustomizer = new ConfigClassBundleTackerCustomizer(this); + bundleTracker = new BundleTracker(bundleContext, Bundle.ACTIVE, bundlerTrackerCustomizer); + bundleTracker.open(); + } + + @Deactivate + private void deactivate() { + bundleTracker.close(); + bundleTracker = null; + } + + @Override + public @NotNull SortedSet getConfigurationNames() { + SortedSet allConfigNames = new TreeSet(); + for (BundleConfigurationMapping bundleMapping : bundleMappings.values()) { + allConfigNames.addAll(bundleMapping.getConfigurationNames()); + } + return Collections.unmodifiableSortedSet(allConfigNames); + } + + @Override + public ConfigurationMetadata getConfigurationMetadata(String configName) { + ConfigurationMapping mapping = getConfigurationMapping(configName); + if (mapping != null) { + return mapping.getConfigMetadata(); + } + else { + return null; + } + } + + /** + * Get configuration mapping for given config name. + * On the way check for config name mapping conflicts accross bundles and log a warning if found. + * Is difficult to do this beforehand due to the lazy initialization of the bundle config mappings and the + * dynamic behavior of coming and going bundles with configuration classes. + * @param configName Configuration name + * @return Configuration mapping or null if none found + */ + @SuppressWarnings("null") +/** + * Get configuration mapping for given config name. + * On the way check for config name mapping conflicts accross bundles and log a warning if found. + * Is difficult to do this beforehand due to the lazy initialization of the bundle config mappings and the + * dynamic behavior of coming and going bundles with configuration classes. + * + * @param configName + * Configuration name + * @return Configuration mapping or null if none found + */ +org.apache.sling.caconfig.impl.metadata.ConfigurationMapping getConfigurationMapping(java.lang.String configName) { + org.apache.sling.caconfig.impl.metadata.ConfigurationMapping matchingConfigMapping = null; + org.apache.sling.caconfig.impl.metadata.BundleConfigurationMapping matchingBundleMapping = null; + for (org.apache.sling.caconfig.impl.metadata.BundleConfigurationMapping bundleMapping : bundleMappings.values()) { + org.apache.sling.caconfig.impl.metadata.ConfigurationMapping configMapping = bundleMapping.getConfigurationMapping(configName); + { + { + // conflict in name mapping across bundles found + org.apache.sling.caconfig.impl.metadata.AnnotationClassConfigurationMetadataProvider.log.warn("Configuration name conflict: Both configuration classes {} (Bundle {}) " + "and {} (Bundle {}) define the configuration name '{}', ignoring the latter.", /* NPEX_NULL_EXP */ + matchingConfigMapping.getConfigClass().getName(), matchingBundleMapping.getBundle().getSymbolicName(), configMapping.getConfigClass().getName(), bundleMapping.getBundle().getSymbolicName(), configName); + } + } + } + return matchingConfigMapping; +} + + void addBundeMapping(BundleConfigurationMapping bundleMapping) { + log.debug("Add bundle mapping: {}", bundleMapping); + bundleMappings.put(bundleMapping.getBundle(), bundleMapping); + } + + void removeBundleMapping(BundleConfigurationMapping bundleMapping) { + log.debug("Remove bundle mapping: {}", bundleMapping); + bundleMappings.remove(bundleMapping.getBundle()); + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/metadata.json new file mode 100644 index 000000000..be9c05564 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97", + "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/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProvider.java", + "line": 110, + "npe_method": "getConfigurationMapping", + "deref_field": "matchingConfigMapping", + "npe_class": "AnnotationClassConfigurationMetadataProvider", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "AnnotationClassConfigurationMetadataProvider_97" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/npe.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/npe.json new file mode 100644 index 000000000..7b3e70a4d --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassConfigurationMetadataProvider_97/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassConfigurationMetadataProvider.java", + "line": 110, + "npe_method": "getConfigurationMapping", + "deref_field": "matchingConfigMapping", + "npe_class": "AnnotationClassConfigurationMetadataProvider" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-AnnotationClassParser_122/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/buggy.java new file mode 100644 index 000000000..0a26fe38d --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/buggy.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.caconfig.impl.metadata; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.annotation.Configuration; +import org.apache.sling.caconfig.annotation.Property; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; + +/** + * Helper methods for parsing metadata from configuration annotation classes. + */ +public final class AnnotationClassParser { + + private static final Pattern METHOD_NAME_MAPPING = Pattern.compile("(\\$\\$)|(\\$)|(__)|(_)"); + + private AnnotationClassParser() { + // static methods only + } + + /** + * Checks if the given class is suitable to be mapped with context-aware configuration. + * The given class has to be an annotation class, and the {@link Configuration} annotation has to be present. + * @param clazz Given class + * @return True if class is suitable for context-aware configuration + */ + public static boolean isContextAwareConfig(Class clazz) { + return clazz.isAnnotation() && clazz.isAnnotationPresent(Configuration.class); + } + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @return Configuration name + */ + public static String getConfigurationName(Class clazz) { + Configuration configAnnotation = clazz.getAnnotation(Configuration.class); + if (configAnnotation == null) { + return null; + } + return getConfigurationName(clazz, configAnnotation); + } + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @param configAnnotation Configuration metadata + * @return Configuration name + */ + private static String getConfigurationName(Class clazz, Configuration configAnnotation) { + String configName = configAnnotation.name(); + if (StringUtils.isBlank(configName)) { + configName = clazz.getName(); + } + return configName; + } + + /** + * Implements the method name mapping as defined in OSGi R6 Compendium specification, + * Chapter 112. Declarative Services Specification, Chapter 112.8.2.1. Component Property Mapping. + * @param methodName Method name + * @return Mapped property name + */ + public static String getPropertyName(String methodName) { + Matcher matcher = METHOD_NAME_MAPPING.matcher(methodName); + StringBuffer mappedName = new StringBuffer(); + while (matcher.find()) { + String replacement = ""; + if (matcher.group(1) != null) { + replacement = "\\$"; + } + if (matcher.group(2) != null) { + replacement = ""; + } + if (matcher.group(3) != null) { + replacement = "_"; + } + if (matcher.group(4) != null) { + replacement = "."; + } + matcher.appendReplacement(mappedName, replacement); + } + matcher.appendTail(mappedName); + return mappedName.toString(); + } + + /** + * Build configuration metadata by parsing the given annotation interface class and it's configuration annotations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ +/** + * Build configuration metadata by parsing the given annotation interface class and it's configuration annotations. + * + * @param clazz + * Configuration annotation class + * @return Configuration metadata + */ +public static org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata buildConfigurationMetadata(java.lang.Class clazz) { + org.apache.sling.caconfig.annotation.Configuration configAnnotation = clazz.getAnnotation(org.apache.sling.caconfig.annotation.Configuration.class); + { + // configuration metadata and property metadata + java.lang.String configName = org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.getConfigurationName(clazz, /* NPEX_NULL_EXP */ + configAnnotation); + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata configMetadata = new org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata(configName, org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.buildConfigurationMetadata_PropertyMetadata(clazz), configAnnotation.collection()).label(org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.emptyToNull(configAnnotation.label())).description(org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.emptyToNull(configAnnotation.description())).properties(org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.propsArrayToMap(configAnnotation.property())); + return configMetadata; + } +} + + /** + * Build configuration metadata by parsing the given annotation interface class which is used for nested configurations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ + private static ConfigurationMetadata buildConfigurationMetadata_Nested(Class clazz, String configName, boolean collection) { + return new ConfigurationMetadata(configName, + buildConfigurationMetadata_PropertyMetadata(clazz), + collection); + } + + private static Collection> buildConfigurationMetadata_PropertyMetadata(Class clazz) { + // sort properties by order number, or alternatively by label, name + SortedSet> propertyMetadataSet = new TreeSet<>(new Comparator>() { + @Override + public int compare(PropertyMetadata o1, PropertyMetadata o2) { + int compare = Integer.compare(o1.getOrder(), o2.getOrder()); + if (compare == 0) { + String sort1 = StringUtils.defaultString(o1.getLabel(), o1.getName()); + String sort2 = StringUtils.defaultString(o2.getLabel(), o2.getName()); + compare = sort1.compareTo(sort2); + } + return compare; + } + }); + Method[] propertyMethods = clazz.getDeclaredMethods(); + for (Method propertyMethod : propertyMethods) { + PropertyMetadata propertyMetadata = buildPropertyMetadata(propertyMethod, propertyMethod.getReturnType()); + propertyMetadataSet.add(propertyMetadata); + } + return propertyMetadataSet; + } + + @SuppressWarnings("unchecked") + private static PropertyMetadata buildPropertyMetadata(Method propertyMethod, Class type) { + String propertyName = getPropertyName(propertyMethod.getName()); + + PropertyMetadata propertyMetadata; + if (type.isArray() && type.getComponentType().isAnnotation()) { + ConfigurationMetadata nestedConfigMetadata = buildConfigurationMetadata_Nested(type.getComponentType(), propertyName, true); + propertyMetadata = new PropertyMetadata<>(propertyName, ConfigurationMetadata[].class) + .configurationMetadata(nestedConfigMetadata); + } + else if (type.isAnnotation()) { + ConfigurationMetadata nestedConfigMetadata = buildConfigurationMetadata_Nested(type, propertyName, false); + propertyMetadata = new PropertyMetadata<>(propertyName, ConfigurationMetadata.class) + .configurationMetadata(nestedConfigMetadata); + } + else { + propertyMetadata = new PropertyMetadata<>(propertyName, type) + .defaultValue((T)propertyMethod.getDefaultValue()); + } + + Property propertyAnnotation = propertyMethod.getAnnotation(Property.class); + if (propertyAnnotation != null) { + propertyMetadata.label(emptyToNull(propertyAnnotation.label())) + .description(emptyToNull(propertyAnnotation.description())) + .properties(propsArrayToMap(propertyAnnotation.property())) + .order(propertyAnnotation.order()); + } + else { + Map emptyMap = Collections.emptyMap(); + propertyMetadata.properties(emptyMap); + } + + return (PropertyMetadata)propertyMetadata; + } + + private static String emptyToNull(String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + else { + return value; + } + } + + private static Map propsArrayToMap(String[] properties) { + Map props = new HashMap<>(); + for (String property : properties) { + int index = StringUtils.indexOf(property, "="); + if (index >= 0) { + String key = property.substring(0, index); + String value = property.substring(index + 1); + props.put(key, value); + } + } + return props; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/metadata.json new file mode 100644 index 000000000..ed2246ead --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122", + "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/caconfig/impl/metadata/AnnotationClassParser.java", + "line": 132, + "npe_method": "buildConfigurationMetadata", + "deref_field": "configAnnotation", + "npe_class": "AnnotationClassParser", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "AnnotationClassParser_122" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/npe.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/npe.json new file mode 100644 index 000000000..d3e860779 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_122/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java", + "line": 132, + "npe_method": "buildConfigurationMetadata", + "deref_field": "configAnnotation", + "npe_class": "AnnotationClassParser" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-AnnotationClassParser_192/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/buggy.java new file mode 100644 index 000000000..a6aacd82b --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/buggy.java @@ -0,0 +1,214 @@ +/* + * 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.caconfig.impl.metadata; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.annotation.Configuration; +import org.apache.sling.caconfig.annotation.Property; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; + +/** + * Helper methods for parsing metadata from configuration annotation classes. + */ +public final class AnnotationClassParser { + + private static final Pattern METHOD_NAME_MAPPING = Pattern.compile("(\\$\\$)|(\\$)|(__)|(_)"); + + private AnnotationClassParser() { + // static methods only + } + + /** + * Checks if the given class is suitable to be mapped with context-aware configuration. + * The given class has to be an annotation class, and the {@link Configuration} annotation has to be present. + * @param clazz Given class + * @return True if class is suitable for context-aware configuration + */ + public static boolean isContextAwareConfig(Class clazz) { + return clazz.isAnnotation() && clazz.isAnnotationPresent(Configuration.class); + } + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @return Configuration name + */ + public static String getConfigurationName(Class clazz) { + Configuration configAnnotation = clazz.getAnnotation(Configuration.class); + if (configAnnotation == null) { + return null; + } + return getConfigurationName(clazz, configAnnotation); + } + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @param configAnnotation Configuration metadata + * @return Configuration name + */ + private static String getConfigurationName(Class clazz, Configuration configAnnotation) { + String configName = configAnnotation.name(); + if (StringUtils.isBlank(configName)) { + configName = clazz.getName(); + } + return configName; + } + + /** + * Implements the method name mapping as defined in OSGi R6 Compendium specification, + * Chapter 112. Declarative Services Specification, Chapter 112.8.2.1. Component Property Mapping. + * @param methodName Method name + * @return Mapped property name + */ + public static String getPropertyName(String methodName) { + Matcher matcher = METHOD_NAME_MAPPING.matcher(methodName); + StringBuffer mappedName = new StringBuffer(); + while (matcher.find()) { + String replacement = ""; + if (matcher.group(1) != null) { + replacement = "\\$"; + } + if (matcher.group(2) != null) { + replacement = ""; + } + if (matcher.group(3) != null) { + replacement = "_"; + } + if (matcher.group(4) != null) { + replacement = "."; + } + matcher.appendReplacement(mappedName, replacement); + } + matcher.appendTail(mappedName); + return mappedName.toString(); + } + + /** + * Build configuration metadata by parsing the given annotation interface class and it's configuration annotations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ + public static ConfigurationMetadata buildConfigurationMetadata(Class clazz) { + Configuration configAnnotation = clazz.getAnnotation(Configuration.class); + if (configAnnotation == null) { + throw new IllegalArgumentException("Class has not @Configuration annotation: " + clazz.getName()); + } + + // configuration metadata and property metadata + String configName = getConfigurationName(clazz, configAnnotation); + ConfigurationMetadata configMetadata = new ConfigurationMetadata(configName, + buildConfigurationMetadata_PropertyMetadata(clazz), + configAnnotation.collection()) + .label(emptyToNull(configAnnotation.label())) + .description(emptyToNull(configAnnotation.description())) + .properties(propsArrayToMap(configAnnotation.property())); + + return configMetadata; + } + + /** + * Build configuration metadata by parsing the given annotation interface class which is used for nested configurations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ + private static ConfigurationMetadata buildConfigurationMetadata_Nested(Class clazz, String configName, boolean collection) { + return new ConfigurationMetadata(configName, + buildConfigurationMetadata_PropertyMetadata(clazz), + collection); + } + + private static Collection> buildConfigurationMetadata_PropertyMetadata(Class clazz) { + // sort properties by order number, or alternatively by label, name + SortedSet> propertyMetadataSet = new TreeSet<>(new Comparator>() { + @Override + public int compare(PropertyMetadata o1, PropertyMetadata o2) { + int compare = Integer.compare(o1.getOrder(), o2.getOrder()); + if (compare == 0) { + String sort1 = StringUtils.defaultString(o1.getLabel(), o1.getName()); + String sort2 = StringUtils.defaultString(o2.getLabel(), o2.getName()); + compare = sort1.compareTo(sort2); + } + return compare; + } + }); + Method[] propertyMethods = clazz.getDeclaredMethods(); + for (Method propertyMethod : propertyMethods) { + PropertyMetadata propertyMetadata = buildPropertyMetadata(propertyMethod, propertyMethod.getReturnType()); + propertyMetadataSet.add(propertyMetadata); + } + return propertyMetadataSet; + } + + @SuppressWarnings("unchecked") +private static org.apache.sling.caconfig.spi.metadata.PropertyMetadata buildPropertyMetadata(java.lang.reflect.Method propertyMethod, java.lang.Class type) { + java.lang.String propertyName = org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.getPropertyName(propertyMethod.getName()); + org.apache.sling.caconfig.spi.metadata.PropertyMetadata propertyMetadata; + if (type.isArray() && type.getComponentType().isAnnotation()) { + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata nestedConfigMetadata = org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.buildConfigurationMetadata_Nested(type.getComponentType(), propertyName, true); + propertyMetadata = new org.apache.sling.caconfig.spi.metadata.PropertyMetadata<>(propertyName, org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata[].class).configurationMetadata(nestedConfigMetadata); + } else if (type.isAnnotation()) { + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata nestedConfigMetadata = org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.buildConfigurationMetadata_Nested(type, propertyName, false); + propertyMetadata = new org.apache.sling.caconfig.spi.metadata.PropertyMetadata<>(propertyName, org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata.class).configurationMetadata(nestedConfigMetadata); + } else { + propertyMetadata = new org.apache.sling.caconfig.spi.metadata.PropertyMetadata<>(propertyName, type).defaultValue(((T) (propertyMethod.getDefaultValue()))); + } + org.apache.sling.caconfig.annotation.Property propertyAnnotation = propertyMethod.getAnnotation(org.apache.sling.caconfig.annotation.Property.class); + { + propertyMetadata.label(org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.emptyToNull(/* NPEX_NULL_EXP */ + propertyAnnotation.label())).description(org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.emptyToNull(propertyAnnotation.description())).properties(org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.propsArrayToMap(propertyAnnotation.property())).order(propertyAnnotation.order()); + } + return ((org.apache.sling.caconfig.spi.metadata.PropertyMetadata) (propertyMetadata)); +} + + private static String emptyToNull(String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + else { + return value; + } + } + + private static Map propsArrayToMap(String[] properties) { + Map props = new HashMap<>(); + for (String property : properties) { + int index = StringUtils.indexOf(property, "="); + if (index >= 0) { + String key = property.substring(0, index); + String value = property.substring(index + 1); + props.put(key, value); + } + } + return props; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/metadata.json new file mode 100644 index 000000000..dccae2de4 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192", + "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/caconfig/impl/metadata/AnnotationClassParser.java", + "line": 187, + "npe_method": "buildPropertyMetadata", + "deref_field": "propertyAnnotation", + "npe_class": "AnnotationClassParser", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "AnnotationClassParser_192" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/npe.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/npe.json new file mode 100644 index 000000000..e20c21a83 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_192/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java", + "line": 187, + "npe_method": "buildPropertyMetadata", + "deref_field": "propertyAnnotation", + "npe_class": "AnnotationClassParser" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-AnnotationClassParser_66/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/buggy.java new file mode 100644 index 000000000..cb77a9655 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/buggy.java @@ -0,0 +1,235 @@ +/* + * 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.caconfig.impl.metadata; + +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.annotation.Configuration; +import org.apache.sling.caconfig.annotation.Property; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; + +/** + * Helper methods for parsing metadata from configuration annotation classes. + */ +public final class AnnotationClassParser { + + private static final Pattern METHOD_NAME_MAPPING = Pattern.compile("(\\$\\$)|(\\$)|(__)|(_)"); + + private AnnotationClassParser() { + // static methods only + } + + /** + * Checks if the given class is suitable to be mapped with context-aware configuration. + * The given class has to be an annotation class, and the {@link Configuration} annotation has to be present. + * @param clazz Given class + * @return True if class is suitable for context-aware configuration + */ + public static boolean isContextAwareConfig(Class clazz) { + return clazz.isAnnotation() && clazz.isAnnotationPresent(Configuration.class); + } + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @return Configuration name + */ +/** + * Get configuration name for given configuration annotation class. + * + * @param clazz + * Annotation class + * @return Configuration name + */ +public static java.lang.String getConfigurationName(java.lang.Class clazz) { + org.apache.sling.caconfig.annotation.Configuration configAnnotation = clazz.getAnnotation(org.apache.sling.caconfig.annotation.Configuration.class); + { + return org.apache.sling.caconfig.impl.metadata.AnnotationClassParser.getConfigurationName(clazz, /* NPEX_NULL_EXP */ + configAnnotation); + } +} + + /** + * Get configuration name for given configuration annotation class. + * @param clazz Annotation class + * @param configAnnotation Configuration metadata + * @return Configuration name + */ + private static String getConfigurationName(Class clazz, Configuration configAnnotation) { + String configName = configAnnotation.name(); + if (StringUtils.isBlank(configName)) { + configName = clazz.getName(); + } + return configName; + } + + /** + * Implements the method name mapping as defined in OSGi R6 Compendium specification, + * Chapter 112. Declarative Services Specification, Chapter 112.8.2.1. Component Property Mapping. + * @param methodName Method name + * @return Mapped property name + */ + public static String getPropertyName(String methodName) { + Matcher matcher = METHOD_NAME_MAPPING.matcher(methodName); + StringBuffer mappedName = new StringBuffer(); + while (matcher.find()) { + String replacement = ""; + if (matcher.group(1) != null) { + replacement = "\\$"; + } + if (matcher.group(2) != null) { + replacement = ""; + } + if (matcher.group(3) != null) { + replacement = "_"; + } + if (matcher.group(4) != null) { + replacement = "."; + } + matcher.appendReplacement(mappedName, replacement); + } + matcher.appendTail(mappedName); + return mappedName.toString(); + } + + /** + * Build configuration metadata by parsing the given annotation interface class and it's configuration annotations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ + public static ConfigurationMetadata buildConfigurationMetadata(Class clazz) { + Configuration configAnnotation = clazz.getAnnotation(Configuration.class); + if (configAnnotation == null) { + throw new IllegalArgumentException("Class has not @Configuration annotation: " + clazz.getName()); + } + + // configuration metadata and property metadata + String configName = getConfigurationName(clazz, configAnnotation); + ConfigurationMetadata configMetadata = new ConfigurationMetadata(configName, + buildConfigurationMetadata_PropertyMetadata(clazz), + configAnnotation.collection()) + .label(emptyToNull(configAnnotation.label())) + .description(emptyToNull(configAnnotation.description())) + .properties(propsArrayToMap(configAnnotation.property())); + + return configMetadata; + } + + /** + * Build configuration metadata by parsing the given annotation interface class which is used for nested configurations. + * @param clazz Configuration annotation class + * @return Configuration metadata + */ + private static ConfigurationMetadata buildConfigurationMetadata_Nested(Class clazz, String configName, boolean collection) { + return new ConfigurationMetadata(configName, + buildConfigurationMetadata_PropertyMetadata(clazz), + collection); + } + + private static Collection> buildConfigurationMetadata_PropertyMetadata(Class clazz) { + // sort properties by order number, or alternatively by label, name + SortedSet> propertyMetadataSet = new TreeSet<>(new Comparator>() { + @Override + public int compare(PropertyMetadata o1, PropertyMetadata o2) { + int compare = Integer.compare(o1.getOrder(), o2.getOrder()); + if (compare == 0) { + String sort1 = StringUtils.defaultString(o1.getLabel(), o1.getName()); + String sort2 = StringUtils.defaultString(o2.getLabel(), o2.getName()); + compare = sort1.compareTo(sort2); + } + return compare; + } + }); + Method[] propertyMethods = clazz.getDeclaredMethods(); + for (Method propertyMethod : propertyMethods) { + PropertyMetadata propertyMetadata = buildPropertyMetadata(propertyMethod, propertyMethod.getReturnType()); + propertyMetadataSet.add(propertyMetadata); + } + return propertyMetadataSet; + } + + @SuppressWarnings("unchecked") + private static PropertyMetadata buildPropertyMetadata(Method propertyMethod, Class type) { + String propertyName = getPropertyName(propertyMethod.getName()); + + PropertyMetadata propertyMetadata; + if (type.isArray() && type.getComponentType().isAnnotation()) { + ConfigurationMetadata nestedConfigMetadata = buildConfigurationMetadata_Nested(type.getComponentType(), propertyName, true); + propertyMetadata = new PropertyMetadata<>(propertyName, ConfigurationMetadata[].class) + .configurationMetadata(nestedConfigMetadata); + } + else if (type.isAnnotation()) { + ConfigurationMetadata nestedConfigMetadata = buildConfigurationMetadata_Nested(type, propertyName, false); + propertyMetadata = new PropertyMetadata<>(propertyName, ConfigurationMetadata.class) + .configurationMetadata(nestedConfigMetadata); + } + else { + propertyMetadata = new PropertyMetadata<>(propertyName, type) + .defaultValue((T)propertyMethod.getDefaultValue()); + } + + Property propertyAnnotation = propertyMethod.getAnnotation(Property.class); + if (propertyAnnotation != null) { + propertyMetadata.label(emptyToNull(propertyAnnotation.label())) + .description(emptyToNull(propertyAnnotation.description())) + .properties(propsArrayToMap(propertyAnnotation.property())) + .order(propertyAnnotation.order()); + } + else { + Map emptyMap = Collections.emptyMap(); + propertyMetadata.properties(emptyMap); + } + + return (PropertyMetadata)propertyMetadata; + } + + private static String emptyToNull(String value) { + if (StringUtils.isEmpty(value)) { + return null; + } + else { + return value; + } + } + + private static Map propsArrayToMap(String[] properties) { + Map props = new HashMap<>(); + for (String property : properties) { + int index = StringUtils.indexOf(property, "="); + if (index >= 0) { + String key = property.substring(0, index); + String value = property.substring(index + 1); + props.put(key, value); + } + } + return props; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/metadata.json new file mode 100644 index 000000000..52adb5b0c --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66", + "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/caconfig/impl/metadata/AnnotationClassParser.java", + "line": 75, + "npe_method": "getConfigurationName", + "deref_field": "configAnnotation", + "npe_class": "AnnotationClassParser", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "AnnotationClassParser_66" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/npe.json b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/npe.json new file mode 100644 index 000000000..d9aa88348 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-AnnotationClassParser_66/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/AnnotationClassParser.java", + "line": 75, + "npe_method": "getConfigurationName", + "deref_field": "configAnnotation", + "npe_class": "AnnotationClassParser" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-BundleConfigurationMapping_106/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/buggy.java new file mode 100644 index 000000000..be020ece3 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/buggy.java @@ -0,0 +1,125 @@ +/* + * 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.caconfig.impl.metadata; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.StringUtils; +import org.osgi.framework.Bundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Contains metadata of all configuration classes that where declared via the bundle header for a bundle. + * The configuration class metadata is not parsed on initialization, but lazily on first access. + */ +class BundleConfigurationMapping { + + private final Bundle bundle; + private final String classNamesList; + private final AtomicReference> configMappingsRef = new AtomicReference<>(null); + + private static final Logger log = LoggerFactory.getLogger(BundleConfigurationMapping.class); + + public BundleConfigurationMapping(Bundle bundle, String classNamesList) { + this.bundle = bundle; + this.classNamesList = classNamesList; + } + + public Bundle getBundle() { + return bundle; + } + + /** + * Thread-safe lazy initialization of configuration mappings. + * @return Configuration mappings + */ + private Map getConfigMappings() { + Map configMappings = configMappingsRef.get(); + if (configMappings == null) { + configMappings = initializeConfigMappings(); + if (configMappingsRef.compareAndSet(null, configMappings)) { + return configMappings; + } + else { + return configMappingsRef.get(); + } + } + else { + return configMappings; + } + } + + /** + * Parse all annotation classes + * @return + */ + private Map initializeConfigMappings() { + Map configMappings = new HashMap<>(); + + String[] classNames = StringUtils.split(StringUtils.deleteWhitespace(classNamesList), ","); + for (String className : classNames) { + try { + Class configClass = bundle.loadClass(className); + if (AnnotationClassParser.isContextAwareConfig(configClass)) { + log.debug("{}: Add configuration class {}", bundle.getSymbolicName(), className); + + ConfigurationMapping configMapping = new ConfigurationMapping(configClass); + if (!hasMappingConflict(configMapping, configMappings)) { + configMappings.put(configMapping.getConfigName(), configMapping); + } + } + else { + log.warn("Ignoring invalid configuration class: {}", className); + } + } + catch (ClassNotFoundException ex) { + log.warn("Unable to load class: " + className, ex); + } + } + + return configMappings; + } + +private boolean hasMappingConflict(org.apache.sling.caconfig.impl.metadata.ConfigurationMapping newConfigMapping, java.util.Map configMappings) { + org.apache.sling.caconfig.impl.metadata.ConfigurationMapping conflictingConfigMapping = configMappings.get(newConfigMapping.getConfigName()); + { + org.apache.sling.caconfig.impl.metadata.BundleConfigurationMapping.log.warn("Configuration name conflict in bundle {}: Both configuration classes {} and {} define the configuration name '{}', ignoring the latter.", bundle.getSymbolicName(), /* NPEX_NULL_EXP */ + conflictingConfigMapping.getConfigClass().getName(), newConfigMapping.getConfigClass().getName(), newConfigMapping.getConfigName()); + return true; + } +} + + public Set getConfigurationNames() { + return getConfigMappings().keySet(); + } + + public ConfigurationMapping getConfigurationMapping(String configName) { + return getConfigMappings().get(configName); + } + + @Override + public String toString() { + return "Classes from bundle '" + bundle.getSymbolicName() + "': " + classNamesList; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/metadata.json new file mode 100644 index 000000000..e852b0b15 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106", + "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/caconfig/impl/metadata/BundleConfigurationMapping.java", + "line": 107, + "npe_method": "hasMappingConflict", + "deref_field": "conflictingConfigMapping", + "npe_class": "BundleConfigurationMapping", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "BundleConfigurationMapping_106" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/npe.json b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/npe.json new file mode 100644 index 000000000..e250bd2a0 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_106/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/BundleConfigurationMapping.java", + "line": 107, + "npe_method": "hasMappingConflict", + "deref_field": "conflictingConfigMapping", + "npe_class": "BundleConfigurationMapping" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-BundleConfigurationMapping_58/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/buggy.java new file mode 100644 index 000000000..51e351e64 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/buggy.java @@ -0,0 +1,129 @@ +/* + * 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.caconfig.impl.metadata; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.StringUtils; +import org.osgi.framework.Bundle; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Contains metadata of all configuration classes that where declared via the bundle header for a bundle. + * The configuration class metadata is not parsed on initialization, but lazily on first access. + */ +class BundleConfigurationMapping { + + private final Bundle bundle; + private final String classNamesList; + private final AtomicReference> configMappingsRef = new AtomicReference<>(null); + + private static final Logger log = LoggerFactory.getLogger(BundleConfigurationMapping.class); + + public BundleConfigurationMapping(Bundle bundle, String classNamesList) { + this.bundle = bundle; + this.classNamesList = classNamesList; + } + + public Bundle getBundle() { + return bundle; + } + + /** + * Thread-safe lazy initialization of configuration mappings. + * @return Configuration mappings + */ +/** + * Thread-safe lazy initialization of configuration mappings. + * + * @return Configuration mappings + */ +private java.util.Map getConfigMappings() { + java.util.Map configMappings = configMappingsRef.get(); + { + return /* NPEX_NULL_EXP */ + configMappings; + } +} + + /** + * Parse all annotation classes + * @return + */ + private Map initializeConfigMappings() { + Map configMappings = new HashMap<>(); + + String[] classNames = StringUtils.split(StringUtils.deleteWhitespace(classNamesList), ","); + for (String className : classNames) { + try { + Class configClass = bundle.loadClass(className); + if (AnnotationClassParser.isContextAwareConfig(configClass)) { + log.debug("{}: Add configuration class {}", bundle.getSymbolicName(), className); + + ConfigurationMapping configMapping = new ConfigurationMapping(configClass); + if (!hasMappingConflict(configMapping, configMappings)) { + configMappings.put(configMapping.getConfigName(), configMapping); + } + } + else { + log.warn("Ignoring invalid configuration class: {}", className); + } + } + catch (ClassNotFoundException ex) { + log.warn("Unable to load class: " + className, ex); + } + } + + return configMappings; + } + + private boolean hasMappingConflict(ConfigurationMapping newConfigMapping, + Map configMappings) { + ConfigurationMapping conflictingConfigMapping = configMappings.get(newConfigMapping.getConfigName()); + if (conflictingConfigMapping != null) { + log.warn("Configuration name conflict in bundle {}: Both configuration classes {} and {} define the configuration name '{}', ignoring the latter.", + bundle.getSymbolicName(), + conflictingConfigMapping.getConfigClass().getName(), + newConfigMapping.getConfigClass().getName(), + newConfigMapping.getConfigName()); + return true; + } + else { + return false; + } + } + + public Set getConfigurationNames() { + return getConfigMappings().keySet(); + } + + public ConfigurationMapping getConfigurationMapping(String configName) { + return getConfigMappings().get(configName); + } + + @Override + public String toString() { + return "Classes from bundle '" + bundle.getSymbolicName() + "': " + classNamesList; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/metadata.json new file mode 100644 index 000000000..207e54e7a --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58", + "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/caconfig/impl/metadata/BundleConfigurationMapping.java", + "line": 65, + "npe_method": "getConfigMappings", + "deref_field": "configMappings", + "npe_class": "BundleConfigurationMapping", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "BundleConfigurationMapping_58" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/npe.json b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/npe.json new file mode 100644 index 000000000..1ce159435 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-BundleConfigurationMapping_58/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/BundleConfigurationMapping.java", + "line": 65, + "npe_method": "getConfigMappings", + "deref_field": "configMappings", + "npe_class": "BundleConfigurationMapping" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-CAConfigInventoryPrinter_146/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/buggy.java new file mode 100644 index 000000000..dd0812571 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/buggy.java @@ -0,0 +1,169 @@ +/* + * 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.caconfig.management.impl.console; + +import java.io.PrintWriter; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.felix.inventory.Format; +import org.apache.felix.inventory.InventoryPrinter; +import org.apache.sling.caconfig.resource.spi.CollectionInheritanceDecider; +import org.apache.sling.caconfig.resource.spi.ConfigurationResourceResolvingStrategy; +import org.apache.sling.caconfig.resource.spi.ContextPathStrategy; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.ServiceUtil; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; + +/** + * Web console configuration printer. + */ +@Component(service=InventoryPrinter.class, +property={Constants.SERVICE_DESCRIPTION + "=Apache Sling Context-Aware Configuration Resolver Console Inventory Printer", + InventoryPrinter.NAME + "=" + CAConfigInventoryPrinter.NAME, + InventoryPrinter.TITLE + "=" + CAConfigInventoryPrinter.TITLE, + InventoryPrinter.FORMAT + "=TEXT"}) +public class CAConfigInventoryPrinter implements InventoryPrinter { + + public static final String NAME = "slingcaconfig"; + public static final String TITLE = "Sling Context-Aware Configuration"; + + private BundleContext bundleContext; + + @Activate + private void activate(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + @Override + public void print(PrintWriter pw, Format format, boolean isZip) { + if (format != Format.TEXT) { + return; + } + + printSPISection(pw, ContextPathStrategy.class, "Context Path Strategies"); + printSPISection(pw, ConfigurationResourceResolvingStrategy.class, "Configuration Resource Resolving Strategies"); + printSPISection(pw, CollectionInheritanceDecider.class, "Collection Inheritance Deciders"); + printSPISection(pw, ConfigurationInheritanceStrategy.class, "Configuration Inheritance Strategies"); + printSPISection(pw, ConfigurationPersistenceStrategy2.class, "Configuration Persistance Strategies", + new ConfigurationPersistenceStrategyPrinter()); + printSPISection(pw, ConfigurationMetadataProvider.class, "Configuration Metadata Providers", + new ConfigurationMetadataPrinter()); + printSPISection(pw, ConfigurationOverrideProvider.class, "Configuration Override Providers", + new ConfigurationOverridePrinter()); + } + + @SafeVarargs + private final void printSPISection(PrintWriter pw, Class clazz, String title, ServiceConfigurationPrinter... serviceConfigPrinters) { + Collection> serviceReferences = getServiceReferences(clazz); + + pw.println(title); + pw.println(StringUtils.repeat('-', title.length())); + + if (serviceReferences.isEmpty()) { + pw.println("(none)"); + } + else { + for (ServiceReference serviceReference : serviceReferences) { + pw.print(ServiceConfigurationPrinter.BULLET); + pw.print(getServiceClassName(serviceReference)); + pw.print(" ["); + pw.print(getServiceRanking(serviceReference)); + if (!isEnabled(serviceReference)) { + pw.print(", disabled"); + } + pw.print("]"); + pw.println(); + for (ServiceConfigurationPrinter serviceConfigPrinter : serviceConfigPrinters) { + serviceConfigPrinter.printConfiguration(pw, serviceReference, bundleContext); + } + } + } + pw.println(); + } + + private Collection> getServiceReferences(Class clazz) { + try { + SortedMap,ServiceReference> sortedServices = new TreeMap<>(); + Collection> serviceReferences = bundleContext.getServiceReferences(clazz, null); + for (ServiceReference serviceReference : serviceReferences) { + Map props = new HashMap<>(); + for (String property : serviceReference.getPropertyKeys()) { + props.put(property, serviceReference.getProperty(property)); + } + sortedServices.put( + ServiceUtil.getComparableForServiceRanking(props, Order.DESCENDING), + serviceReference + ); + } + return sortedServices.values(); + } + catch (InvalidSyntaxException ex) { + throw new RuntimeException(ex); + } + } + + private String getServiceClassName(ServiceReference serviceReference) { + Object service = bundleContext.getService(serviceReference); + String serviceClassName = service.getClass().getName(); + bundleContext.ungetService(serviceReference); + return serviceClassName; + } + +private int getServiceRanking(org.osgi.framework.ServiceReference serviceReference) { + java.lang.Object serviceRanking = serviceReference.getProperty(org.osgi.framework.Constants.SERVICE_RANKING); + { + if (/* NPEX_NULL_EXP */ + serviceRanking instanceof java.lang.Number) { + return ((java.lang.Number) (serviceRanking)).intValue(); + } else { + return org.apache.commons.lang3.math.NumberUtils.toInt(serviceRanking.toString(), 0); + } + } +} + + private boolean isEnabled(ServiceReference serviceReference) { + Object enabledObject = (Object)serviceReference.getProperty("enabled"); + if (enabledObject != null) { + if (enabledObject instanceof Boolean) { + return ((Boolean)enabledObject).booleanValue(); + } + else { + return BooleanUtils.toBoolean(enabledObject.toString()); + } + } + return true; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/metadata.json new file mode 100644 index 000000000..746c02d46 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_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/caconfig/management/impl/console/CAConfigInventoryPrinter.java", + "line": 148, + "npe_method": "getServiceRanking", + "deref_field": "serviceRanking", + "npe_class": "CAConfigInventoryPrinter", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "CAConfigInventoryPrinter_146" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/npe.json b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/npe.json new file mode 100644 index 000000000..d0f403d87 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-CAConfigInventoryPrinter_146/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/console/CAConfigInventoryPrinter.java", + "line": 148, + "npe_method": "getServiceRanking", + "deref_field": "serviceRanking", + "npe_class": "CAConfigInventoryPrinter" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationBindingsValueProvider_82/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/buggy.java new file mode 100644 index 000000000..d80cd9312 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/buggy.java @@ -0,0 +1,208 @@ +/* + * 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.caconfig.impl; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.script.Bindings; + +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.caconfig.ConfigurationBuilder; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.scripting.api.BindingsValuesProvider; +import org.osgi.framework.Constants; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.metatype.annotations.AttributeDefinition; +import org.osgi.service.metatype.annotations.Designate; +import org.osgi.service.metatype.annotations.ObjectClassDefinition; + +/** + * Binds a script variable "caconfig" to the current configuration value map to be + * used in HTL (Sightly). + */ +@Component(immediate = true, service = BindingsValuesProvider.class, property = { + "javax.script.name=sightly", + Constants.SERVICE_RANKING + "=100" }) +@Designate(ocd = ConfigurationBindingsValueProvider.Config.class) +public class ConfigurationBindingsValueProvider implements BindingsValuesProvider { + + /** + * Name of the variable to which the config value map is bound to in script + * configuration. + */ + public static final String BINDING_VARIABLE = "caconfig"; + + @ObjectClassDefinition(name = "Apache Sling Context-Aware Configuration HTL Binding Values Provider", + description = "Binds a script variable '" + BINDING_VARIABLE + "' to the HTL/Sightly scripting context.") + static @interface Config { + + @AttributeDefinition(name = "Enabled", description = "Enable provider.") + boolean enabled() default true; + + } + + @Reference + private ConfigurationMetadataProviderMultiplexer configMetadataProvider; + + private boolean enabled; + + @Override + @SuppressWarnings("unused") +public void addBindings(javax.script.Bindings bindings) { + if ((!enabled) || (!bindings.containsKey(org.apache.sling.api.scripting.SlingBindings.REQUEST))) { + return; + } + org.apache.sling.api.SlingHttpServletRequest request = ((org.apache.sling.api.SlingHttpServletRequest) (bindings.get(org.apache.sling.api.scripting.SlingBindings.REQUEST))); + org.apache.sling.api.resource.Resource resource = request.getResource(); + { + java.util.Map configMap = new org.apache.sling.caconfig.impl.ConfigurationBindingsValueProvider.ConfigMap(/* NPEX_NULL_EXP */ + resource, configMetadataProvider); + bindings.put(org.apache.sling.caconfig.impl.ConfigurationBindingsValueProvider.BINDING_VARIABLE, configMap); + } +} + + @Activate + void activate(Config config) { + this.enabled = config.enabled(); + } + + + /** + * This is a "virtual" containing configuration names as keys, and the underlying value maps/value map collections as values. + * The map accesses only the data that is really required in a lazy fashion. + */ + private static class ConfigMap implements Map { + + private final Resource resource; + private final ConfigurationMetadataProvider configMetadataProvider; + private Set configNamesCache; + private Map valuesCache = new HashMap<>(); + + ConfigMap(Resource resource, ConfigurationMetadataProvider configMetadataProvider) { + this.resource = resource; + this.configMetadataProvider = configMetadataProvider; + } + + private Set getConfigNames() { + if (configNamesCache == null) { + configNamesCache = configMetadataProvider.getConfigurationNames(); + } + return configNamesCache; + } + + @Override + public int size() { + return getConfigNames().size(); + } + + @Override + public boolean isEmpty() { + return getConfigNames().isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return getConfigNames().contains(key); + } + + @Override + public Object get(Object key) { + Object value = valuesCache.get(key); + if (value == null) { + value = getConfigValue((String)key); + if (value != null) { + valuesCache.put((String)key, value); + } + } + return value; + } + + private Object getConfigValue(String configName) { + @SuppressWarnings("null") + ConfigurationBuilder configBuilder = resource.adaptTo(ConfigurationBuilder.class).name(configName); + if (isCollection(configName)) { + return configBuilder.asValueMapCollection(); + } + else { + return configBuilder.asValueMap(); + } + } + + private boolean isCollection(String configName) { + ConfigurationMetadata configMetadata = configMetadataProvider.getConfigurationMetadata(configName); + if (configMetadata != null) { + return configMetadata.isCollection(); + } + else { + return false; + } + } + + @Override + public Set keySet() { + return getConfigNames(); + } + + @Override + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(Map m) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public Collection values() { + throw new UnsupportedOperationException(); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/metadata.json new file mode 100644 index 000000000..bdbe16647 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82", + "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/caconfig/impl/ConfigurationBindingsValueProvider.java", + "line": 84, + "npe_method": "addBindings", + "deref_field": "resource", + "npe_class": "ConfigurationBindingsValueProvider", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationBindingsValueProvider_82" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/npe.json new file mode 100644 index 000000000..f22b567a7 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationBindingsValueProvider_82/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/ConfigurationBindingsValueProvider.java", + "line": 84, + "npe_method": "addBindings", + "deref_field": "resource", + "npe_class": "ConfigurationBindingsValueProvider" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationCollectionDataImpl_69/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/buggy.java new file mode 100644 index 000000000..07efbdb2a --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/buggy.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.caconfig.management.ConfigurationCollectionData; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.resource.impl.def.ConfigurationResourceNameConstants; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationCollectionDataImpl implements ConfigurationCollectionData { + + private final String configName; + private final Collection items; + private final String resourcePath; + private final Map properties; + private final ConfigurationManagementSettings configurationManagementSettings; + private Map filteredPropertiesCache; + + public ConfigurationCollectionDataImpl(String configName, Collection items, + String resourcePath, Map properties, + ConfigurationManagementSettings configurationManagementSettings) { + this.configName = configName; + this.items = items; + this.resourcePath = resourcePath; + this.properties = properties; + this.configurationManagementSettings = configurationManagementSettings; + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public @NotNull Collection getItems() { + return items; + } + + @Override + public String getResourcePath() { + return resourcePath; + } + + @Override +public java.util.@org.jetbrains.annotations.NotNull +Map getProperties() { + if (filteredPropertiesCache == null) { + filteredPropertiesCache = new java.util.HashMap<>(); + { + filteredPropertiesCache.putAll(/* NPEX_NULL_EXP */ + properties); + org.apache.sling.caconfig.management.impl.PropertiesFilterUtil.removeIgnoredProperties(filteredPropertiesCache, configurationManagementSettings); + } + } + return filteredPropertiesCache; +} + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/metadata.json new file mode 100644 index 000000000..e317f6e7a --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69", + "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/caconfig/management/impl/ConfigurationCollectionDataImpl.java", + "line": 72, + "npe_method": "getProperties", + "deref_field": "properties", + "npe_class": "ConfigurationCollectionDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationCollectionDataImpl_69" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/npe.json new file mode 100644 index 000000000..0392083f3 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationCollectionDataImpl_69/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationCollectionDataImpl.java", + "line": 72, + "npe_method": "getProperties", + "deref_field": "properties", + "npe_class": "ConfigurationCollectionDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_134/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/buggy.java new file mode 100644 index 000000000..9d7744e76 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/buggy.java @@ -0,0 +1,283 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override +public java.util.@org.jetbrains.annotations.NotNull +Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new java.util.LinkedHashSet<>(); + { + propertyNamesCache.addAll(/* NPEX_NULL_EXP */ + configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new java.util.TreeSet<>(org.apache.sling.api.resource.ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + org.apache.sling.caconfig.management.impl.PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; +} + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override + public @NotNull ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + Map props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new ValueMapDecorator(props); + } + return effectiveValuesCache; + } + + private void resolveNestedConfigs(Map props) { + if (configMetadata == null) { + return; + } + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + String relatedConfigPath = resolvedConfigurationResource != null ? resolvedConfigurationResource.getPath() : null; + String nestedConfigName; + if (configResourceCollection) { + String collectionItemName = StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + + "/" + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath) + + "/" + nestedConfigMetadata.getName(); + } + else { + nestedConfigName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(ConfigurationMetadata.class)) { + ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } + else if (propertyMetadata.getType().equals(ConfigurationMetadata[].class)) { + Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new ConfigurationData[configDatas.size()])); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/metadata.json new file mode 100644 index 000000000..0caffdd1d --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 137, + "npe_method": "getPropertyNames", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_134" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/npe.json new file mode 100644 index 000000000..e809625c9 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_134/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 137, + "npe_method": "getPropertyNames", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_163/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/buggy.java new file mode 100644 index 000000000..779c49927 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/buggy.java @@ -0,0 +1,283 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override +public org.apache.sling.api.resource.@org.jetbrains.annotations.NotNull +ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + java.util.Map props = new java.util.HashMap<>(); + { + for (org.apache.sling.caconfig.spi.metadata.PropertyMetadata propertyMetadata : /* NPEX_NULL_EXP */ + configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(org.apache.sling.api.resource.ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + org.apache.sling.caconfig.management.impl.PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new org.apache.sling.api.wrappers.ValueMapDecorator(props); + } + return effectiveValuesCache; +} + + private void resolveNestedConfigs(Map props) { + if (configMetadata == null) { + return; + } + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + String relatedConfigPath = resolvedConfigurationResource != null ? resolvedConfigurationResource.getPath() : null; + String nestedConfigName; + if (configResourceCollection) { + String collectionItemName = StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + + "/" + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath) + + "/" + nestedConfigMetadata.getName(); + } + else { + nestedConfigName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(ConfigurationMetadata.class)) { + ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } + else if (propertyMetadata.getType().equals(ConfigurationMetadata[].class)) { + Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new ConfigurationData[configDatas.size()])); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/metadata.json new file mode 100644 index 000000000..565bbd74c --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 166, + "npe_method": "getEffectiveValues", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_163" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/npe.json new file mode 100644 index 000000000..8e1aa4578 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_163/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 166, + "npe_method": "getEffectiveValues", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_165/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/buggy.java new file mode 100644 index 000000000..802e8b9ba --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/buggy.java @@ -0,0 +1,283 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override +public org.apache.sling.api.resource.@org.jetbrains.annotations.NotNull +ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + java.util.Map props = new java.util.HashMap<>(); + { + for (org.apache.sling.caconfig.spi.metadata.PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + { + props.put(propertyMetadata.getName(), /* NPEX_NULL_EXP */ + propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(org.apache.sling.api.resource.ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + org.apache.sling.caconfig.management.impl.PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new org.apache.sling.api.wrappers.ValueMapDecorator(props); + } + return effectiveValuesCache; +} + + private void resolveNestedConfigs(Map props) { + if (configMetadata == null) { + return; + } + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + String relatedConfigPath = resolvedConfigurationResource != null ? resolvedConfigurationResource.getPath() : null; + String nestedConfigName; + if (configResourceCollection) { + String collectionItemName = StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + + "/" + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath) + + "/" + nestedConfigMetadata.getName(); + } + else { + nestedConfigName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(ConfigurationMetadata.class)) { + ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } + else if (propertyMetadata.getType().equals(ConfigurationMetadata[].class)) { + Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new ConfigurationData[configDatas.size()])); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/metadata.json new file mode 100644 index 000000000..5b74df347 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 168, + "npe_method": "getEffectiveValues", + "deref_field": "getDefaultValue", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_165" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/npe.json new file mode 100644 index 000000000..1fe6ea39b --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_165/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 168, + "npe_method": "getEffectiveValues", + "deref_field": "getDefaultValue", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_181/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/buggy.java new file mode 100644 index 000000000..a1bbdcb7f --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/buggy.java @@ -0,0 +1,277 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override + public @NotNull ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + Map props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new ValueMapDecorator(props); + } + return effectiveValuesCache; + } + +private void resolveNestedConfigs(java.util.Map props) { + { + for (org.apache.sling.caconfig.spi.metadata.PropertyMetadata propertyMetadata : /* NPEX_NULL_EXP */ + configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + java.lang.String relatedConfigPath = (resolvedConfigurationResource != null) ? resolvedConfigurationResource.getPath() : null; + java.lang.String nestedConfigName; + if (configResourceCollection) { + java.lang.String collectionItemName = org.apache.commons.lang3.StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = (((configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + "/") + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath)) + "/") + nestedConfigMetadata.getName(); + } else { + nestedConfigName = (configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/") + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata.class)) { + org.apache.sling.caconfig.management.ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } else if (propertyMetadata.getType().equals(org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata[].class)) { + java.util.Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new org.apache.sling.caconfig.management.ConfigurationData[configDatas.size()])); + } + } + } + } +} + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/metadata.json new file mode 100644 index 000000000..860a71ce7 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 183, + "npe_method": "resolveNestedConfigs", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_181" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/npe.json new file mode 100644 index 000000000..e399a7d06 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_181/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 183, + "npe_method": "resolveNestedConfigs", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_187/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/buggy.java new file mode 100644 index 000000000..eb8afd758 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/buggy.java @@ -0,0 +1,278 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override + public @NotNull ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + Map props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new ValueMapDecorator(props); + } + return effectiveValuesCache; + } + +private void resolveNestedConfigs(java.util.Map props) { + if (configMetadata == null) { + return; + } + for (org.apache.sling.caconfig.spi.metadata.PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + java.lang.String relatedConfigPath = /* NPEX_NULL_EXP */ + resolvedConfigurationResource.getPath(); + java.lang.String nestedConfigName; + if (configResourceCollection) { + java.lang.String collectionItemName = org.apache.commons.lang3.StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = (((configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + "/") + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath)) + "/") + nestedConfigMetadata.getName(); + } else { + nestedConfigName = (configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/") + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata.class)) { + org.apache.sling.caconfig.management.ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } else if (propertyMetadata.getType().equals(org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata[].class)) { + java.util.Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new org.apache.sling.caconfig.management.ConfigurationData[configDatas.size()])); + } + } + } +} + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/metadata.json new file mode 100644 index 000000000..236f7e8e0 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 188, + "npe_method": "resolveNestedConfigs", + "deref_field": "resolvedConfigurationResource", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_187" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/npe.json new file mode 100644 index 000000000..75067edd4 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_187/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 188, + "npe_method": "resolveNestedConfigs", + "deref_field": "resolvedConfigurationResource", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_216/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/buggy.java new file mode 100644 index 000000000..8e68eb575 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/buggy.java @@ -0,0 +1,269 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override + public @NotNull ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + Map props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new ValueMapDecorator(props); + } + return effectiveValuesCache; + } + + private void resolveNestedConfigs(Map props) { + if (configMetadata == null) { + return; + } + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + String relatedConfigPath = resolvedConfigurationResource != null ? resolvedConfigurationResource.getPath() : null; + String nestedConfigName; + if (configResourceCollection) { + String collectionItemName = StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + + "/" + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath) + + "/" + nestedConfigMetadata.getName(); + } + else { + nestedConfigName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(ConfigurationMetadata.class)) { + ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } + else if (propertyMetadata.getType().equals(ConfigurationMetadata[].class)) { + Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new ConfigurationData[configDatas.size()])); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override +public org.apache.sling.caconfig.management.ValueInfo getValueInfo(java.lang.String propertyName) { + org.apache.sling.caconfig.spi.metadata.PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + java.lang.Object value; + java.lang.Object effectiveValue; + { + java.lang.Class type = /* NPEX_NULL_EXP */ + propertyMetadata.getType(); + if (type == org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata.class) { + type = org.apache.sling.caconfig.management.ConfigurationData.class; + } else if (type == org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata[].class) { + type = org.apache.sling.caconfig.management.ConfigurationData[].class; + } else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + return new org.apache.sling.caconfig.management.impl.ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, resolvedConfigurationResource, writebackConfigurationResource, configurationResourceInheritanceChain, contextResource, configName, configurationOverrideMultiplexer, isAllOverridden); +} + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/metadata.json new file mode 100644 index 000000000..adfac711b --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 218, + "npe_method": "getValueInfo", + "deref_field": "propertyMetadata", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_216" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/npe.json new file mode 100644 index 000000000..2402060dc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_216/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 218, + "npe_method": "getValueInfo", + "deref_field": "propertyMetadata", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_254/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/buggy.java new file mode 100644 index 000000000..7f1b474c8 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/buggy.java @@ -0,0 +1,279 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override + public @NotNull ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + Map props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new ValueMapDecorator(props); + } + return effectiveValuesCache; + } + + private void resolveNestedConfigs(Map props) { + if (configMetadata == null) { + return; + } + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + String relatedConfigPath = resolvedConfigurationResource != null ? resolvedConfigurationResource.getPath() : null; + String nestedConfigName; + if (configResourceCollection) { + String collectionItemName = StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + + "/" + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath) + + "/" + nestedConfigMetadata.getName(); + } + else { + nestedConfigName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(ConfigurationMetadata.class)) { + ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } + else if (propertyMetadata.getType().equals(ConfigurationMetadata[].class)) { + Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new ConfigurationData[configDatas.size()])); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + +private org.apache.sling.caconfig.spi.metadata.PropertyMetadata getPropertyMetadata(java.lang.String propertyName) { + { + return /* NPEX_NULL_EXP */ + configMetadata.getPropertyMetadata().get(propertyName); + } +} + + @Override + public boolean isInherited() { + // detect if the whole config or config item was inherited + if (resolvedConfigurationResource != null && resolvedConfigurationResource.getPath() != null) { + if (writebackConfigurationResource == null) { + return true; + } + else { + return !StringUtils.equals(writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; + } + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/metadata.json new file mode 100644 index 000000000..6a4da8a74 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 256, + "npe_method": "getPropertyMetadata", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_254" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/npe.json new file mode 100644 index 000000000..4bb0754e3 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_254/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 256, + "npe_method": "getPropertyMetadata", + "deref_field": "configMetadata", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationDataImpl_266/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/buggy.java new file mode 100644 index 000000000..7a9935401 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/buggy.java @@ -0,0 +1,279 @@ +/* + * 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.caconfig.management.impl; + +import java.lang.reflect.Array; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ValueInfo; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceStrategy2; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; + +final class ConfigurationDataImpl implements ConfigurationData { + + private final ConfigurationMetadata configMetadata; + private final Resource resolvedConfigurationResource; + private final Resource writebackConfigurationResource; + private final List configurationResourceInheritanceChain; + private final Resource contextResource; + private final String configName; + private final ConfigurationManager configurationManager; + private final ConfigurationManagementSettings configurationManagementSettings; + private final ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + private final ConfigurationPersistenceStrategy2 configurationPersistenceStrategy; + private final boolean configResourceCollection; + private final String collectionItemName; + private final boolean isAllOverridden; + + private Set propertyNamesCache; + private ValueMap valuesCache; + private ValueMap effectiveValuesCache; + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource resolvedConfigurationResource, Resource writebackConfigurationResource, + Iterator configurationResourceInheritanceChain, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection, String collectionItemName) { + this.configMetadata = configMetadata; + this.resolvedConfigurationResource = resolvedConfigurationResource; + this.writebackConfigurationResource = writebackConfigurationResource; + this.configurationResourceInheritanceChain = configurationResourceInheritanceChain != null + ? IteratorUtils.toList(configurationResourceInheritanceChain) : null; + this.contextResource = contextResource; + this.configName = configName; + this.configurationManager = configurationManager; + this.configurationManagementSettings = configurationManagementSettings; + this.configurationOverrideMultiplexer = configurationOverrideMultiplexer; + this.configurationPersistenceStrategy = configurationPersistenceStrategy; + this.configResourceCollection = configResourceCollection; + this.collectionItemName = collectionItemName; + this.isAllOverridden = contextResource != null ? configurationOverrideMultiplexer.isAllOverridden(contextResource.getPath(), configName) : false; + } + + public ConfigurationDataImpl(ConfigurationMetadata configMetadata, + Resource contextResource, String configName, + ConfigurationManager configurationManager, + ConfigurationManagementSettings configurationManagementSettings, + ConfigurationOverrideMultiplexer configurationOverrideMultiplexer, + ConfigurationPersistenceStrategy2 configurationPersistenceStrategy, + boolean configResourceCollection) { + this(configMetadata, null, null, null, + contextResource, configName, + configurationManager, + configurationManagementSettings, + configurationOverrideMultiplexer, + configurationPersistenceStrategy, + configResourceCollection, null); + } + + @Override + public @NotNull String getConfigName() { + return configName; + } + + @Override + public String getCollectionItemName() { + return collectionItemName; + } + + @Override + public String getResourcePath() { + if (writebackConfigurationResource != null) { + return writebackConfigurationResource.getPath(); + } + if (resolvedConfigurationResource != null) { + return resolvedConfigurationResource.getPath(); + } + return null; + } + + @Override + public @NotNull Set getPropertyNames() { + if (propertyNamesCache == null) { + propertyNamesCache = new LinkedHashSet<>(); + if (configMetadata != null) { + propertyNamesCache.addAll(configMetadata.getPropertyMetadata().keySet()); + } + if (resolvedConfigurationResource != null) { + propertyNamesCache.addAll(new TreeSet<>(ResourceUtil.getValueMap(resolvedConfigurationResource).keySet())); + } + PropertiesFilterUtil.removeIgnoredProperties(propertyNamesCache, configurationManagementSettings); + } + return propertyNamesCache; + } + + @Override + public @NotNull ValueMap getValues() { + if (valuesCache == null) { + Map props = new HashMap<>(); + if (writebackConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(writebackConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + valuesCache = new ValueMapDecorator(props); + } + return valuesCache; + } + + @Override + public @NotNull ValueMap getEffectiveValues() { + if (effectiveValuesCache == null) { + Map props = new HashMap<>(); + if (configMetadata != null) { + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.getDefaultValue() != null) { + props.put(propertyMetadata.getName(), propertyMetadata.getDefaultValue()); + } + } + } + if (resolvedConfigurationResource != null) { + props.putAll(ResourceUtil.getValueMap(resolvedConfigurationResource)); + } + PropertiesFilterUtil.removeIgnoredProperties(props, configurationManagementSettings); + resolveNestedConfigs(props); + effectiveValuesCache = new ValueMapDecorator(props); + } + return effectiveValuesCache; + } + + private void resolveNestedConfigs(Map props) { + if (configMetadata == null) { + return; + } + for (PropertyMetadata propertyMetadata : configMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + String relatedConfigPath = resolvedConfigurationResource != null ? resolvedConfigurationResource.getPath() : null; + String nestedConfigName; + if (configResourceCollection) { + String collectionItemName = StringUtils.defaultString(getCollectionItemName(), "newItem"); + nestedConfigName = configurationPersistenceStrategy.getCollectionParentConfigName(configName, relatedConfigPath) + + "/" + configurationPersistenceStrategy.getCollectionItemConfigName(collectionItemName, relatedConfigPath) + + "/" + nestedConfigMetadata.getName(); + } + else { + nestedConfigName = configurationPersistenceStrategy.getConfigName(configName, relatedConfigPath) + "/" + nestedConfigMetadata.getName(); + } + if (propertyMetadata.getType().equals(ConfigurationMetadata.class)) { + ConfigurationData configData = configurationManager.getConfiguration(contextResource, nestedConfigName); + props.put(propertyMetadata.getName(), configData); + } + else if (propertyMetadata.getType().equals(ConfigurationMetadata[].class)) { + Collection configDatas = configurationManager.getConfigurationCollection(contextResource, nestedConfigName).getItems(); + props.put(propertyMetadata.getName(), configDatas.toArray(new ConfigurationData[configDatas.size()])); + } + } + } + } + + @SuppressWarnings("unchecked") + @Override + public ValueInfo getValueInfo(String propertyName) { + PropertyMetadata propertyMetadata = getPropertyMetadata(propertyName); + Object value; + Object effectiveValue; + if (propertyMetadata != null) { + Class type = propertyMetadata.getType(); + if (type == ConfigurationMetadata.class) { + type = ConfigurationData.class; + } + else if (type == ConfigurationMetadata[].class) { + type = ConfigurationData[].class; + } + else { + type = primitiveToWrapper(type); + } + value = getValues().get(propertyName, type); + effectiveValue = getEffectiveValues().get(propertyName, type); + } + else { + value = getValues().get(propertyName); + effectiveValue = getEffectiveValues().get(propertyName); + } + return new ValueInfoImpl(propertyName, value, effectiveValue, propertyMetadata, + resolvedConfigurationResource, + writebackConfigurationResource, + configurationResourceInheritanceChain, + contextResource, + configName, + configurationOverrideMultiplexer, + isAllOverridden); + } + + private Class primitiveToWrapper(Class type) { + if (type.isArray()) { + return Array.newInstance(ClassUtils.primitiveToWrapper(type.getComponentType()), 0).getClass(); + } + else { + return ClassUtils.primitiveToWrapper(type); + } + } + + private PropertyMetadata getPropertyMetadata(String propertyName) { + if (configMetadata == null) { + return null; + } + else { + return configMetadata.getPropertyMetadata().get(propertyName); + } + } + + @Override +public boolean isInherited() { + // detect if the whole config or config item was inherited + if ((resolvedConfigurationResource != null) && (resolvedConfigurationResource.getPath() != null)) { + { + return !org.apache.commons.lang3.StringUtils.equals(/* NPEX_NULL_EXP */ + writebackConfigurationResource.getPath(), resolvedConfigurationResource.getPath()); + } + } + return false; +} + + @Override + public boolean isOverridden() { + return isAllOverridden; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/metadata.json new file mode 100644 index 000000000..27100130a --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266", + "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/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 268, + "npe_method": "isInherited", + "deref_field": "writebackConfigurationResource", + "npe_class": "ConfigurationDataImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationDataImpl_266" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/npe.json new file mode 100644 index 000000000..d5df64b87 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationDataImpl_266/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationDataImpl.java", + "line": 268, + "npe_method": "isInherited", + "deref_field": "writebackConfigurationResource", + "npe_class": "ConfigurationDataImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/buggy.java new file mode 100644 index 000000000..d8b16551d --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/buggy.java @@ -0,0 +1,90 @@ +/* + * 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.caconfig.impl; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.collections4.ResettableListIterator; +import org.apache.commons.collections4.iterators.ListIteratorWrapper; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationInheritanceStrategyMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Detects all {@link ConfigurationInheritanceStrategy} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationInheritanceStrategyMultiplexer.class, +reference={ + @Reference(name="configurationInheritanceStrategy", service=ConfigurationInheritanceStrategy.class, + bind="bindConfigurationInheritanceStrategy", unbind="unbindConfigurationInheritanceStrategy", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationInheritanceStrategyMultiplexerImpl implements ConfigurationInheritanceStrategyMultiplexer { + + private RankedServices items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationInheritanceStrategy(ConfigurationInheritanceStrategy item, Map props) { + items.bind(item, props); + } + + protected void unbindConfigurationInheritanceStrategy(ConfigurationInheritanceStrategy item, Map props) { + items.unbind(item, props); + } + + /** + * Get result from first strategy implementation that has an answer. + */ + @SuppressWarnings("unchecked") + @Override +/** + * Get result from first strategy implementation that has an answer. + */ +public org.apache.sling.api.resource.Resource getResource(@org.jetbrains.annotations.NotNull +java.util.Iterator configResources) { + java.util.List itemList = items.getList(); + if (itemList.isEmpty()) { + return null; + } else if (itemList.size() == 1) { + return itemList.get(0).getResource(configResources); + } else { + org.apache.commons.collections4.ResettableListIterator resettableConfigResources = new org.apache.commons.collections4.iterators.ListIteratorWrapper(configResources); + for (org.apache.sling.caconfig.spi.ConfigurationInheritanceStrategy item : items) { + org.apache.sling.api.resource.Resource result = item.getResource(resettableConfigResources); + { + return /* NPEX_NULL_EXP */ + result; + } + } + return null; + } +} + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/metadata.json new file mode 100644 index 000000000..3bc832069 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79", + "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/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImpl.java", + "line": 83, + "npe_method": "getResource", + "deref_field": "result", + "npe_class": "ConfigurationInheritanceStrategyMultiplexerImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationInheritanceStrategyMultiplexerImpl_79" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/npe.json new file mode 100644 index 000000000..1686073f0 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationInheritanceStrategyMultiplexerImpl_79/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/ConfigurationInheritanceStrategyMultiplexerImpl.java", + "line": 83, + "npe_method": "getResource", + "deref_field": "result", + "npe_class": "ConfigurationInheritanceStrategyMultiplexerImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationManagerImpl_95/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/buggy.java new file mode 100644 index 000000000..a6bd85ba5 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/buggy.java @@ -0,0 +1,413 @@ +/* + * 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.caconfig.management.impl; + +import static org.apache.sling.caconfig.impl.ConfigurationNameConstants.CONFIGS_BUCKET_NAME; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.ResettableIterator; +import org.apache.commons.collections4.Transformer; +import org.apache.commons.collections4.iterators.ListIteratorWrapper; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.caconfig.management.ConfigurationCollectionData; +import org.apache.sling.caconfig.management.ConfigurationData; +import org.apache.sling.caconfig.management.ConfigurationManagementSettings; +import org.apache.sling.caconfig.management.ConfigurationManager; +import org.apache.sling.caconfig.management.ConfigurationResourceResolverConfig; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationInheritanceStrategyMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationPersistenceStrategyMultiplexer; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationResourceResolvingStrategyMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil; +import org.apache.sling.caconfig.resource.impl.util.MapUtil; +import org.apache.sling.caconfig.spi.ConfigurationCollectionPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistData; +import org.apache.sling.caconfig.spi.ConfigurationPersistenceException; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(service = ConfigurationManager.class) +public class ConfigurationManagerImpl implements ConfigurationManager { + + @Reference + private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy; + @Reference + private ConfigurationMetadataProviderMultiplexer configurationMetadataProvider; + @Reference + private ConfigurationPersistenceStrategyMultiplexer configurationPersistenceStrategy; + @Reference + private ConfigurationInheritanceStrategyMultiplexer configurationInheritanceStrategy; + @Reference + private ConfigurationOverrideMultiplexer configurationOverrideMultiplexer; + @Reference + private ConfigurationResourceResolverConfig configurationResourceResolverConfig; + @Reference + private ConfigurationManagementSettings configurationManagementSettings; + + private static final Logger log = LoggerFactory.getLogger(ConfigurationManagerImpl.class); + + @SuppressWarnings("unchecked") + @Override +public org.apache.sling.caconfig.management.ConfigurationData getConfiguration(@org.jetbrains.annotations.NotNull +org.apache.sling.api.resource.Resource resource, @org.jetbrains.annotations.NotNull +java.lang.String configName) { + org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil.ensureValidConfigName(configName); + if (org.apache.sling.caconfig.management.impl.ConfigurationManagerImpl.log.isDebugEnabled()) { + org.apache.sling.caconfig.management.impl.ConfigurationManagerImpl.log.debug("Get configuration for context path {}, name '{}'", resource.getPath(), configName); + } + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); + org.apache.sling.api.resource.Resource configResource = null; + java.util.Iterator configResourceInheritanceChain = configurationResourceResolvingStrategy.getResourceInheritanceChain(resource, configurationResourceResolverConfig.configBucketNames(), configName); + { + org.apache.commons.collections4.ResettableIterator resettableConfigResourceInheritanceChain = new org.apache.commons.collections4.iterators.ListIteratorWrapper(/* NPEX_NULL_EXP */ + configResourceInheritanceChain); + configResource = applyPersistenceAndInheritance(resource.getPath(), configName, resettableConfigResourceInheritanceChain, false, resource.getResourceResolver()); + if (configResource != null) { + // get writeback resource for "reverse inheritance detection" + org.apache.sling.api.resource.Resource writebackConfigResource = null; + java.lang.String writebackConfigResourcePath = null; + for (java.lang.String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + writebackConfigResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, configBucketName, configName); + if (writebackConfigResourcePath != null) { + writebackConfigResource = resource.getResourceResolver().getResource(writebackConfigResourcePath); + if (writebackConfigResource != null) { + writebackConfigResource = configurationPersistenceStrategy.getResource(writebackConfigResource); + break; + } + } + } + if (org.apache.sling.caconfig.management.impl.ConfigurationManagerImpl.log.isTraceEnabled()) { + org.apache.sling.caconfig.management.impl.ConfigurationManagerImpl.log.trace(((((((("+ Found config resource for context path " + resource.getPath()) + ": ") + configResource.getPath()) + " ") + org.apache.sling.caconfig.resource.impl.util.MapUtil.traceOutput(configResource.getValueMap())) + ", ") + "writeback config resource: ") + writebackConfigResourcePath); + } + resettableConfigResourceInheritanceChain.reset(); + return new org.apache.sling.caconfig.management.impl.ConfigurationDataImpl(configMetadata, configResource, writebackConfigResource, applyPersistence(resettableConfigResourceInheritanceChain, false), resource, configName, this, configurationManagementSettings, configurationOverrideMultiplexer, configurationPersistenceStrategy, false, null); + } + } + if (configMetadata != null) { + // if no config resource found still check for overrides + configResource = configurationOverrideMultiplexer.overrideProperties(resource.getPath(), configName, ((org.apache.sling.api.resource.Resource) (null)), resource.getResourceResolver()); + if (configResource != null) { + return new org.apache.sling.caconfig.management.impl.ConfigurationDataImpl(configMetadata, configResource, null, null, resource, configName, this, configurationManagementSettings, configurationOverrideMultiplexer, configurationPersistenceStrategy, false, null); + } + // if no config resource found but config metadata exist return empty config data with default values + return new org.apache.sling.caconfig.management.impl.ConfigurationDataImpl(configMetadata, resource, configName, this, configurationManagementSettings, configurationOverrideMultiplexer, configurationPersistenceStrategy, false); + } + return null; +} + + @SuppressWarnings("unchecked") + @Override + public@NotNull ConfigurationCollectionData getConfigurationCollection(@NotNull Resource resource, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + if (log.isDebugEnabled()) { + log.debug("Get configuration collection for context path {}, name '{}'", resource.getPath(), configName); + } + ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); + List configData = new ArrayList<>(); + + // get all possible colection parent config names + Collection collectionParentConfigNames = configurationPersistenceStrategy.getAllCollectionParentConfigNames(configName); + + // get configuration resource items + List> configResourceInheritanceChains = new ArrayList<>(); + for (String collectionParentConfigName : collectionParentConfigNames) { + Collection> result = configurationResourceResolvingStrategy + .getResourceCollectionInheritanceChain(resource, configurationResourceResolverConfig.configBucketNames(), collectionParentConfigName); + if (result != null) { + configResourceInheritanceChains.addAll(result); + } + } + + String writebackConfigResourceCollectionParentPath = null; + for (Iterator configResourceInheritanceChain : configResourceInheritanceChains) { + ResettableIterator resettableConfigResourceInheritanceChain = new ListIteratorWrapper(configResourceInheritanceChain); + Resource configResource = applyPersistenceAndInheritance(resource.getPath(), configName, resettableConfigResourceInheritanceChain, true, resource.getResourceResolver()); + resettableConfigResourceInheritanceChain.reset(); + Resource untransformedConfigResource = (Resource)resettableConfigResourceInheritanceChain.next(); + if (configResource != null) { + // get writeback resource for "reverse inheritance detection" + Resource writebackConfigResource = null; + + String writebackConfigResourcePath = null; + for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + writebackConfigResourceCollectionParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, configBucketName, configName); + if (writebackConfigResourceCollectionParentPath != null) { + writebackConfigResourceCollectionParentPath = configurationPersistenceStrategy.getCollectionParentResourcePath(writebackConfigResourceCollectionParentPath); + writebackConfigResourcePath = writebackConfigResourceCollectionParentPath + "/" + untransformedConfigResource.getName(); + writebackConfigResource = configResource.getResourceResolver().getResource(writebackConfigResourcePath); + if (writebackConfigResource != null) { + writebackConfigResource = configurationPersistenceStrategy.getCollectionItemResource(writebackConfigResource); + break; + } + } + } + + if (log.isTraceEnabled()) { + log.trace("+ Found config resource for context path " + resource.getPath() + ": " + configResource.getPath() + " " + + MapUtil.traceOutput(configResource.getValueMap()) + ", " + + "writeback config resource: " + writebackConfigResourcePath); + } + resettableConfigResourceInheritanceChain.reset(); + configData.add(new ConfigurationDataImpl(configMetadata, configResource, writebackConfigResource, + applyPersistence(resettableConfigResourceInheritanceChain, true), + resource, configName, this, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, + true, untransformedConfigResource.getName())); + } + } + // fallback for writeback path detection when no configuration resources does exist yet + if (writebackConfigResourceCollectionParentPath == null) { + for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + writebackConfigResourceCollectionParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, configBucketName, configName); + if (writebackConfigResourceCollectionParentPath != null) { + break; + } + } + } + + // get properties of parent resource of the current level + Map resourceCollectionParentProps = null; + if (writebackConfigResourceCollectionParentPath != null) { + Resource writebackConfigResourceCollectionParent = resource.getResourceResolver().getResource(writebackConfigResourceCollectionParentPath); + if (writebackConfigResourceCollectionParent != null) { + for (String resourceName : configurationManagementSettings.getConfigCollectionPropertiesResourceNames()) { + Resource propsResource = writebackConfigResourceCollectionParent.getChild(resourceName); + if (propsResource != null) { + resourceCollectionParentProps = propsResource.getValueMap(); + break; + } + } + } + } + + return new ConfigurationCollectionDataImpl( + configName, + configData, + writebackConfigResourceCollectionParentPath, + resourceCollectionParentProps, + configurationManagementSettings + ); + } + + @SuppressWarnings("unchecked") + private Iterator applyPersistence(final Iterator configResourceInheritanceChain, final boolean isCollection) { + if (configResourceInheritanceChain == null) { + return null; + } + return IteratorUtils.transformedIterator(configResourceInheritanceChain, + new Transformer() { + @Override + public Object transform(Object input) { + if (isCollection) { + return configurationPersistenceStrategy.getCollectionItemResource((Resource)input); + } + else { + return configurationPersistenceStrategy.getResource((Resource)input); + } + } + }); + } + + private Resource applyPersistenceAndInheritance(String contextPath, String configName, Iterator configResourceInheritanceChain, + boolean isCollection, ResourceResolver resourceResolver) { + if (configResourceInheritanceChain == null) { + return null; + } + + // apply configuration persistence transformation + Iterator transformedConfigResources = applyPersistence(configResourceInheritanceChain, isCollection); + + // apply resource inheritance + Resource configResource = configurationInheritanceStrategy.getResource(transformedConfigResources); + + // apply overrides + return configurationOverrideMultiplexer.overrideProperties(contextPath, configName, configResource, resourceResolver); + } + + @Override + public void persistConfiguration(@NotNull Resource resource, @NotNull String configName, @NotNull ConfigurationPersistData data) { + ConfigNameUtil.ensureValidConfigName(configName); + String configResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, CONFIGS_BUCKET_NAME, configName); + if (configResourcePath == null) { + throw new ConfigurationPersistenceException("Unable to persist configuration: Configuration resolving strategy returned no path."); + } + if (log.isDebugEnabled()) { + log.debug("Persist configuration for context path {}, name '{}' to {}", resource.getPath(), configName, configResourcePath); + } + if (!configurationPersistenceStrategy.persistConfiguration(resource.getResourceResolver(), configResourcePath, data)) { + throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); + } + } + + @Override + public void persistConfigurationCollection(@NotNull Resource resource, @NotNull String configName, @NotNull ConfigurationCollectionPersistData data) { + ConfigNameUtil.ensureValidConfigName(configName); + String configResourceParentPath = configurationResourceResolvingStrategy.getResourceCollectionParentPath(resource, CONFIGS_BUCKET_NAME, configName); + if (configResourceParentPath == null) { + throw new ConfigurationPersistenceException("Unable to persist configuration collection: Configuration resolving strategy returned no parent path."); + } + if (log.isDebugEnabled()) { + log.debug("Persist configuration collection for context path {}, name '{}' to {}", resource.getPath(), configName, configResourceParentPath); + } + if (!configurationPersistenceStrategy.persistConfigurationCollection(resource.getResourceResolver(), configResourceParentPath, data)) { + throw new ConfigurationPersistenceException("Unable to persist configuration: No persistence strategy found."); + } + } + + @Override + public ConfigurationData newCollectionItem(@NotNull Resource resource, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + ConfigurationMetadata configMetadata = getConfigurationMetadata(configName); + if (configMetadata != null) { + return new ConfigurationDataImpl(configMetadata, + resource, configName, this, configurationManagementSettings, + configurationOverrideMultiplexer, configurationPersistenceStrategy, true); + } + return null; + } + + @Override + public void deleteConfiguration(@NotNull Resource resource, @NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + + // try to delete from all config bucket names + boolean foundAnyPath = false; + for (String configBucketName : configurationResourceResolverConfig.configBucketNames()) { + String configResourcePath = configurationResourceResolvingStrategy.getResourcePath(resource, configBucketName, configName); + if (configResourcePath != null) { + foundAnyPath = true; + if (log.isDebugEnabled()) { + log.debug("Delete configuration for context path {}, name '{}' from {}", resource.getPath(), configName, configResourcePath); + } + if (!configurationPersistenceStrategy.deleteConfiguration(resource.getResourceResolver(), configResourcePath)) { + throw new ConfigurationPersistenceException("Unable to delete configuration: No persistence strategy found."); + } + } + } + if (!foundAnyPath) { + throw new ConfigurationPersistenceException("Unable to delete configuration: Configuration resolving strategy returned no path."); + } + } + + @Override + public @NotNull SortedSet getConfigurationNames() { + return configurationMetadataProvider.getConfigurationNames(); + } + + @Override + public ConfigurationMetadata getConfigurationMetadata(@NotNull String configName) { + ConfigNameUtil.ensureValidConfigName(configName); + ConfigurationMetadata metadata = configurationMetadataProvider.getConfigurationMetadata(configName); + if (metadata != null) { + log.trace("+ Configuration metadata found for: {}", configName); + return metadata; + } + + // if no metadata found with direct match try to resolve nested configuration metadata references + for (String partialConfigName : ConfigNameUtil.getAllPartialConfigNameVariations(configName)) { + ConfigurationMetadata partialConfigMetadata = getConfigurationMetadata(partialConfigName); + if (partialConfigMetadata != null) { + ConfigurationMetadata nestedConfigMetadata = getNestedConfigurationMetadata(partialConfigMetadata, configName, partialConfigName); + if (nestedConfigMetadata != null) { + log.trace("+ Nested configuration metadata found for: {}", configName); + return nestedConfigMetadata; + } + } + } + + log.trace("- No configuration metadata found for: {}", configName); + return null; + } + + private ConfigurationMetadata getNestedConfigurationMetadata(ConfigurationMetadata configMetadata, String configName, String partialConfigName) { + if (StringUtils.startsWith(configName, partialConfigName + "/")) { + + // depending on different persistence strategies config names can be transformed differently - try all combinations here + Set prefixesToRemove = new LinkedHashSet<>(); + if (configMetadata.isCollection()) { + String collectionItemName = StringUtils.substringBefore(StringUtils.substringAfter(configName, partialConfigName + "/"), "/"); + for (String collectionParentConfigName : configurationPersistenceStrategy.getAllCollectionParentConfigNames(partialConfigName)) { + for (String collectionItemConfigName : configurationPersistenceStrategy.getAllCollectionItemConfigNames(collectionItemName)) { + prefixesToRemove.add(collectionParentConfigName + "/" + collectionItemConfigName + "/"); + } + } + } + else { + for (String configNameItem : configurationPersistenceStrategy.getAllConfigNames(partialConfigName)) { + prefixesToRemove.add(configNameItem + "/"); + } + } + + for (String prefixToRemove : prefixesToRemove) { + String remainingConfigName = StringUtils.substringAfter(configName, prefixToRemove); + // try direct match + ConfigurationMetadata nestedConfigMetadata = getNestedConfigurationMetadataFromProperty(configMetadata, remainingConfigName); + if (nestedConfigMetadata != null) { + return nestedConfigMetadata; + } + // try to find partial match for deeper nestings + for (String partialRemainingConfigName : ConfigNameUtil.getAllPartialConfigNameVariations(remainingConfigName)) { + ConfigurationMetadata partialConfigMetadata = getNestedConfigurationMetadataFromProperty(configMetadata, partialRemainingConfigName); + if (partialConfigMetadata != null) { + nestedConfigMetadata = getNestedConfigurationMetadata(partialConfigMetadata, remainingConfigName, partialRemainingConfigName); + if (nestedConfigMetadata != null) { + return nestedConfigMetadata; + } + } + } + } + } + return null; + } + + private ConfigurationMetadata getNestedConfigurationMetadataFromProperty(ConfigurationMetadata partialConfigMetadata, String configName) { + for (PropertyMetadata propertyMetadata : partialConfigMetadata.getPropertyMetadata().values()) { + if (propertyMetadata.isNestedConfiguration()) { + ConfigurationMetadata nestedConfigMetadata = propertyMetadata.getConfigurationMetadata(); + if (StringUtils.equals(configName, nestedConfigMetadata.getName())) { + return nestedConfigMetadata; + } + } + } + return null; + } + + @Override + public String getPersistenceResourcePath(@NotNull String configResourcePath) { + return configurationPersistenceStrategy.getResourcePath(configResourcePath); + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/metadata.json new file mode 100644 index 000000000..73354aab4 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95", + "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/caconfig/management/impl/ConfigurationManagerImpl.java", + "line": 96, + "npe_method": "getConfiguration", + "deref_field": "configResourceInheritanceChain", + "npe_class": "ConfigurationManagerImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationManagerImpl_95" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/npe.json new file mode 100644 index 000000000..114725cf3 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationManagerImpl_95/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/ConfigurationManagerImpl.java", + "line": 96, + "npe_method": "getConfiguration", + "deref_field": "configResourceInheritanceChain", + "npe_class": "ConfigurationManagerImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationMetadataPrinter_57/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/buggy.java new file mode 100644 index 000000000..f1559f8dc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/buggy.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sling.caconfig.management.impl.console; + +import java.io.PrintWriter; +import java.lang.reflect.Array; + +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; + +/** + * Print configuration metadata provided by a {@link ConfigurationMetadata}. + */ +class ConfigurationMetadataPrinter implements ServiceConfigurationPrinter { + + @Override +public void printConfiguration(java.io.PrintWriter pw, org.osgi.framework.ServiceReference serviceReference, org.osgi.framework.BundleContext bundleContext) { + org.apache.sling.caconfig.spi.ConfigurationMetadataProvider service = bundleContext.getService(serviceReference); + for (java.lang.String configName : service.getConfigurationNames()) { + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata metadata = service.getConfigurationMetadata(configName); + if (metadata == null) { + continue; + } + pw.print(org.apache.sling.caconfig.management.impl.console.ServiceConfigurationPrinter.INDENT); + pw.print(org.apache.sling.caconfig.management.impl.console.ServiceConfigurationPrinter.BULLET); + pw.println(metadata.getName()); + for (org.apache.sling.caconfig.spi.metadata.PropertyMetadata property : metadata.getPropertyMetadata().values()) { + pw.print(org.apache.sling.caconfig.management.impl.console.ServiceConfigurationPrinter.INDENT_2); + pw.print(org.apache.sling.caconfig.management.impl.console.ServiceConfigurationPrinter.BULLET); + pw.print(property.getName()); + pw.print("("); + pw.print(property.getType().getSimpleName()); + pw.print(")"); + { + pw.print(" = "); + printValue(pw, /* NPEX_NULL_EXP */ + property.getDefaultValue()); + } + pw.println(); + } + } + bundleContext.ungetService(serviceReference); +} + + private void printValue(PrintWriter pw, Object value) { + if (value.getClass().isArray()) { + for (int i=0; i 0) { + pw.print(", "); + } + printValue(pw, Array.get(value, i)); + } + } + else { + pw.print(value); + } + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/metadata.json new file mode 100644 index 000000000..aed2ff75c --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57", + "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/caconfig/management/impl/console/ConfigurationMetadataPrinter.java", + "line": 56, + "npe_method": "printConfiguration", + "deref_field": "getDefaultValue", + "npe_class": "ConfigurationMetadataPrinter", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationMetadataPrinter_57" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/npe.json new file mode 100644 index 000000000..77d41f6e8 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataPrinter_57/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/management/impl/console/ConfigurationMetadataPrinter.java", + "line": 56, + "npe_method": "printConfiguration", + "deref_field": "getDefaultValue", + "npe_class": "ConfigurationMetadataPrinter" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/buggy.java new file mode 100644 index 000000000..d8cff351b --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/buggy.java @@ -0,0 +1,90 @@ +/* + * 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.caconfig.impl.metadata; + +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.sling.caconfig.management.multiplexer.ConfigurationMetadataProviderMultiplexer; +import org.apache.sling.caconfig.spi.ConfigurationMetadataProvider; +import org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.jetbrains.annotations.NotNull; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; + +/** + * Detects all {@link ConfigurationMetadataProvider} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationMetadataProviderMultiplexer.class, +reference={ + @Reference(name="configurationMetadataProvider", service=ConfigurationMetadataProvider.class, + bind="bindConfigurationMetadataProvider", unbind="unbindConfigurationMetadataProvider", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationMetadataProviderMultiplexerImpl implements ConfigurationMetadataProviderMultiplexer { + + private RankedServices items = new RankedServices<>(Order.DESCENDING); + + protected void bindConfigurationMetadataProvider(ConfigurationMetadataProvider configurationMetadataProvider, Map props) { + items.bind(configurationMetadataProvider, props); + } + + protected void unbindConfigurationMetadataProvider(ConfigurationMetadataProvider configurationMetadataProvider, Map props) { + items.unbind(configurationMetadataProvider, props); + } + + /** + * Merge configuration names from all providers. + */ + @Override + public @NotNull SortedSet getConfigurationNames() { + SortedSet configNames = new TreeSet<>(); + for (ConfigurationMetadataProvider item : items) { + configNames.addAll(item.getConfigurationNames()); + } + return configNames; + } + + /** + * Get configuration metadata from first provider (ranking priority) that has an answer. + */ + @Override +/** + * Get configuration metadata from first provider (ranking priority) that has an answer. + */ +public org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata getConfigurationMetadata(java.lang.String configName) { + for (org.apache.sling.caconfig.spi.ConfigurationMetadataProvider item : items) { + org.apache.sling.caconfig.spi.metadata.ConfigurationMetadata configMetadata = item.getConfigurationMetadata(configName); + { + return /* NPEX_NULL_EXP */ + configMetadata; + } + } + return null; +} + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/metadata.json new file mode 100644 index 000000000..cbe72d49e --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79", + "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/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImpl.java", + "line": 84, + "npe_method": "getConfigurationMetadata", + "deref_field": "configMetadata", + "npe_class": "ConfigurationMetadataProviderMultiplexerImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationMetadataProviderMultiplexerImpl_79" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/npe.json new file mode 100644 index 000000000..192a7dc73 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationMetadataProviderMultiplexerImpl_79/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/metadata/ConfigurationMetadataProviderMultiplexerImpl.java", + "line": 84, + "npe_method": "getConfigurationMetadata", + "deref_field": "configMetadata", + "npe_class": "ConfigurationMetadataProviderMultiplexerImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/buggy.java new file mode 100644 index 000000000..b701e7539 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/buggy.java @@ -0,0 +1,161 @@ +/* + * 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.caconfig.impl.override; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.SyntheticResource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.impl.ConfigurationResourceWrapper; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.MapUtil; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.apache.sling.commons.osgi.RankedServices.ChangeListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Detects all {@link ConfigurationOverrideProvider} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationOverrideMultiplexer.class, +reference={ + @Reference(name="configurationOverrideProvider", service=ConfigurationOverrideProvider.class, + bind="bindConfigurationOverrideProvider", unbind="unbindConfigurationOverrideProvider", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationOverrideMultiplexerImpl implements ConfigurationOverrideMultiplexer, ChangeListener { + + private RankedServices items = new RankedServices<>(Order.DESCENDING, this); + private volatile Collection allOverrides = Collections.emptyList(); + + private static final Logger log = LoggerFactory.getLogger(ConfigurationOverrideMultiplexerImpl.class); + + protected void bindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map props) { + items.bind(item, props); + } + + protected void unbindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map props) { + items.unbind(item, props); + } + + @Override + public boolean isAllOverridden(@NotNull String contextPath, @NotNull String configName) { + for (OverrideItem override : allOverrides) { + if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) { + if (override.isAllProperties()) { + return true; + } + } + } + return false; + } + + @Override + public Map overrideProperties(@NotNull String contextPath, @NotNull String configName, @NotNull Map properties) { + if (allOverrides.size() == 0) { + return null; + } + boolean anyMatch = false; + Map overrideProperties = new HashMap<>(properties); + + for (OverrideItem override : allOverrides) { + if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) { + if (override.isAllProperties()) { + overrideProperties.clear(); + } + overrideProperties.putAll(override.getProperties()); + anyMatch = true; + } + } + + if (anyMatch) { + return overrideProperties; + } + else { + return null; + } + } + + @Override + public Resource overrideProperties(@NotNull String contextPath, @NotNull String configName, @Nullable Resource configResource) { + if (configResource == null) { + return null; + } + return overrideProperties(contextPath, configName, configResource, configResource.getResourceResolver()); + } + + @Override +public org.apache.sling.api.resource.Resource overrideProperties(@org.jetbrains.annotations.NotNull +java.lang.String contextPath, @org.jetbrains.annotations.NotNull +java.lang.String configName, @org.jetbrains.annotations.Nullable +org.apache.sling.api.resource.Resource configResource, @org.jetbrains.annotations.NotNull +org.apache.sling.api.resource.ResourceResolver resourceResolver) { + java.util.Map overrideProperties = overrideProperties(contextPath, configName, /* NPEX_NULL_EXP */ + configResource.getValueMap()); + if (overrideProperties == null) { + return configResource; + } + org.apache.sling.api.resource.Resource configResourceToUse = configResource; + if (configResourceToUse == null) { + // build synthetic resource if override properties exist + configResourceToUse = new org.apache.sling.api.resource.SyntheticResource(resourceResolver, ((java.lang.String) (null)), ((java.lang.String) (null))); + } + if (org.apache.sling.caconfig.impl.override.ConfigurationOverrideMultiplexerImpl.log.isTraceEnabled()) { + org.apache.sling.caconfig.impl.override.ConfigurationOverrideMultiplexerImpl.log.trace((((((((("! Override properties for context path " + contextPath) + ", name '") + configName) + "', ") + (configResource != null ? "config path " + configResource.getPath() : "no config path")) + ": ") + (configResource != null ? org.apache.sling.caconfig.resource.impl.util.MapUtil.traceOutput(configResource.getValueMap()) : "empty")) + " -> ") + org.apache.sling.caconfig.resource.impl.util.MapUtil.traceOutput(overrideProperties)); + } + return new org.apache.sling.caconfig.impl.ConfigurationResourceWrapper(configResourceToUse, new org.apache.sling.api.wrappers.ValueMapDecorator(overrideProperties)); +} + + /** + * If a provider is added or removed parse and collect all overrides again (to ensure correct overall order is preserved). + */ + @Override + public void changed() { + List overrides = new ArrayList<>(); + for (ConfigurationOverrideProvider item : items) { + Collection itemOverrides = OverrideStringParser.parse(item.getOverrideStrings()); + if (log.isDebugEnabled() && !itemOverrides.isEmpty()) { + log.debug("Override items from " + item.getClass().getName() + ":\n" + StringUtils.join(itemOverrides, "\n")); + } + overrides.addAll(itemOverrides); + } + allOverrides = overrides; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/metadata.json new file mode 100644 index 000000000..2e698bec7 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125", + "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/caconfig/impl/override/ConfigurationOverrideMultiplexerImpl.java", + "line": 130, + "npe_method": "overrideProperties", + "deref_field": "configResource", + "npe_class": "ConfigurationOverrideMultiplexerImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationOverrideMultiplexerImpl_125" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/npe.json new file mode 100644 index 000000000..0bf0622c8 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_125/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImpl.java", + "line": 130, + "npe_method": "overrideProperties", + "deref_field": "configResource", + "npe_class": "ConfigurationOverrideMultiplexerImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/buggy.java new file mode 100644 index 000000000..799901f3f --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/buggy.java @@ -0,0 +1,160 @@ +/* + * 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.caconfig.impl.override; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.SyntheticResource; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.caconfig.impl.ConfigurationResourceWrapper; +import org.apache.sling.caconfig.management.multiplexer.ConfigurationOverrideMultiplexer; +import org.apache.sling.caconfig.resource.impl.util.MapUtil; +import org.apache.sling.caconfig.spi.ConfigurationOverrideProvider; +import org.apache.sling.commons.osgi.Order; +import org.apache.sling.commons.osgi.RankedServices; +import org.apache.sling.commons.osgi.RankedServices.ChangeListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.osgi.service.component.annotations.ReferencePolicyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Detects all {@link ConfigurationOverrideProvider} implementations in the container + * and consolidates their result based on service ranking. + */ +@Component(service = ConfigurationOverrideMultiplexer.class, +reference={ + @Reference(name="configurationOverrideProvider", service=ConfigurationOverrideProvider.class, + bind="bindConfigurationOverrideProvider", unbind="unbindConfigurationOverrideProvider", + cardinality=ReferenceCardinality.MULTIPLE, + policy=ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY) +}) +public class ConfigurationOverrideMultiplexerImpl implements ConfigurationOverrideMultiplexer, ChangeListener { + + private RankedServices items = new RankedServices<>(Order.DESCENDING, this); + private volatile Collection allOverrides = Collections.emptyList(); + + private static final Logger log = LoggerFactory.getLogger(ConfigurationOverrideMultiplexerImpl.class); + + protected void bindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map props) { + items.bind(item, props); + } + + protected void unbindConfigurationOverrideProvider(ConfigurationOverrideProvider item, Map props) { + items.unbind(item, props); + } + + @Override + public boolean isAllOverridden(@NotNull String contextPath, @NotNull String configName) { + for (OverrideItem override : allOverrides) { + if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) { + if (override.isAllProperties()) { + return true; + } + } + } + return false; + } + + @Override + public Map overrideProperties(@NotNull String contextPath, @NotNull String configName, @NotNull Map properties) { + if (allOverrides.size() == 0) { + return null; + } + boolean anyMatch = false; + Map overrideProperties = new HashMap<>(properties); + + for (OverrideItem override : allOverrides) { + if (StringUtils.equals(configName, override.getConfigName()) && override.matchesPath(contextPath)) { + if (override.isAllProperties()) { + overrideProperties.clear(); + } + overrideProperties.putAll(override.getProperties()); + anyMatch = true; + } + } + + if (anyMatch) { + return overrideProperties; + } + else { + return null; + } + } + + @Override + public Resource overrideProperties(@NotNull String contextPath, @NotNull String configName, @Nullable Resource configResource) { + if (configResource == null) { + return null; + } + return overrideProperties(contextPath, configName, configResource, configResource.getResourceResolver()); + } + + @Override +public org.apache.sling.api.resource.Resource overrideProperties(@org.jetbrains.annotations.NotNull +java.lang.String contextPath, @org.jetbrains.annotations.NotNull +java.lang.String configName, @org.jetbrains.annotations.Nullable +org.apache.sling.api.resource.Resource configResource, @org.jetbrains.annotations.NotNull +org.apache.sling.api.resource.ResourceResolver resourceResolver) { + java.util.Map overrideProperties = overrideProperties(contextPath, configName, configResource != null ? configResource.getValueMap() : org.apache.sling.api.resource.ValueMap.EMPTY); + { + org.apache.sling.api.resource.Resource configResourceToUse = configResource; + if (configResourceToUse == null) { + // build synthetic resource if override properties exist + configResourceToUse = new org.apache.sling.api.resource.SyntheticResource(resourceResolver, ((java.lang.String) (null)), ((java.lang.String) (null))); + } + if (org.apache.sling.caconfig.impl.override.ConfigurationOverrideMultiplexerImpl.log.isTraceEnabled()) { + org.apache.sling.caconfig.impl.override.ConfigurationOverrideMultiplexerImpl.log.trace((((((((("! Override properties for context path " + contextPath) + ", name '") + configName) + "', ") + (configResource != null ? "config path " + configResource.getPath() : "no config path")) + ": ") + (configResource != null ? org.apache.sling.caconfig.resource.impl.util.MapUtil.traceOutput(configResource.getValueMap()) : "empty")) + " -> ") + org.apache.sling.caconfig.resource.impl.util.MapUtil.traceOutput(/* NPEX_NULL_EXP */ + overrideProperties)); + } + return new org.apache.sling.caconfig.impl.ConfigurationResourceWrapper(configResourceToUse, new org.apache.sling.api.wrappers.ValueMapDecorator(overrideProperties)); + } +} + + /** + * If a provider is added or removed parse and collect all overrides again (to ensure correct overall order is preserved). + */ + @Override + public void changed() { + List overrides = new ArrayList<>(); + for (ConfigurationOverrideProvider item : items) { + Collection itemOverrides = OverrideStringParser.parse(item.getOverrideStrings()); + if (log.isDebugEnabled() && !itemOverrides.isEmpty()) { + log.debug("Override items from " + item.getClass().getName() + ":\n" + StringUtils.join(itemOverrides, "\n")); + } + overrides.addAll(itemOverrides); + } + allOverrides = overrides; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/metadata.json new file mode 100644 index 000000000..45711fcbb --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126", + "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/caconfig/impl/override/ConfigurationOverrideMultiplexerImpl.java", + "line": 138, + "npe_method": "overrideProperties", + "deref_field": "overrideProperties", + "npe_class": "ConfigurationOverrideMultiplexerImpl", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "ConfigurationOverrideMultiplexerImpl_126" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/npe.json b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/npe.json new file mode 100644 index 000000000..a6d2b030f --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-ConfigurationOverrideMultiplexerImpl_126/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/override/ConfigurationOverrideMultiplexerImpl.java", + "line": 138, + "npe_method": "overrideProperties", + "deref_field": "overrideProperties", + "npe_class": "ConfigurationOverrideMultiplexerImpl" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-OverrideItem_67/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/buggy.java new file mode 100644 index 000000000..97e544496 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/buggy.java @@ -0,0 +1,107 @@ +/* + * 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.caconfig.impl.override; + +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +/** + * Holds override information provided by override providers. + */ +class OverrideItem { + + private final String path; + private final Pattern pathPattern; + private final String configName; + private final Map properties; + private final boolean allProperties; + + public OverrideItem(String path, String configName, + Map properties, boolean allProperties) { + this.path = path; + this.pathPattern = toPathPattern(path); + this.configName = configName; + this.properties = properties; + this.allProperties = allProperties; + } + + private static Pattern toPathPattern(String path) { + if (StringUtils.isBlank(path)) { + return null; + } + return Pattern.compile("^" + Pattern.quote(StringUtils.trim(path)) + "(/.*)?$"); + } + + /** + * @return Path (incl. subtree) to match - or null for all paths + */ + public String getPath() { + return path; + } + + /** + * @param path Path to check + * @return true if path matches + */ +/** + * + * + * @param path + * Path to check + * @return true if path matches + */ +public boolean matchesPath(java.lang.String path) { + { + return /* NPEX_NULL_EXP */ + pathPattern.matcher(path).matches(); + } +} + + /** + * @return Configuration name (may contain a relative hierarchy with "/") + */ + public String getConfigName() { + return configName; + } + + /** + * @return Properties map + */ + public Map getProperties() { + return properties; + } + + /** + * @return If true, all properties for this config name should be replaced + * with those from the map. Otherwise they are merged. + */ + public boolean isAllProperties() { + return allProperties; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/metadata.json new file mode 100644 index 000000000..ca722350b --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-OverrideItem_67", + "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/caconfig/impl/override/OverrideItem.java", + "line": 76, + "npe_method": "matchesPath", + "deref_field": "pathPattern", + "npe_class": "OverrideItem", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "OverrideItem_67" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/npe.json b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/npe.json new file mode 100644 index 000000000..bc098e328 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideItem_67/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/override/OverrideItem.java", + "line": 76, + "npe_method": "matchesPath", + "deref_field": "pathPattern", + "npe_class": "OverrideItem" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-OverrideStringParser_257/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/buggy.java new file mode 100644 index 000000000..b161dc115 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/buggy.java @@ -0,0 +1,298 @@ +/* + * 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.caconfig.impl.override; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonException; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonReaderFactory; +import javax.json.JsonString; +import javax.json.JsonValue; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parses override configuration strings like these: + *
    + *
  • {configName}/{propertyName}={propertyJsonValue}
  • + *
  • {configName}={propertyJsonObject}
  • + *
  • [{contextPath}]{configName}/{propertyName}={propertyJsonValue}
  • + *
  • [{contextPath}]{configName}={propertyJsonObject}
  • + *
+ */ +class OverrideStringParser { + + private static final Logger log = LoggerFactory.getLogger(OverrideStringParser.class); + + private static final Pattern OVERRIDE_PATTERN = Pattern.compile("^(\\[([^\\[\\]=]+)\\])?([^\\[\\]=]+)=(.*)$"); + + private static final JsonReaderFactory JSON_READER_FACTORY = Json.createReaderFactory(Collections.emptyMap()); + + private OverrideStringParser() { + // static method sonly + } + + /** + * Parses a list of override strings from a override provider. + * @param overrideStrings Override strings + * @return Override objects + */ + public static Collection parse(Collection overrideStrings) { + List result = new ArrayList<>(); + + for (String overrideString : overrideStrings) { + + // check if override generic pattern is matched + Matcher matcher = OVERRIDE_PATTERN.matcher(StringUtils.defaultString(overrideString)); + if (!matcher.matches()) { + log.warn("Ignore config override string - invalid syntax: {}", overrideString); + continue; + } + + // get single parts + String path = StringUtils.trim(matcher.group(2)); + String configName = StringUtils.trim(matcher.group(3)); + String value = StringUtils.trim(StringUtils.defaultString(matcher.group(4))); + + OverrideItem item; + try { + // check if value is JSON = defines whole parameter map for a config name + JsonObject json = toJson(value); + if (json != null) { + item = new OverrideItem(path, configName, toMap(json), true); + } + else { + // otherwise it defines a key/value pair in a single line + String propertyName = StringUtils.substringAfterLast(configName, "/"); + if (StringUtils.isEmpty(propertyName)) { + log.warn("Ignore config override string - missing property name: {}", overrideString); + continue; + } + configName = StringUtils.substringBeforeLast(configName, "/"); + Map props = new HashMap<>(); + props.put(propertyName, convertJsonValue(value)); + item = new OverrideItem(path, configName, props, false); + } + } + catch (JsonException ex) { + log.warn("Ignore config override string - invalid JSON syntax ({}): {}", ex.getMessage(), overrideString); + continue; + } + + // validate item + if (!isValid(item, overrideString)) { + continue; + } + + // if item does not contain a full property set try to merge with existing one + if (!item.isAllProperties()) { + boolean foundMatchingItem = false; + for (OverrideItem existingItem : result) { + if (!existingItem.isAllProperties() + && StringUtils.equals(item.getPath(), existingItem.getPath()) + && StringUtils.equals(item.getConfigName(), existingItem.getConfigName())) { + existingItem.getProperties().putAll(item.getProperties()); + foundMatchingItem = true; + break; + } + } + if (foundMatchingItem) { + continue; + } + } + + // add item to result + result.add(item); + } + + return result; + } + + /** + * Try to convert value to JSON object + * @param value Value string + * @return JSON object or null if the string does not start with "{" + * @throws JSONException when JSON parsing failed + */ + private static JsonObject toJson(String value) { + if (!StringUtils.startsWith(value, "{")) { + return null; + } + try (Reader reader = new StringReader(value); + JsonReader jsonReader = JSON_READER_FACTORY.createReader(reader)) { + return jsonReader.readObject(); + } + catch (IOException ex) { + return null; + } + } + + /** + * Convert JSON object to map. + * @param json JSON object + * @return Map (keys/values are not validated) + */ + private static Map toMap(JsonObject json) { + Map props = new HashMap<>(); + Iterator keys = json.keySet().iterator(); + while (keys.hasNext()) { + String key = keys.next(); + props.put(key, convertJsonValue(json.get(key))); + } + return props; + } + + /** + * Convert single JSON-conformant value object + * @param jsonValue JSON value + * @return Object + * @throws JSONException If JSON-parsing of value failed + */ + private static Object convertJsonValue(String jsonValue) { + String jsonString = "{\"value\":" + jsonValue + "}"; + JsonObject json = toJson(jsonString); + return convertJsonValue(json.get("value")); + } + + private static Object convertJsonValue(JsonValue jsonValue) { + switch (jsonValue.getValueType()) { + case STRING: + return ((JsonString)jsonValue).getString(); + case NUMBER: + JsonNumber number = (JsonNumber)jsonValue; + if (number.isIntegral()) { + return number.longValue(); + } + else { + return number.doubleValue(); + } + case TRUE: + return true; + case FALSE: + return false; + case NULL: + return null; + case ARRAY: + return convertJsonArray((JsonArray)jsonValue); + default: + throw new RuntimeException("Unexpected JSON value type: " + jsonValue.getValueType() + ": " + jsonValue); + } + } + + private static Object convertJsonArray(JsonArray jsonArray) { + if (jsonArray.size() > 0) { + Object firstValue = convertJsonValue(jsonArray.get(0)); + if (firstValue != null) { + Class firstType = firstValue.getClass(); + Object convertedArray = Array.newInstance(firstType, jsonArray.size()); + for (int i=0; i entry : item.getProperties().entrySet()) { + java.lang.String propertyName = entry.getKey(); + if (org.apache.commons.lang3.StringUtils.isEmpty(propertyName) || org.apache.commons.lang3.StringUtils.contains(propertyName, "/")) { + org.apache.sling.caconfig.impl.override.OverrideStringParser.log.warn("Ignore config override string - invalid property name ({}): {}", propertyName, overrideString); + return false; + } + java.lang.Object value = entry.getValue(); + if ((value == null) || (!org.apache.sling.caconfig.impl.override.OverrideStringParser.isSupportedType(value))) { + org.apache.sling.caconfig.impl.override.OverrideStringParser.log.warn("Ignore config override string - invalid property value ({} - {}): {}", /* NPEX_NULL_EXP */ + value, value.getClass().getName(), overrideString); + return false; + } + } + return true; +} + + /** + * Validate if the given object is not null, and the type is supported for configuration values. + * @param value Value + * @return true if valid + */ + private static boolean isSupportedType(Object value) { + if (value == null) { + return false; + } + Class clazz = value.getClass(); + if (clazz.isArray()) { + clazz = clazz.getComponentType(); + } + for (Class type : PropertyMetadata.SUPPORTED_TYPES) { + if (type.equals(clazz )) { + return true; + } + if (type.isPrimitive() && ClassUtils.primitiveToWrapper(type).equals(clazz)) { + return true; + } + } + return false; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/metadata.json new file mode 100644 index 000000000..3eb0fae50 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-OverrideStringParser_257", + "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/caconfig/impl/override/OverrideStringParser.java", + "line": 267, + "npe_method": "isValid", + "deref_field": "value", + "npe_class": "OverrideStringParser", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "OverrideStringParser_257" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/npe.json b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/npe.json new file mode 100644 index 000000000..033cb6007 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_257/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/override/OverrideStringParser.java", + "line": 267, + "npe_method": "isValid", + "deref_field": "value", + "npe_class": "OverrideStringParser" +} \ No newline at end of file diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/Dockerfile b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/Dockerfile new file mode 100644 index 000000000..c4c10d8cc --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/Dockerfile @@ -0,0 +1,18 @@ +FROM ghcr.io/kupl/starlab-benchmarks/java-base:sling-org-apache-sling-caconfig-impl + +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-caconfig-impl-OverrideStringParser_98/buggy.java b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/buggy.java new file mode 100644 index 000000000..142db6275 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/buggy.java @@ -0,0 +1,273 @@ +/* + * 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.caconfig.impl.override; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonException; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonReaderFactory; +import javax.json.JsonString; +import javax.json.JsonValue; + +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.caconfig.spi.metadata.PropertyMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parses override configuration strings like these: + *
    + *
  • {configName}/{propertyName}={propertyJsonValue}
  • + *
  • {configName}={propertyJsonObject}
  • + *
  • [{contextPath}]{configName}/{propertyName}={propertyJsonValue}
  • + *
  • [{contextPath}]{configName}={propertyJsonObject}
  • + *
+ */ +class OverrideStringParser { + + private static final Logger log = LoggerFactory.getLogger(OverrideStringParser.class); + + private static final Pattern OVERRIDE_PATTERN = Pattern.compile("^(\\[([^\\[\\]=]+)\\])?([^\\[\\]=]+)=(.*)$"); + + private static final JsonReaderFactory JSON_READER_FACTORY = Json.createReaderFactory(Collections.emptyMap()); + + private OverrideStringParser() { + // static method sonly + } + + /** + * Parses a list of override strings from a override provider. + * @param overrideStrings Override strings + * @return Override objects + */ +/** + * Parses a list of override strings from a override provider. + * + * @param overrideStrings + * Override strings + * @return Override objects + */ +public static java.util.Collection parse(java.util.Collection overrideStrings) { + java.util.List result = new java.util.ArrayList<>(); + for (java.lang.String overrideString : overrideStrings) { + // check if override generic pattern is matched + java.util.regex.Matcher matcher = org.apache.sling.caconfig.impl.override.OverrideStringParser.OVERRIDE_PATTERN.matcher(org.apache.commons.lang3.StringUtils.defaultString(overrideString)); + if (!matcher.matches()) { + org.apache.sling.caconfig.impl.override.OverrideStringParser.log.warn("Ignore config override string - invalid syntax: {}", overrideString); + continue; + } + // get single parts + java.lang.String path = org.apache.commons.lang3.StringUtils.trim(matcher.group(2)); + java.lang.String configName = org.apache.commons.lang3.StringUtils.trim(matcher.group(3)); + java.lang.String value = org.apache.commons.lang3.StringUtils.trim(org.apache.commons.lang3.StringUtils.defaultString(matcher.group(4))); + org.apache.sling.caconfig.impl.override.OverrideItem item; + try { + // check if value is JSON = defines whole parameter map for a config name + javax.json.JsonObject json = org.apache.sling.caconfig.impl.override.OverrideStringParser.toJson(value); + { + item = new org.apache.sling.caconfig.impl.override.OverrideItem(path, configName, org.apache.sling.caconfig.impl.override.OverrideStringParser.toMap(/* NPEX_NULL_EXP */ + json), true); + } + } catch (javax.json.JsonException ex) { + org.apache.sling.caconfig.impl.override.OverrideStringParser.log.warn("Ignore config override string - invalid JSON syntax ({}): {}", ex.getMessage(), overrideString); + continue; + } + // validate item + if (!org.apache.sling.caconfig.impl.override.OverrideStringParser.isValid(item, overrideString)) { + continue; + } + // if item does not contain a full property set try to merge with existing one + if (!item.isAllProperties()) { + boolean foundMatchingItem = false; + for (org.apache.sling.caconfig.impl.override.OverrideItem existingItem : result) { + if (((!existingItem.isAllProperties()) && org.apache.commons.lang3.StringUtils.equals(item.getPath(), existingItem.getPath())) && org.apache.commons.lang3.StringUtils.equals(item.getConfigName(), existingItem.getConfigName())) { + existingItem.getProperties().putAll(item.getProperties()); + foundMatchingItem = true; + break; + } + } + if (foundMatchingItem) { + continue; + } + } + // add item to result + result.add(item); + } + return result; +} + + /** + * Try to convert value to JSON object + * @param value Value string + * @return JSON object or null if the string does not start with "{" + * @throws JSONException when JSON parsing failed + */ + private static JsonObject toJson(String value) { + if (!StringUtils.startsWith(value, "{")) { + return null; + } + try (Reader reader = new StringReader(value); + JsonReader jsonReader = JSON_READER_FACTORY.createReader(reader)) { + return jsonReader.readObject(); + } + catch (IOException ex) { + return null; + } + } + + /** + * Convert JSON object to map. + * @param json JSON object + * @return Map (keys/values are not validated) + */ + private static Map toMap(JsonObject json) { + Map props = new HashMap<>(); + Iterator keys = json.keySet().iterator(); + while (keys.hasNext()) { + String key = keys.next(); + props.put(key, convertJsonValue(json.get(key))); + } + return props; + } + + /** + * Convert single JSON-conformant value object + * @param jsonValue JSON value + * @return Object + * @throws JSONException If JSON-parsing of value failed + */ + private static Object convertJsonValue(String jsonValue) { + String jsonString = "{\"value\":" + jsonValue + "}"; + JsonObject json = toJson(jsonString); + return convertJsonValue(json.get("value")); + } + + private static Object convertJsonValue(JsonValue jsonValue) { + switch (jsonValue.getValueType()) { + case STRING: + return ((JsonString)jsonValue).getString(); + case NUMBER: + JsonNumber number = (JsonNumber)jsonValue; + if (number.isIntegral()) { + return number.longValue(); + } + else { + return number.doubleValue(); + } + case TRUE: + return true; + case FALSE: + return false; + case NULL: + return null; + case ARRAY: + return convertJsonArray((JsonArray)jsonValue); + default: + throw new RuntimeException("Unexpected JSON value type: " + jsonValue.getValueType() + ": " + jsonValue); + } + } + + private static Object convertJsonArray(JsonArray jsonArray) { + if (jsonArray.size() > 0) { + Object firstValue = convertJsonValue(jsonArray.get(0)); + if (firstValue != null) { + Class firstType = firstValue.getClass(); + Object convertedArray = Array.newInstance(firstType, jsonArray.size()); + for (int i=0; i entry : item.getProperties().entrySet()) { + String propertyName = entry.getKey(); + if (StringUtils.isEmpty(propertyName) || StringUtils.contains(propertyName, "/")) { + log.warn("Ignore config override string - invalid property name ({}): {}", propertyName, overrideString); + return false; + } + Object value = entry.getValue(); + if (value == null || !isSupportedType(value)) { + log.warn("Ignore config override string - invalid property value ({} - {}): {}", value, value != null ? value.getClass().getName() : "", overrideString); + return false; + } + } + return true; + } + + /** + * Validate if the given object is not null, and the type is supported for configuration values. + * @param value Value + * @return true if valid + */ + private static boolean isSupportedType(Object value) { + if (value == null) { + return false; + } + Class clazz = value.getClass(); + if (clazz.isArray()) { + clazz = clazz.getComponentType(); + } + for (Class type : PropertyMetadata.SUPPORTED_TYPES) { + if (type.equals(clazz )) { + return true; + } + if (type.isPrimitive() && ClassUtils.primitiveToWrapper(type).equals(clazz)) { + return true; + } + } + return false; + } + +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/metadata.json b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/metadata.json new file mode 100644 index 000000000..231f974d1 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/metadata.json @@ -0,0 +1,21 @@ +{ + "language": "java", + "id": "sling-org-apache-sling-caconfig-impl-OverrideStringParser_98", + "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/caconfig/impl/override/OverrideStringParser.java", + "line": 103, + "npe_method": "parse", + "deref_field": "json", + "npe_class": "OverrideStringParser", + "repo": "sling-org-apache-sling-caconfig-impl", + "bug_id": "OverrideStringParser_98" + } +} diff --git a/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/npe.json b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/npe.json new file mode 100644 index 000000000..05a17c053 --- /dev/null +++ b/Java/sling-org-apache-sling-caconfig-impl-OverrideStringParser_98/npe.json @@ -0,0 +1,7 @@ +{ + "filepath": "src/main/java/org/apache/sling/caconfig/impl/override/OverrideStringParser.java", + "line": 103, + "npe_method": "parse", + "deref_field": "json", + "npe_class": "OverrideStringParser" +} \ No newline at end of file