SLING-10763 : Provide a framework to check artifact versions
diff --git a/pom.xml b/pom.xml
index 344d1a9..ed8b2f0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,7 +18,7 @@
</parent>
<artifactId>org.apache.sling.feature.extension.apiregions</artifactId>
- <version>1.3.11-SNAPSHOT</version>
+ <version>1.4.0</version>
<name>Sling Featuremodel - API Regions Exension</name>
<scm>
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/ArtifactRulesMergeHandler.java b/src/main/java/org/apache/sling/feature/extension/apiregions/ArtifactRulesMergeHandler.java
new file mode 100644
index 0000000..98a02f5
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/ArtifactRulesMergeHandler.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.feature.extension.apiregions;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.builder.HandlerContext;
+import org.apache.sling.feature.builder.MergeHandler;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.ArtifactRules;
+
+/**
+ * Merge the artifact rules extension
+ */
+public class ArtifactRulesMergeHandler implements MergeHandler {
+
+ @Override
+ public boolean canMerge(final Extension extension) {
+ return ArtifactRules.EXTENSION_NAME.equals(extension.getName());
+ }
+
+ @Override
+ public void merge(final HandlerContext context,
+ final Feature targetFeature,
+ final Feature sourceFeature,
+ final Extension targetExtension,
+ final Extension sourceExtension) {
+
+ if ( targetExtension == null ) {
+ // no target available yet, just copy source
+ final ArtifactRules sourceRules = ArtifactRules.getArtifactRules(sourceExtension);
+ ArtifactRules.setArtifactRules(targetFeature, sourceRules);
+ } else {
+ final ArtifactRules sourceRules = ArtifactRules.getArtifactRules(sourceExtension);
+ final ArtifactRules targetRules = ArtifactRules.getArtifactRules(targetExtension);
+
+ // mode merging
+ if ( context.isInitialMerge() ) {
+ targetRules.setMode(sourceRules.getMode());
+ } else {
+ if ( targetRules.getMode().ordinal() > sourceRules.getMode().ordinal() ) {
+ targetRules.setMode(sourceRules.getMode());
+ }
+ }
+
+ // merge - just add
+ targetRules.getBundleVersionRules().addAll(sourceRules.getBundleVersionRules());
+
+ ArtifactRules.setArtifactRules(targetFeature, targetRules);
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckArtifactRules.java b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckArtifactRules.java
new file mode 100644
index 0000000..0b24240
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/analyser/CheckArtifactRules.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.feature.extension.apiregions.analyser;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.ArtifactRules;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.Mode;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.VersionRule;
+
+
+public class CheckArtifactRules implements AnalyserTask{
+
+ @Override
+ public String getId() {
+ return "artifact-rules";
+ }
+
+ @Override
+ public String getName() {
+ return "Artifact rules analyser task";
+ }
+
+ @Override
+ public void execute(final AnalyserTaskContext context) throws Exception {
+ final ArtifactRules rules = ArtifactRules.getArtifactRules(context.getFeature());
+ if ( rules == null ) {
+ context.reportExtensionWarning(ArtifactRules.EXTENSION_NAME, "Artifact rules are not specified, unable to validate feature");
+ } else {
+ for(final Artifact bundle : context.getFeature().getBundles()) {
+ for(final VersionRule rule : rules.getBundleVersionRules()) {
+ if ( rule.getArtifactId() != null && rule.getArtifactId().isSame(bundle.getId())) {
+ if ( ! rule.isAllowed(bundle.getId().getOSGiVersion())) {
+ String msg = rule.getMessage();
+ if ( msg == null ) {
+ msg = "Bundle with version " + bundle.getId().getVersion() + " is not allowed.";
+ }
+ Mode m = rules.getMode();
+ if ( rule.getMode() != null ) {
+ m = rule.getMode();
+ }
+ if ( m == Mode.LENIENT ) {
+ context.reportArtifactWarning(bundle.getId(), msg);
+ } else {
+ context.reportArtifactError(bundle.getId(), msg);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRules.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRules.java
new file mode 100755
index 0000000..945e674
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRules.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.artifacts;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.extension.apiregions.api.config.AttributeableEntity;
+
+/**
+ * Artifact rules define additional rules for artifacts in a feature model.
+ * The rules are stored as an extension in the feature model.
+ * This class is not thread safe.
+ */
+public class ArtifactRules extends AttributeableEntity {
+
+ /** The name of the feature model extension. */
+ public static final String EXTENSION_NAME = "artifact-rules";
+
+ /**
+ * Get the artifact rules from the feature - if it exists.
+ * If the rules are updated, the containing feature is left untouched.
+ * {@link #setArtifactRules(Feature, ArtifactRules)} can be used to update
+ * the feature.
+ * @param feature The feature
+ * @return The rules or {@code null}.
+ * @throws IllegalArgumentException If the extension is wrongly formatted
+ */
+ public static ArtifactRules getArtifactRules(final Feature feature) {
+ final Extension ext = feature == null ? null : feature.getExtensions().getByName(EXTENSION_NAME);
+ return getArtifactRules(ext);
+ }
+
+ /**
+ * Get the artifact rules from the extension - if it exists.
+ * If the rules are updated, the containing feature is left untouched.
+ * {@link #setArtifactRules(Feature, ArtifactRules)} can be used to update
+ * the feature.
+ * @param ext The extension
+ * @return The rules or {@code null} if the extension is {@code null}.
+ * @throws IllegalArgumentException If the extension is wrongly formatted
+ */
+ public static ArtifactRules getArtifactRules(final Extension ext) {
+ if ( ext == null ) {
+ return null;
+ }
+ if ( ext.getType() != ExtensionType.JSON ) {
+ throw new IllegalArgumentException("Extension " + ext.getName() + " must have JSON type");
+ }
+ try {
+ final ArtifactRules result = new ArtifactRules();
+ result.fromJSONObject(ext.getJSONStructure().asJsonObject());
+ return result;
+ } catch ( final IOException ioe) {
+ throw new IllegalArgumentException(ioe.getMessage(), ioe);
+ }
+ }
+
+ /**
+ * Set the rules as an extension to the feature
+ * @param feature The feature
+ * @param rules The rules. If {@code null} the extension will be removed.
+ * @throws IllegalStateException If the feature has already an extension of a wrong type
+ * @throws IllegalArgumentException If the rules can't be serialized to JSON
+ */
+ public static void setArtifactRules(final Feature feature, final ArtifactRules rules) {
+ Extension ext = feature.getExtensions().getByName(EXTENSION_NAME);
+ if ( rules == null ) {
+ if ( ext != null ) {
+ feature.getExtensions().remove(ext);
+ }
+ } else {
+ if ( ext == null ) {
+ ext = new Extension(ExtensionType.JSON, EXTENSION_NAME, ExtensionState.OPTIONAL);
+ feature.getExtensions().add(ext);
+ }
+ try {
+ ext.setJSONStructure(rules.toJSONObject());
+ } catch ( final IOException ioe) {
+ throw new IllegalArgumentException(ioe);
+ }
+ }
+ }
+
+ /** The validation mode */
+ private Mode mode;
+
+ /** The version rules */
+ private final List<VersionRule> bundleVersionRules = new ArrayList<>();
+
+ /**
+ * Create a new rules object
+ */
+ public ArtifactRules() {
+ this.setDefaults();
+ }
+
+ @Override
+ protected void setDefaults() {
+ super.setDefaults();
+ this.setMode(Mode.STRICT);
+ }
+
+ /**
+ * Clear the object and reset to defaults
+ */
+ @Override
+ public void clear() {
+ super.clear();
+ this.getBundleVersionRules().clear();
+ }
+
+ /**
+ * Convert this object into JSON
+ *
+ * @return The json object builder
+ * @throws IOException If generating the JSON fails
+ */
+ @Override
+ public JsonObjectBuilder createJson() throws IOException {
+ final JsonObjectBuilder objBuilder = super.createJson();
+ if ( this.getMode() != Mode.STRICT ) {
+ objBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
+ }
+
+ if ( !this.getBundleVersionRules().isEmpty() ) {
+ final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
+ for(final VersionRule rule : this.getBundleVersionRules()) {
+ arrayBuilder.add(rule.createJson());
+ }
+ objBuilder.add(InternalConstants.KEY_BUNDLE_VERSION_RULES, arrayBuilder);
+ }
+
+ return objBuilder;
+ }
+
+ /**
+ * Extract the metadata from the JSON object.
+ * This method first calls {@link #clear()}.
+ *
+ * @param jsonObj The JSON Object
+ * @throws IOException If JSON parsing fails
+ */
+ @Override
+ public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+ super.fromJSONObject(jsonObj);
+ try {
+ final String modeVal = this.getString(InternalConstants.KEY_MODE);
+ if ( modeVal != null ) {
+ this.setMode(Mode.valueOf(modeVal.toUpperCase()));
+ }
+
+ JsonValue val = this.getAttributes().remove(InternalConstants.KEY_BUNDLE_VERSION_RULES);
+ if ( val != null ) {
+ for(final JsonValue innerVal : val.asJsonArray()) {
+ final VersionRule rule = new VersionRule();
+ rule.fromJSONObject(innerVal.asJsonObject());
+ this.getBundleVersionRules().add(rule);
+ }
+ }
+
+ } catch (final JsonException | IllegalArgumentException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Get the validation mode.
+ * The default is {@link Mode#STRICT}
+ * @return The mode
+ */
+ public Mode getMode() {
+ return this.mode;
+ }
+
+ /**
+ * Set the validation mode
+ * @param value The validation mode
+ */
+ public void setMode(final Mode value) {
+ this.mode = value == null ? Mode.STRICT : value;
+ }
+
+ /**
+ * Return the list of version rules for bundles. The returned list is mutable.
+ * @return The list of rules, might be empty.
+ */
+ public List<VersionRule> getBundleVersionRules() {
+ return this.bundleVersionRules;
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/InternalConstants.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/InternalConstants.java
new file mode 100755
index 0000000..b158b4e
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/InternalConstants.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.artifacts;
+
+/**
+ * Constants used in this implementation
+ */
+abstract class InternalConstants {
+
+ static final String KEY_MODE = "mode";
+
+ static final String KEY_ARTIFACT_ID = "artifact-id";
+
+ static final String KEY_ALLOWED_VERSION_RANGES = "allowed-version-ranges";
+
+ static final String KEY_DENIED_VERSION_RANGES = "denied-version-ranges";
+
+ static final String KEY_MESSAGE = "message";
+
+ static final String KEY_BUNDLE_VERSION_RULES = "bundle-version-rules";
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/Mode.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/Mode.java
new file mode 100755
index 0000000..ce5a468
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/Mode.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.api.artifacts;
+
+/**
+ * The mode for the rules
+ */
+public enum Mode {
+
+ /** Default mode - If validation fails, issue an error. */
+ STRICT,
+
+ /** If validation fails, issue a warning (but use the invalid value). */
+ LENIENT
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/VersionRule.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/VersionRule.java
new file mode 100755
index 0000000..1e4de5a
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/VersionRule.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.feature.extension.apiregions.api.artifacts;
+
+import java.io.IOException;
+
+import javax.json.JsonException;
+import javax.json.JsonObject;
+import javax.json.JsonObjectBuilder;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.extension.apiregions.api.config.AttributeableEntity;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+
+/**
+ * A rule to validate the version of an artifact.
+ * This class is not thread safe.
+ */
+public class VersionRule extends AttributeableEntity {
+
+ /** The optional validation mode */
+ private Mode mode;
+
+ /** The artifact id. */
+ private ArtifactId artifactId;
+
+ /** The message */
+ private String message;
+
+ /** The allowed version ranges */
+ private VersionRange[] allowedVersionRanges;
+
+ /** The denied version ranges */
+ private VersionRange[] deniedVersionRanges;
+
+ /**
+ * Create a new rules object
+ */
+ public VersionRule() {
+ this.setDefaults();
+ }
+
+ /**
+ * Clear the object and reset to defaults
+ */
+ @Override
+ public void clear() {
+ super.clear();
+ this.setArtifactId(null);
+ this.setMode(null);
+ this.setMessage(null);
+ this.setAllowedVersionRanges(null);
+ this.setDeniedVersionRanges(null);
+ }
+
+ /**
+ * Convert this object into JSON
+ *
+ * @return The json object builder
+ * @throws IOException If generating the JSON fails
+ */
+ @Override
+ public JsonObjectBuilder createJson() throws IOException {
+ final JsonObjectBuilder objBuilder = super.createJson();
+ if ( this.getMode() != null ) {
+ objBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
+ }
+
+ if ( this.getArtifactId() != null ) {
+ objBuilder.add(InternalConstants.KEY_ARTIFACT_ID, this.getArtifactId().toMvnId());
+ }
+
+ this.setString(objBuilder, InternalConstants.KEY_MESSAGE, this.getMessage());
+
+ if ( this.getAllowedVersionRanges() != null && this.getAllowedVersionRanges().length > 0 ) {
+ final String[] arr = new String[this.getAllowedVersionRanges().length];
+ for(int i=0;i<this.getAllowedVersionRanges().length;i++) {
+ arr[i] = this.getAllowedVersionRanges()[i].toString();
+ }
+ this.setStringArray(objBuilder, InternalConstants.KEY_ALLOWED_VERSION_RANGES, arr);
+ }
+
+ if ( this.getDeniedVersionRanges() != null && this.getDeniedVersionRanges().length > 0 ) {
+ final String[] arr = new String[this.getDeniedVersionRanges().length];
+ for(int i=0;i<this.getDeniedVersionRanges().length;i++) {
+ arr[i] = this.getDeniedVersionRanges()[i].toString();
+ }
+ this.setStringArray(objBuilder, InternalConstants.KEY_DENIED_VERSION_RANGES, arr);
+ }
+
+ return objBuilder;
+ }
+
+ /**
+ * Extract the metadata from the JSON object.
+ * This method first calls {@link #clear()}.
+ *
+ * @param jsonObj The JSON Object
+ * @throws IOException If JSON parsing fails
+ */
+ @Override
+ public void fromJSONObject(final JsonObject jsonObj) throws IOException {
+ super.fromJSONObject(jsonObj);
+ try {
+ String val = this.getString(InternalConstants.KEY_MODE);
+ if ( val != null ) {
+ this.setMode(Mode.valueOf(val.toUpperCase()));
+ }
+
+ val = this.getString(InternalConstants.KEY_ARTIFACT_ID);
+ if ( val != null ) {
+ this.setArtifactId(ArtifactId.parse(val));
+ }
+
+ this.setMessage(this.getString(InternalConstants.KEY_MESSAGE));
+
+ String[] arr = this.getStringArray(InternalConstants.KEY_ALLOWED_VERSION_RANGES);
+ if ( arr != null && arr.length > 0 ) {
+ final VersionRange[] ranges = new VersionRange[arr.length];
+ for(int i=0;i<arr.length;i++) {
+ try {
+ ranges[i] = new VersionRange(arr[i]);
+ } catch ( final IllegalArgumentException iae) {
+ throw new IOException("Illegal argument for allowed version range: " + arr[i]);
+ }
+ }
+ this.setAllowedVersionRanges(ranges);
+ }
+
+ arr = this.getStringArray(InternalConstants.KEY_DENIED_VERSION_RANGES);
+ if ( arr != null && arr.length > 0 ) {
+ final VersionRange[] ranges = new VersionRange[arr.length];
+ for(int i=0;i<arr.length;i++) {
+ try {
+ ranges[i] = new VersionRange(arr[i]);
+ } catch ( final IllegalArgumentException iae) {
+ throw new IOException("Illegal argument for allowed version range: " + arr[i]);
+ }
+ }
+ this.setDeniedVersionRanges(ranges);
+ }
+ } catch (final JsonException | IllegalArgumentException e) {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Get the validation mode.
+ * The default is {@link Mode#STRICT}
+ * @return The mode
+ */
+ public Mode getMode() {
+ return this.mode;
+ }
+
+ /**
+ * Set the validation mode
+ * @param value The validation mode
+ */
+ public void setMode(final Mode value) {
+ this.mode = value;
+ }
+
+ /**
+ * Get the artifact id
+ * @return the artifactId
+ */
+ public ArtifactId getArtifactId() {
+ return artifactId;
+ }
+
+ /**
+ * Set the artifact id
+ * @param artifactId the artifactId to set
+ */
+ public void setArtifactId(final ArtifactId artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ /**
+ * The validation message
+ * @return the message
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Set the validation message
+ * @param message the message to set
+ */
+ public void setMessage(final String message) {
+ this.message = message;
+ }
+
+ /**
+ * The allowed version ranges
+ * @return the allowedVersions or {@code null}
+ */
+ public VersionRange[] getAllowedVersionRanges() {
+ return allowedVersionRanges;
+ }
+
+ /**
+ * Set the allowed version ranges
+ * @param allowedVersions the allowedVersions to set
+ */
+ public void setAllowedVersionRanges(final VersionRange[] allowedVersions) {
+ this.allowedVersionRanges = allowedVersions;
+ }
+
+ /**
+ * Get the denied version ranges
+ * @return the deniedVersions or {@code null}
+ */
+ public VersionRange[] getDeniedVersionRanges() {
+ return deniedVersionRanges;
+ }
+
+ /**
+ * Set the denied version ranges
+ * @param deniedVersions the deniedVersions to set
+ */
+ public void setDeniedVersionRanges(final VersionRange[] deniedVersions) {
+ this.deniedVersionRanges = deniedVersions;
+ }
+
+ /**
+ * Check if a version is allowed according to the rules
+ * @param artifactVersion The version
+ * @return {@code true} if it is allowed, {@code false} otherwise
+ */
+ public boolean isAllowed(final Version artifactVersion) {
+ boolean result = false;
+ if ( this.getAllowedVersionRanges() != null && this.getAllowedVersionRanges().length > 0 ) {
+ for(final VersionRange range : this.getAllowedVersionRanges()) {
+ if ( range.includes(artifactVersion) ) {
+ result = true;
+ break;
+ }
+ }
+ if ( result && this.getDeniedVersionRanges() != null ) {
+ for(final VersionRange range : this.getDeniedVersionRanges()) {
+ if ( range.includes(artifactVersion) ) {
+ result = false;
+ break;
+ }
+ }
+ }
+ }
+ return result;
+
+ }
+}
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/package-info.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/package-info.java
new file mode 100755
index 0000000..3b169bc
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/artifacts/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+@org.osgi.annotation.versioning.Version("1.0.0")
+package org.apache.sling.feature.extension.apiregions.api.artifacts;
+
+
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
index 43617f9..c3908f9 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntity.java
@@ -21,10 +21,14 @@
import java.util.Map;
import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonArrayBuilder;
import javax.json.JsonException;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
+import javax.json.JsonString;
import javax.json.JsonValue;
+import javax.json.JsonValue.ValueType;
import org.apache.felix.cm.json.Configurations;
@@ -33,14 +37,14 @@
* This class is not thread safe.
*/
public abstract class AttributeableEntity {
-
+
/** The additional attributes */
private final Map<String, JsonValue> attributes = new LinkedHashMap<>();
/**
* Apply the non-null default values.
*/
- void setDefaults() {
+ protected void setDefaults() {
// nothing to do
}
@@ -51,7 +55,7 @@
this.setDefaults();
this.attributes.clear();
}
-
+
/**
* Convert this object into JSON
*
@@ -66,7 +70,7 @@
/**
* Extract the metadata from the JSON object.
* This method first calls {@link #clear()}
- *
+ *
* @param jsonObj The JSON Object
* @throws IOException If JSON parsing fails
*/
@@ -95,7 +99,7 @@
* @return The json object builder
* @throws IOException If generating the JSON fails
*/
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objectBuilder = Json.createObjectBuilder();
for(final Map.Entry<String, JsonValue> entry : this.getAttributes().entrySet()) {
@@ -104,13 +108,13 @@
return objectBuilder;
}
-
+
/**
* Helper method to get a string value from a JsonValue
* @param jsonValue The json value
* @return The string value or {@code null}.
*/
- String getString(final JsonValue jsonValue) {
+ protected String getString(final JsonValue jsonValue) {
final Object obj = Configurations.convertToObject(jsonValue);
if ( obj != null ) {
return obj.toString();
@@ -123,7 +127,7 @@
* @param attributeName The attribute name
* @return The string value or {@code null}.
*/
- String getString(final String attributeName) {
+ protected String getString(final String attributeName) {
final JsonValue val = this.getAttributes().remove(attributeName);
if ( val != null ) {
final Object obj = Configurations.convertToObject(val);
@@ -135,12 +139,39 @@
}
/**
+ * Helper method to get a string array from an attribute
+ * @param attributeName The attribute name
+ * @return The string array or {@code null}.
+ * @since 1.6.0
+ */
+ protected String[] getStringArray(final String attributeName) throws IOException {
+ final JsonValue val = this.getAttributes().remove(attributeName);
+ if ( val != null ) {
+ if ( val.getValueType() == ValueType.ARRAY ) {
+ final JsonArray array = val.asJsonArray();
+ final String[] result = new String[array.size()];
+ int i = 0;
+ for(final JsonValue v : array) {
+ result[i] = Configurations.convertToObject(v).toString();
+ i++;
+ }
+ return result;
+ } else if ( val.getValueType() == ValueType.STRING ) {
+ return new String[] {((JsonString)val).getString()};
+ } else {
+ throw new IOException("Invalid type for string array value " + attributeName + " : " + val.getValueType().name());
+ }
+ }
+ return null;
+ }
+
+ /**
* Helper method to get a number value from an attribute
* @param attributeName The attribute name
* @return The string value or {@code null}.
* @throws IOException If the attribute value is not of type boolean
*/
- Number getNumber(final String attributeName) throws IOException {
+ protected Number getNumber(final String attributeName) throws IOException {
final JsonValue val = this.getAttributes().remove(attributeName);
if ( val != null ) {
final Object obj = Configurations.convertToObject(val);
@@ -154,20 +185,40 @@
/**
* Helper method to set a string value
+ * @param builder The json object builder
+ * @param attributeName The name of the attribute
+ * @param value The string value
*/
- void setString(final JsonObjectBuilder builder, final String attributeName, final String value) {
+ protected void setString(final JsonObjectBuilder builder, final String attributeName, final String value) {
if ( value != null ) {
builder.add(attributeName, value);
}
}
- /**
+ /**
+ * Helper method to set a string array
+ * @param builder The json object builder
+ * @param attributeName The name of the attribute
+ * @param value The string array
+ * @since 1.6.0
+ */
+ protected void setStringArray(final JsonObjectBuilder builder, final String attributeName, final String[] value) {
+ if ( value != null && value.length > 0 ) {
+ final JsonArrayBuilder jab = Json.createArrayBuilder();
+ for(final String v : value) {
+ jab.add(v);
+ }
+ builder.add(attributeName, jab);
+ }
+ }
+
+ /**
* Helper method to get a integer value from an attribute
* @param attributeName The attribute name
* @param defaultValue default value
* @return The integer value or the default value
*/
- int getInteger(final String attributeName, final int defaultValue) {
+ protected int getInteger(final String attributeName, final int defaultValue) {
final String val = this.getString(attributeName);
if ( val != null ) {
return Integer.parseInt(val);
@@ -182,7 +233,7 @@
* @return The boolean value or the default value
* @throws IOException If the attribute value is not of type boolean
*/
- boolean getBoolean(final String attributeName, final boolean defaultValue) throws IOException {
+ protected boolean getBoolean(final String attributeName, final boolean defaultValue) throws IOException {
final JsonValue val = this.getAttributes().remove(attributeName);
if ( val != null ) {
final Object obj = Configurations.convertToObject(val);
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
index 4e3b1f3..3535246 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurableEntity.java
@@ -64,7 +64,7 @@
*/
private final List<String> internalProperties = new ArrayList<>();
- void setDefaults() {
+ protected void setDefaults() {
super.setDefaults();
this.setAllowAdditionalProperties(false);
this.setRegion(Region.GLOBAL);
@@ -200,7 +200,7 @@
* @throws IOException If generating the JSON fails
*/
@Override
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objBuilder = super.createJson();
if ( !this.getPropertyDescriptions().isEmpty() ) {
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
index 5108580..efd3807 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/ConfigurationApi.java
@@ -42,10 +42,10 @@
* This class is not thread safe.
*/
public class ConfigurationApi extends AttributeableEntity {
-
+
/** The name of the api regions extension. */
public static final String EXTENSION_NAME = "configuration-api";
-
+
/**
* Get the configuration api from the feature - if it exists.
* If the configuration api is updated, the containing feature is left untouched.
@@ -82,7 +82,7 @@
throw new IllegalArgumentException(ioe.getMessage(), ioe);
}
}
-
+
/**
* Set the configuration api as an extension to the feature
* @param feature The feature
@@ -126,28 +126,29 @@
/** The set of internal framework property names */
private final Set<String> internalFrameworkProperties = new TreeSet<>();
-
+
/** The configuration region of this feature */
private Region region;
/** The cached region information for feature origins */
private final Map<ArtifactId, Region> regionCache = new LinkedHashMap<>();
- /**
- * The default validation mode.
+ /**
+ * The default validation mode.
* @since 1.2
*/
private Mode mode;
-
+
public ConfigurationApi() {
this.setDefaults();
}
-
- void setDefaults() {
+
+ @Override
+ protected void setDefaults() {
super.setDefaults();
this.setMode(Mode.STRICT);
}
-
+
/**
* Clear the object and reset to defaults
*/
@@ -167,7 +168,7 @@
/**
* Extract the metadata from the JSON object.
* This method first calls {@link #clear()}.
- *
+ *
* @param jsonObj The JSON Object
* @throws IOException If JSON parsing fails
*/
@@ -177,7 +178,7 @@
try {
final String typeVal = this.getString(InternalConstants.KEY_REGION);
if ( typeVal != null ) {
- this.setRegion(Region.valueOf(typeVal.toUpperCase()));
+ this.setRegion(Region.valueOf(typeVal.toUpperCase()));
}
JsonValue val;
@@ -189,7 +190,7 @@
this.getConfigurationDescriptions().put(innerEntry.getKey(), cfg);
}
}
-
+
val = this.getAttributes().remove(InternalConstants.KEY_FACTORIES);
if ( val != null ) {
for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
@@ -232,14 +233,14 @@
val = this.getAttributes().remove(InternalConstants.KEY_REGION_CACHE);
if ( val != null ) {
for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
- this.getFeatureToRegionCache().put(ArtifactId.parse(innerEntry.getKey()),
+ this.getFeatureToRegionCache().put(ArtifactId.parse(innerEntry.getKey()),
Region.valueOf(getString(innerEntry.getValue()).toUpperCase()));
}
}
-
+
final String modeVal = this.getString(InternalConstants.KEY_MODE);
if ( modeVal != null ) {
- this.setMode(Mode.valueOf(modeVal.toUpperCase()));
+ this.setMode(Mode.valueOf(modeVal.toUpperCase()));
}
} catch (final JsonException | IllegalArgumentException e) {
@@ -276,7 +277,8 @@
* @return Mutable set of internal configuration pids
* @deprecated Please use empty configuration descriptions via {@link #getConfigurationDescriptions()}
*/
- public Set<String> getInternalConfigurations() {
+ @Deprecated
+ public Set<String> getInternalConfigurations() {
return internalConfigurations;
}
@@ -285,7 +287,8 @@
* @return Mutable set of internal factory configuration pids
* @deprecated Please use empty factory configuration descriptions via {@link #getFactoryConfigurationDescriptions()}
*/
- public Set<String> getInternalFactoryConfigurations() {
+ @Deprecated
+ public Set<String> getInternalFactoryConfigurations() {
return internalFactories;
}
@@ -360,7 +363,7 @@
* @throws IOException If generating the JSON fails
*/
@Override
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objBuilder = super.createJson();
if ( this.getRegion() != null ) {
objBuilder.add(InternalConstants.KEY_REGION, this.getRegion().name());
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
index a2bf0eb..a5c6496 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/DescribableEntity.java
@@ -172,7 +172,7 @@
* @throws IOException If generating the JSON fails
*/
@Override
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objectBuilder = super.createJson();
this.setString(objectBuilder, InternalConstants.KEY_TITLE, this.getTitle());
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
index 71c5606..4f6ac08 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/FactoryConfigurationDescription.java
@@ -43,7 +43,7 @@
this.setDefaults();
}
- void setDefaults() {
+ protected void setDefaults() {
super.setDefaults();
this.getOperations().add(Operation.CREATE);
this.getOperations().add(Operation.UPDATE);
@@ -117,7 +117,7 @@
* @throws IOException If generating the JSON fails
*/
@Override
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objBuilder = super.createJson();
if ( !this.getOperations().isEmpty() && this.getOperations().size() != 2 ) {
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
index 7df9908..8470387 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Option.java
@@ -79,7 +79,7 @@
* @throws IOException If generating the JSON fails
*/
@Override
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objectBuilder = super.createJson();
this.setString(objectBuilder, InternalConstants.KEY_VALUE, this.getValue());
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java
index 367e118..6ee4fcf 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/PropertyDescription.java
@@ -94,7 +94,7 @@
this.setDefaults();
}
- void setDefaults() {
+ protected void setDefaults() {
super.setDefaults();
this.setType(PropertyType.STRING);
this.setCardinality(1);
@@ -194,7 +194,7 @@
* @throws IOException If generating the JSON fails
*/
@Override
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objectBuilder = super.createJson();
if ( this.getType() != null && this.getType() != PropertyType.STRING ) {
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
index 7106cb6..2e90a0d 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/Range.java
@@ -102,7 +102,7 @@
* @throws IOException If generating the JSON fails
*/
@Override
- JsonObjectBuilder createJson() throws IOException {
+ protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objectBuilder = super.createJson();
if ( this.getMin() != null ) {
diff --git a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
index 49fdfc7..0c44f4f 100644
--- a/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
+++ b/src/main/java/org/apache/sling/feature/extension/apiregions/api/config/package-info.java
@@ -17,7 +17,7 @@
* under the License.
*/
-@org.osgi.annotation.versioning.Version("1.5.0")
+@org.osgi.annotation.versioning.Version("1.6.0")
package org.apache.sling.feature.extension.apiregions.api.config;
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.builder.MergeHandler b/src/main/resources/META-INF/services/org.apache.sling.feature.builder.MergeHandler
index caa2534..2867a76 100644
--- a/src/main/resources/META-INF/services/org.apache.sling.feature.builder.MergeHandler
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.builder.MergeHandler
@@ -1,2 +1,3 @@
org.apache.sling.feature.extension.apiregions.APIRegionMergeHandler
org.apache.sling.feature.extension.apiregions.ConfigurationApiMergeHandler
+org.apache.sling.feature.extension.apiregions.ArtifactRulesMergeHandler
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/ArtifactRulesMergeHandlerTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/ArtifactRulesMergeHandlerTest.java
new file mode 100644
index 0000000..2a3a46c
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/ArtifactRulesMergeHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ package org.apache.sling.feature.extension.apiregions;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.builder.BuilderContext;
+import org.apache.sling.feature.builder.FeatureBuilder;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.ArtifactRules;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.Mode;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.VersionRule;
+import org.junit.Test;
+
+public class ArtifactRulesMergeHandlerTest {
+
+ @Test public void testModeMerging() {
+ final Feature featureA = new Feature(ArtifactId.parse("g:a:1"));
+ final ArtifactRules rulesA = new ArtifactRules();
+ rulesA.setMode(Mode.LENIENT);
+ ArtifactRules.setArtifactRules(featureA, rulesA);
+ final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+ final ArtifactRules rulesB = new ArtifactRules();
+ rulesB.setMode(Mode.STRICT);
+ ArtifactRules.setArtifactRules(featureB, rulesB);
+
+ final BuilderContext context = new BuilderContext(id -> null);
+ context.addMergeExtensions(new ArtifactRulesMergeHandler());
+
+ final Feature result = FeatureBuilder.assemble(ArtifactId.parse("g:f:1"), context, featureA, featureB);
+ final ArtifactRules rules = ArtifactRules.getArtifactRules(result);
+ assertNotNull(rules);
+ assertEquals(Mode.STRICT, rules.getMode());
+ }
+
+ @Test public void testRuleMerging() {
+ final Feature featureA = new Feature(ArtifactId.parse("g:a:1"));
+ final ArtifactRules rulesA = new ArtifactRules();
+ final VersionRule vrA = new VersionRule();
+ rulesA.getBundleVersionRules().add(vrA);
+ ArtifactRules.setArtifactRules(featureA, rulesA);
+ final Feature featureB = new Feature(ArtifactId.parse("g:b:1"));
+ final ArtifactRules rulesB = new ArtifactRules();
+ final VersionRule vrB = new VersionRule();
+ rulesB.getBundleVersionRules().add(vrB);
+ ArtifactRules.setArtifactRules(featureB, rulesB);
+
+ final BuilderContext context = new BuilderContext(id -> null);
+ context.addMergeExtensions(new ArtifactRulesMergeHandler());
+
+ final Feature result = FeatureBuilder.assemble(ArtifactId.parse("g:f:1"), context, featureA, featureB);
+ final ArtifactRules rules = ArtifactRules.getArtifactRules(result);
+ assertNotNull(rules);
+ assertEquals(2, rules.getBundleVersionRules().size());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckArtifactRulesTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckArtifactRulesTest.java
new file mode 100644
index 0000000..15cf8d1
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/analyser/CheckArtifactRulesTest.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.extension.apiregions.analyser;
+
+import static org.mockito.Mockito.when;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.ArtifactRules;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.Mode;
+import org.apache.sling.feature.extension.apiregions.api.artifacts.VersionRule;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class CheckArtifactRulesTest {
+
+ private CheckArtifactRules analyser = new CheckArtifactRules();
+
+ private AnalyserTaskContext newContext(final Feature f) {
+ final AnalyserTaskContext context = Mockito.mock(AnalyserTaskContext.class);
+
+ when(context.getFeature()).thenReturn(f);
+
+ return context;
+ }
+
+ @Test public void testValidateFeatureNoRules() throws Exception {
+ final Feature f = new Feature(ArtifactId.parse("g:a:1"));
+
+ final AnalyserTaskContext context = newContext(f);
+ analyser.execute(context);
+
+ Mockito.verify(context, Mockito.never()).reportError(Mockito.anyString());
+ Mockito.verify(context, Mockito.atLeastOnce()).reportExtensionWarning(Mockito.eq(ArtifactRules.EXTENSION_NAME), Mockito.anyString());
+ }
+
+ @Test public void testValidateFeature() throws Exception {
+ final Feature f = new Feature(ArtifactId.parse("g:a:1"));
+ final Artifact bundle = new Artifact(ArtifactId.parse("g:b:1.1"));
+ f.getBundles().add(bundle);
+
+ final VersionRule r = new VersionRule();
+ r.setArtifactId(bundle.getId());
+ r.setMode(Mode.STRICT);
+ r.setMessage("foo");
+
+ final ArtifactRules rules = new ArtifactRules();
+ rules.getBundleVersionRules().add(r);
+
+ ArtifactRules.setArtifactRules(f, rules);
+ final AnalyserTaskContext context = newContext(f);
+ analyser.execute(context);
+
+ Mockito.verify(context, Mockito.atLeastOnce()).reportArtifactError(Mockito.eq(bundle.getId()), Mockito.eq(r.getMessage()));
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRulesTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRulesTest.java
new file mode 100644
index 0000000..d769ac9
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/ArtifactRulesTest.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.feature.extension.apiregions.api.artifacts;
+
+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 javax.json.Json;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.junit.Test;
+
+public class ArtifactRulesTest {
+
+ @Test public void testNullFeature() {
+ assertNull(ArtifactRules.getArtifactRules((Feature)null));
+ }
+
+ @Test public void testNullExtension() {
+ assertNull(ArtifactRules.getArtifactRules((Extension)null));
+ final Feature f = new Feature(ArtifactId.parse("g:a:1.0"));
+ assertNull(ArtifactRules.getArtifactRules(f));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testWrongExtensionType() {
+ final Feature f = new Feature(ArtifactId.parse("g:a:1.0"));
+ final Extension e = new Extension(ExtensionType.TEXT, ArtifactRules.EXTENSION_NAME, ExtensionState.OPTIONAL);
+ f.getExtensions().add(e);
+ ArtifactRules.getArtifactRules(f);
+ }
+
+ @Test public void testSetArtifactRules() {
+ final ArtifactRules rules = new ArtifactRules();
+ final Feature f = new Feature(ArtifactId.parse("g:a:1"));
+
+ assertNull(f.getExtensions().getByName(ArtifactRules.EXTENSION_NAME));
+
+ ArtifactRules.setArtifactRules(f, rules);
+ assertNotNull(f.getExtensions().getByName(ArtifactRules.EXTENSION_NAME));
+ assertNotNull(ArtifactRules.getArtifactRules(f));
+
+ ArtifactRules.setArtifactRules(f, null);
+ assertNull(f.getExtensions().getByName(ArtifactRules.EXTENSION_NAME));
+ }
+
+ @Test public void testClear() {
+ final ArtifactRules entity = new ArtifactRules();
+ entity.getAttributes().put("a", Json.createValue(5));
+ entity.setMode(Mode.LENIENT);
+ entity.getBundleVersionRules().add(new VersionRule());
+ entity.clear();
+ assertTrue(entity.getAttributes().isEmpty());
+ assertTrue(entity.getBundleVersionRules().isEmpty());
+ assertEquals(Mode.STRICT, entity.getMode());
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/VersionRuleTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/VersionRuleTest.java
new file mode 100644
index 0000000..dc4cb3b
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/artifacts/VersionRuleTest.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.feature.extension.apiregions.api.artifacts;
+
+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.io.IOException;
+
+import javax.json.Json;
+
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionState;
+import org.apache.sling.feature.ExtensionType;
+import org.junit.Test;
+import org.osgi.framework.Version;
+import org.osgi.framework.VersionRange;
+
+public class VersionRuleTest {
+
+ @Test public void testClear() {
+ final VersionRule entity = new VersionRule();
+ entity.getAttributes().put("a", Json.createValue(5));
+ entity.setAllowedVersionRanges(new VersionRange[] {new VersionRange("1.0")});
+ entity.setDeniedVersionRanges(new VersionRange[] {new VersionRange("3.0")});
+ entity.setMode(Mode.LENIENT);
+ entity.setMessage("msg");
+ entity.setArtifactId(ArtifactId.parse("g:a:1"));
+ entity.clear();
+ assertTrue(entity.getAttributes().isEmpty());
+ assertNull(entity.getAllowedVersionRanges());
+ assertNull(entity.getDeniedVersionRanges());
+ assertNull(entity.getMessage());
+ assertNull(entity.getArtifactId());
+ assertNull(entity.getMode());
+ }
+
+ @Test public void testFromJSONObject() throws IOException {
+ final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+ ext.setJSON("{ \"mode\" : \"LENIENT\", \"message\" : \"msg\", \"artifact-id\":\"g:a:1\","
+ + "\"allowed-version-ranges\":[\"1.0\"],\"denied-version-ranges\":[\"2.0\"]}");
+
+ final VersionRule entity = new VersionRule();
+ entity.fromJSONObject(ext.getJSONStructure().asJsonObject());
+ assertEquals(Mode.LENIENT, entity.getMode());
+ assertEquals("msg", entity.getMessage());
+ assertEquals(ArtifactId.parse("g:a:1"), entity.getArtifactId());
+ assertEquals(1, entity.getAllowedVersionRanges().length);
+ assertEquals(new VersionRange("1.0"), entity.getAllowedVersionRanges()[0]);
+ assertEquals(1, entity.getDeniedVersionRanges().length);
+ assertEquals(new VersionRange("2.0"), entity.getDeniedVersionRanges()[0]);
+ }
+
+ @Test public void testToJSONObject() throws IOException {
+ final VersionRule entity = new VersionRule();
+ entity.setMode(Mode.LENIENT);
+ entity.setMessage("msg");
+ entity.setArtifactId(ArtifactId.parse("g:a:1"));
+ entity.setAllowedVersionRanges(new VersionRange[] {new VersionRange("1.0.0")});
+ entity.setDeniedVersionRanges(new VersionRange[] {new VersionRange("2.0.0")});
+
+ final Extension ext = new Extension(ExtensionType.JSON, "a", ExtensionState.OPTIONAL);
+ ext.setJSON("{ \"mode\" : \"LENIENT\", \"artifact-id\":\"g:a:1\", \"message\" : \"msg\","
+ + "\"allowed-version-ranges\":[\"1.0.0\"],\"denied-version-ranges\":[\"2.0.0\"]}");
+
+ assertEquals(ext.getJSONStructure().asJsonObject(), entity.toJSONObject());
+ }
+
+ @Test public void testIsAllowedNoRanges() {
+ final VersionRule entity = new VersionRule();
+ assertFalse(entity.isAllowed(new Version("1.0")));
+ assertFalse(entity.isAllowed(new Version("1.3")));
+ assertFalse(entity.isAllowed(new Version("2.1")));
+ }
+
+ @Test public void testIsAllowedAllowedRange() {
+ final VersionRule entity = new VersionRule();
+ entity.setAllowedVersionRanges(new VersionRange[] {new VersionRange("[1.2, 2)")});
+ assertFalse(entity.isAllowed(new Version("1.0")));
+ assertTrue(entity.isAllowed(new Version("1.3")));
+ assertFalse(entity.isAllowed(new Version("2.1")));
+ }
+
+ @Test public void testIsAllowedAllowedDenied() {
+ final VersionRule entity = new VersionRule();
+ entity.setAllowedVersionRanges(new VersionRange[] {new VersionRange("[1.2, 2)")});
+ entity.setDeniedVersionRanges(new VersionRange[] {new VersionRange("[1.3.1,1.3.1]")});
+ assertFalse(entity.isAllowed(new Version("1.0")));
+ assertTrue(entity.isAllowed(new Version("1.3")));
+ assertFalse(entity.isAllowed(new Version("1.3.1")));
+ assertTrue(entity.isAllowed(new Version("1.3.2")));
+ assertFalse(entity.isAllowed(new Version("2.1")));
+ }
+}
diff --git a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntityTest.java b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntityTest.java
index 165a5ed..1679d38 100644
--- a/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntityTest.java
+++ b/src/test/java/org/apache/sling/feature/extension/apiregions/api/config/AttributeableEntityTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.sling.feature.extension.apiregions.api.config;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -24,6 +25,8 @@
import java.io.IOException;
import javax.json.Json;
+import javax.json.JsonArrayBuilder;
+import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;
import org.apache.sling.feature.Extension;
@@ -74,6 +77,20 @@
assertTrue(entity.getAttributes().isEmpty());
}
+ @Test public void testGetStringArray() throws IOException {
+ final AE entity = new AE();
+ assertNull(entity.getStringArray("foo"));
+ entity.getAttributes().put("foo", Json.createValue("bar"));
+ assertArrayEquals(new String[] {"bar"}, entity.getStringArray("foo"));
+ assertTrue(entity.getAttributes().isEmpty());
+ final JsonArrayBuilder jab = Json.createArrayBuilder();
+ jab.add("a");
+ jab.add("b");
+ entity.getAttributes().put("foo", jab.build());
+ assertArrayEquals(new String[] {"a", "b"}, entity.getStringArray("foo"));
+ assertTrue(entity.getAttributes().isEmpty());
+ }
+
@Test public void testGetBoolean() throws IOException {
final AE entity = new AE();
assertTrue(entity.getBoolean("foo", true));
@@ -122,4 +139,20 @@
// this is expected
}
}
+
+ @Test public void testSetStringArray() {
+ final AE entity = new AE();
+
+ JsonObjectBuilder builder = Json.createObjectBuilder();
+ entity.setStringArray(builder, "foo", null);
+ assertEquals("{}", builder.build().toString());
+
+ builder = Json.createObjectBuilder();
+ entity.setStringArray(builder, "foo", new String[0]);
+ assertEquals("{}", builder.build().toString());
+
+ builder = Json.createObjectBuilder();
+ entity.setStringArray(builder, "foo", new String[] {"a", "b"});
+ assertEquals("{\"foo\":[\"a\",\"b\"]}", builder.build().toString());
+ }
}
\ No newline at end of file