| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.sling.feature.extension.apiregions.api; |
| |
| import java.io.IOException; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.stream.Stream; |
| |
| import javax.json.Json; |
| import javax.json.JsonArray; |
| import javax.json.JsonArrayBuilder; |
| import javax.json.JsonException; |
| import javax.json.JsonObject; |
| import javax.json.JsonObjectBuilder; |
| import javax.json.JsonReader; |
| import javax.json.JsonString; |
| import javax.json.JsonValue; |
| import javax.json.JsonValue.ValueType; |
| import javax.json.JsonWriter; |
| |
| import org.apache.sling.feature.Artifact; |
| import org.apache.sling.feature.ArtifactId; |
| import org.apache.sling.feature.Extension; |
| import org.apache.sling.feature.ExtensionType; |
| import org.apache.sling.feature.Feature; |
| |
| /** |
| * Configuration of API regions for Java API. |
| * |
| * This class is not thread safe. |
| */ |
| public class ApiRegions { |
| |
| /** The name of the api regions extension. */ |
| public static final String EXTENSION_NAME = "api-regions"; |
| |
| private static final String NAME_KEY = "name"; |
| |
| private static final String EXPORTS_KEY = "exports"; |
| |
| private final List<ApiRegion> regions = new ArrayList<>(); |
| |
| /** |
| * Return the list of regions |
| * |
| * @return Unmodifiable list of regions, might be empty |
| */ |
| public List<ApiRegion> listRegions() { |
| return Collections.unmodifiableList(this.regions); |
| } |
| |
| /** |
| * Get the root regions. The root is the region which does not have a parent |
| * |
| * @return The root region or {@code null} |
| */ |
| public ApiRegion getRoot() { |
| if (this.regions.isEmpty()) { |
| return null; |
| } |
| return this.regions.get(0); |
| } |
| |
| /** |
| * Check if any region exists |
| * |
| * @return {@code true} if it has any region |
| */ |
| public boolean isEmpty() { |
| return this.regions.isEmpty(); |
| } |
| |
| /** |
| * Add the region. The region is only added if there isn't already a region with |
| * the same name |
| * |
| * @param region The region to add |
| * @return {@code true} if the region could be added, {@code false} otherwise |
| */ |
| public boolean add(final ApiRegion region) { |
| return add(this.regions.size(), region); |
| } |
| |
| /** |
| * Add the region. The region is only added if there isn't already a region with |
| * the same name |
| * |
| * @param idx The position to add |
| * @param region The region to add |
| * @return {@code true} if the region could be added, {@code false} otherwise |
| */ |
| public boolean add(final int idx, final ApiRegion region) { |
| for (final ApiRegion c : this.regions) { |
| if (c.getName().equals(region.getName())) { |
| return false; |
| } |
| } |
| Set<ArtifactId> origins = new LinkedHashSet<>(Arrays.asList(region.getFeatureOrigins())); |
| |
| this.regions.stream() |
| .filter( |
| existingRegion -> |
| { |
| ArtifactId[] targetOrigins = existingRegion.getFeatureOrigins(); |
| return (targetOrigins.length == 0 && origins.isEmpty()) |
| || Stream.of(targetOrigins).anyMatch(origins::contains); |
| } |
| ).reduce((a,b) -> b).ifPresent(region::setParent); |
| |
| this.regions.add(idx, region); |
| return true; |
| } |
| |
| |
| /** |
| * Get a named region |
| * |
| * @param name The name |
| * @return The region or {@code null} |
| */ |
| public ApiRegion getRegionByName(final String name) { |
| ApiRegion found = null; |
| |
| for (final ApiRegion c : this.regions) { |
| if (c.getName().equals(name)) { |
| found = c; |
| break; |
| } |
| } |
| |
| return found; |
| } |
| |
| public ApiRegion[] getRegionsByFeature(final ArtifactId featureId) { |
| return this.regions.stream().filter( |
| region -> Stream.of(region.getFeatureOrigins()).anyMatch(featureId::equals) |
| ).toArray(ApiRegion[]::new); |
| } |
| |
| /** |
| * Get the names of the regions |
| * |
| * @return The list of regions, might be empty |
| */ |
| public List<String> getRegionNames() { |
| final List<String> names = new ArrayList<>(); |
| for (final ApiRegion c : this.regions) { |
| names.add(c.getName()); |
| } |
| return Collections.unmodifiableList(names); |
| } |
| |
| /** |
| * Convert regions into json |
| * |
| * @return The json array |
| * @throws IOException If generating the JSON fails |
| */ |
| public JsonArray toJSONArray() throws IOException { |
| final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); |
| |
| for (final ApiRegion region : this.regions) { |
| final JsonObjectBuilder regionBuilder = Json.createObjectBuilder(); |
| regionBuilder.add(NAME_KEY, region.getName()); |
| |
| if (!region.listExports().isEmpty()) { |
| final JsonArrayBuilder expArrayBuilder = Json.createArrayBuilder(); |
| for (final ApiExport exp : region.listExports()) { |
| expArrayBuilder.add(exp.toJSONValue()); |
| } |
| |
| regionBuilder.add(EXPORTS_KEY, expArrayBuilder); |
| } |
| ArtifactId[] origins = region.getFeatureOrigins(); |
| if (origins.length > 0) { |
| final JsonArrayBuilder originBuilder = Json.createArrayBuilder(); |
| for (ArtifactId origin : origins) { |
| originBuilder.add(origin.toMvnId()); |
| } |
| regionBuilder.add(Artifact.KEY_FEATURE_ORIGINS, originBuilder); |
| } |
| for (final Map.Entry<String, String> entry : region.getProperties().entrySet()) { |
| regionBuilder.add(entry.getKey(), entry.getValue()); |
| } |
| |
| arrayBuilder.add(regionBuilder); |
| } |
| |
| return arrayBuilder.build(); |
| } |
| |
| /** |
| * Convert regions into json |
| * |
| * @return The json array as a string |
| * @throws IOException If generating the JSON fails |
| */ |
| public String toJSON() throws IOException { |
| final JsonArray array = this.toJSONArray(); |
| try (final StringWriter stringWriter = new StringWriter(); |
| final JsonWriter writer = Json.createWriter(stringWriter)) { |
| writer.writeArray(array); |
| return stringWriter.toString(); |
| } |
| } |
| |
| /** |
| * Parse a JSON array into an api regions object |
| * |
| * @param json The json as a string |
| * @return The api regions |
| * @throws IOException If the json could not be parsed |
| */ |
| public static ApiRegions parse(final String json) throws IOException { |
| try (final JsonReader reader = Json.createReader(new StringReader(json))) { |
| return parse(reader.readArray()); |
| } |
| } |
| |
| /** |
| * Parse a JSON array into an api regions object |
| * |
| * @param json The json |
| * @return The api regions |
| * @throws IOException If the json could not be parsed |
| */ |
| public static ApiRegions parse(final JsonArray json) throws IOException { |
| try { |
| final ApiRegions regions = new ApiRegions(); |
| |
| for (final JsonValue value : json) { |
| if (value.getValueType() != ValueType.OBJECT) { |
| throw new IOException("Illegal api regions json " + json); |
| } |
| final JsonObject obj = (JsonObject) value; |
| |
| final ApiRegion region = new ApiRegion(obj.getString(NAME_KEY)); |
| |
| for (final Map.Entry<String, JsonValue> entry : obj.entrySet()) { |
| if (NAME_KEY.equals(entry.getKey())) { |
| continue; // already set |
| } else if (entry.getKey().equals(EXPORTS_KEY)) { |
| for (final JsonValue e : (JsonArray) entry.getValue()) { |
| ApiExport.fromJson(region, e); |
| } |
| } else if (entry.getKey().equals(Artifact.KEY_FEATURE_ORIGINS)) { |
| final Set<ArtifactId> origins = new LinkedHashSet<>(); |
| for (final JsonValue origin : (JsonArray) entry.getValue()) { |
| origins.add(ArtifactId.fromMvnId(((JsonString) origin).getString())); |
| } |
| region.setFeatureOrigins(origins.toArray(new ArtifactId[0])); |
| |
| // everything else is stored as a string property |
| } else { |
| region.getProperties().put(entry.getKey(), ((JsonString) entry.getValue()).getString()); |
| } |
| } |
| if (!regions.add(region)) { |
| throw new IOException("Region " + region.getName() + " is defined twice"); |
| } |
| } |
| return regions; |
| } catch (final JsonException | IllegalArgumentException e) { |
| throw new IOException(e); |
| } |
| } |
| |
| /** |
| * Get the api regions from the feature - if it exists. |
| * @param feature The feature |
| * @return The api regions or {@code null}. |
| * @throws IllegalArgumentException If the extension is wrongly formatted |
| * @since 1.1 |
| */ |
| public static ApiRegions getApiRegions(final Feature feature) { |
| final Extension ext = feature == null ? null : feature.getExtensions().getByName(EXTENSION_NAME); |
| return getApiRegions(ext); |
| } |
| |
| /** |
| * Get the api regions from the extension. |
| * @param ext The extension |
| * @return The api regions or {@code null}. |
| * @throws IllegalArgumentException If the extension is wrongly formatted |
| * @since 1.1 |
| */ |
| public static ApiRegions getApiRegions(final Extension ext) { |
| if ( ext == null ) { |
| return null; |
| } |
| if ( ext.getType() != ExtensionType.JSON ) { |
| throw new IllegalArgumentException("Extension " + ext.getName() + " must have JSON type"); |
| } |
| try { |
| return parse(ext.getJSONStructure().asJsonArray()); |
| } catch ( final IOException ioe) { |
| throw new IllegalArgumentException(ioe.getMessage(), ioe); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "ApiRegions [regions=" + regions + "]"; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(regions); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| ApiRegions other = (ApiRegions) obj; |
| return Objects.equals(regions, other.regions); |
| } |
| } |