blob: 6626287702017131fafb024c718cf22c6fb022eb [file] [log] [blame]
/*
* 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.config;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
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.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;
/**
* A configuration api describes the set of supported OSGi
* configurations and framework properties. This object can be
* stored as an extension inside a feature model.
* 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.
* {@link #setConfigurationApi(Feature, ConfigurationApi)} can be used to update
* the feature.
* @param feature The feature
* @return The configuration api or {@code null}.
* @throws IllegalArgumentException If the extension is wrongly formatted
*/
public static ConfigurationApi getConfigurationApi(final Feature feature) {
final Extension ext = feature == null ? null : feature.getExtensions().getByName(EXTENSION_NAME);
return getConfigurationApi(ext);
}
/**
* Get the configuration api from the extension.
* If the configuration api is updated, the containing extension is left untouched.
* @param ext The extension
* @return The configuration api or {@code null} if the extension is {@code null}.
* @throws IllegalArgumentException If the extension is wrongly formatted
*/
public static ConfigurationApi getConfigurationApi(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 ConfigurationApi result = new ConfigurationApi();
result.fromJSONObject(ext.getJSONStructure().asJsonObject());
return result;
} catch ( final IOException ioe) {
throw new IllegalArgumentException(ioe.getMessage(), ioe);
}
}
/**
* Set the configuration api as an extension to the feature
* @param feature The feature
* @param api The configuration api
* @throws IllegalStateException If the feature has already an extension of a wrong type
* @throws IllegalArgumentException If the api configuration can't be serialized to JSON
*/
public static void setConfigurationApi(final Feature feature, final ConfigurationApi api) {
Extension ext = feature.getExtensions().getByName(EXTENSION_NAME);
if ( api == 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(api.toJSONObject());
} catch ( final IOException ioe) {
throw new IllegalArgumentException(ioe);
}
}
}
/** The map of configurations */
private final Map<String, ConfigurationDescription> configurations = new LinkedHashMap<>();
/** The map of factory configurations */
private final Map<String, FactoryConfigurationDescription> factories = new LinkedHashMap<>();
/** The map of configuration additions @since 1.8 */
private final Map<String, ConfigurationDescriptionAddition> configurationAdditions = new LinkedHashMap<>();
/** The map of factory configuration additions @since 1.8 */
private final Map<String, FactoryConfigurationDescriptionAddition> factoryAdditions = new LinkedHashMap<>();
/** The map of framework properties */
private final Map<String, FrameworkPropertyDescription> frameworkProperties = new LinkedHashMap<>();
/** The set of internal configuration names */
private final Set<String> internalConfigurations = new TreeSet<>();
/** The set of internal factory configuration names */
private final Set<String> internalFactories = new TreeSet<>();
/** 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.
* @since 1.2
*/
private Mode mode;
public ConfigurationApi() {
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.configurations.clear();
this.factories.clear();
this.frameworkProperties.clear();
this.internalConfigurations.clear();
this.internalFactories.clear();
this.internalFrameworkProperties.clear();
this.setRegion(null);
this.getFeatureToRegionCache().clear();
this.getConfigurationDescriptionAdditions().clear();
this.getFactoryConfigurationDescriptionAdditions().clear();
}
/**
* 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 typeVal = this.getString(InternalConstants.KEY_REGION);
if ( typeVal != null ) {
this.setRegion(Region.valueOf(typeVal.toUpperCase()));
}
JsonValue val;
val = this.getAttributes().remove(InternalConstants.KEY_CONFIGURATIONS);
if ( val != null ) {
for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
final ConfigurationDescription cfg = new ConfigurationDescription();
cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
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()) {
final FactoryConfigurationDescription cfg = new FactoryConfigurationDescription();
cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
this.getFactoryConfigurationDescriptions().put(innerEntry.getKey(), cfg);
}
}
val = this.getAttributes().remove(InternalConstants.KEY_FWK_PROPERTIES);
if ( val != null ) {
for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
final FrameworkPropertyDescription cfg = new FrameworkPropertyDescription();
cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
this.getFrameworkPropertyDescriptions().put(innerEntry.getKey(), cfg);
}
}
val = this.getAttributes().remove(InternalConstants.KEY_INTERNAL_CONFIGURATIONS);
if ( val != null ) {
for(final JsonValue innerVal : val.asJsonArray()) {
this.getInternalConfigurations().add(getString(innerVal));
}
}
val = this.getAttributes().remove(InternalConstants.KEY_INTERNAL_FACTORIES);
if ( val != null ) {
for(final JsonValue innerVal : val.asJsonArray()) {
this.getInternalFactoryConfigurations().add(getString(innerVal));
}
}
val = this.getAttributes().remove(InternalConstants.KEY_INTERNAL_FWK_PROPERTIES);
if ( val != null ) {
for(final JsonValue innerVal : val.asJsonArray()) {
this.getInternalFrameworkProperties().add(getString(innerVal));
}
}
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()),
Region.valueOf(getString(innerEntry.getValue()).toUpperCase()));
}
}
final String modeVal = this.getString(InternalConstants.KEY_MODE);
if ( modeVal != null ) {
this.setMode(Mode.valueOf(modeVal.toUpperCase()));
}
val = this.getAttributes().remove(InternalConstants.KEY_CONFIGURATION_ADDITIONS);
if ( val != null ) {
for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
final ConfigurationDescriptionAddition cfg = new ConfigurationDescriptionAddition();
cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
this.getConfigurationDescriptionAdditions().put(innerEntry.getKey(), cfg);
}
}
val = this.getAttributes().remove(InternalConstants.KEY_FACTORY_ADDITIONS);
if ( val != null ) {
for(final Map.Entry<String, JsonValue> innerEntry : val.asJsonObject().entrySet()) {
final FactoryConfigurationDescriptionAddition cfg = new FactoryConfigurationDescriptionAddition();
cfg.fromJSONObject(innerEntry.getValue().asJsonObject());
this.getFactoryConfigurationDescriptionAdditions().put(innerEntry.getKey(), cfg);
}
}
} catch (final JsonException | IllegalArgumentException e) {
throw new IOException(e);
}
}
/**
* Get the configuration descriptions
* @return Mutable map of configuration descriptions by pid
*/
public Map<String, ConfigurationDescription> getConfigurationDescriptions() {
return configurations;
}
/**
* Get the factory configuration descriptions
* @return Mutable map of factory descriptions by factory pid
*/
public Map<String, FactoryConfigurationDescription> getFactoryConfigurationDescriptions() {
return factories;
}
/**
* Get the framework properties
* @return Mutable map of framework properties
*/
public Map<String, FrameworkPropertyDescription> getFrameworkPropertyDescriptions() {
return frameworkProperties;
}
/**
* Get the internal configuration pids
* @return Mutable set of internal configuration pids
* @deprecated Please use empty configuration descriptions via {@link #getConfigurationDescriptions()}
*/
@Deprecated
public Set<String> getInternalConfigurations() {
return internalConfigurations;
}
/**
* Get the internal factory pids
* @return Mutable set of internal factory configuration pids
* @deprecated Please use empty factory configuration descriptions via {@link #getFactoryConfigurationDescriptions()}
*/
@Deprecated
public Set<String> getInternalFactoryConfigurations() {
return internalFactories;
}
/**
* Check if the configuration is an internal configuration
* @param pid The pid
* @return {@code true} if it is an internal configuration
* @since 1.7.0
*/
public boolean isInternalConfiguration(final String pid) {
boolean result = this.internalConfigurations.contains(pid);
if ( !result ) {
final ConfigurationDescription desc = this.configurations.get(pid);
if ( desc != null ) {
result = desc.getPropertyDescriptions().isEmpty();
}
}
return result;
}
/**
* Check if the factory configuration is an internal configuration
* @param factoryPid The factory pid
* @param name Optional name of the configuration
* @return {@code true} if it is an internal factory configuration
* @since 1.7.0
*/
public boolean isInternalFactoryConfiguration(final String factoryPid, final String name) {
boolean result = this.internalFactories.contains(factoryPid);
if ( !result ) {
final FactoryConfigurationDescription desc = this.factories.get(factoryPid);
if ( desc != null ) {
result = desc.getPropertyDescriptions().isEmpty();
if ( !result && name != null ) {
result = desc.getInternalNames().contains(name);
}
}
}
return result;
}
/**
* Get the internal framework property names
* @return Mutable set of internal framework property names
*/
public Set<String> getInternalFrameworkProperties() {
return internalFrameworkProperties;
}
/**
* Get the api configuration region
* @return The region or {@code null}
*/
public Region getRegion() {
return this.region;
}
/**
* Set the api configuration region
* @param value The region to set
*/
public void setRegion(final Region value) {
this.region = value;
}
/**
* Detect the region, either return the stored region or the default (GLOBAL)
* @return The region
* @since 1.1
*/
public Region detectRegion() {
if ( this.getRegion() != null ) {
return this.getRegion();
}
return Region.GLOBAL;
}
/**
* Get the feature to region cache to keep track of regions for origin features
* @return The cache
* @since 1.1.
*/
public Map<ArtifactId, Region> getFeatureToRegionCache() {
return this.regionCache;
}
/**
* Get the validation mode.
* The default is {@link Mode#STRICT}
* @return The mode
* @since 1.2
*/
public Mode getMode() {
return this.mode;
}
/**
* Set the validation mode
* @param value The validation mode
* @since 1.2
*/
public void setMode(final Mode value) {
this.mode = value == null ? Mode.STRICT : value;
}
/**
* Convert this object into JSON
*
* @return The json object builder
* @throws IOException If generating the JSON fails
*/
@Override
protected JsonObjectBuilder createJson() throws IOException {
final JsonObjectBuilder objBuilder = super.createJson();
if ( this.getRegion() != null ) {
objBuilder.add(InternalConstants.KEY_REGION, this.getRegion().name());
}
if ( !this.getConfigurationDescriptions().isEmpty() ) {
final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
for(final Map.Entry<String, ConfigurationDescription> entry : this.getConfigurationDescriptions().entrySet()) {
propBuilder.add(entry.getKey(), entry.getValue().createJson());
}
objBuilder.add(InternalConstants.KEY_CONFIGURATIONS, propBuilder);
}
if ( !this.getFactoryConfigurationDescriptions().isEmpty() ) {
final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
for(final Map.Entry<String, FactoryConfigurationDescription> entry : this.getFactoryConfigurationDescriptions().entrySet()) {
propBuilder.add(entry.getKey(), entry.getValue().createJson());
}
objBuilder.add(InternalConstants.KEY_FACTORIES, propBuilder);
}
if ( !this.getFrameworkPropertyDescriptions().isEmpty() ) {
final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
for(final Map.Entry<String, FrameworkPropertyDescription> entry : this.getFrameworkPropertyDescriptions().entrySet()) {
propBuilder.add(entry.getKey(), entry.getValue().createJson());
}
objBuilder.add(InternalConstants.KEY_FWK_PROPERTIES, propBuilder);
}
if ( !this.getInternalConfigurations().isEmpty() ) {
final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for(final String n : this.getInternalConfigurations()) {
arrayBuilder.add(n);
}
objBuilder.add(InternalConstants.KEY_INTERNAL_CONFIGURATIONS, arrayBuilder);
}
if ( !this.getInternalFactoryConfigurations().isEmpty() ) {
final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for(final String n : this.getInternalFactoryConfigurations()) {
arrayBuilder.add(n);
}
objBuilder.add(InternalConstants.KEY_INTERNAL_FACTORIES, arrayBuilder);
}
if ( !this.getInternalFrameworkProperties().isEmpty() ) {
final JsonArrayBuilder arrayBuilder = Json.createArrayBuilder();
for(final String n : this.getInternalFrameworkProperties()) {
arrayBuilder.add(n);
}
objBuilder.add(InternalConstants.KEY_INTERNAL_FWK_PROPERTIES, arrayBuilder);
}
if ( !this.getFeatureToRegionCache().isEmpty()) {
final JsonObjectBuilder cacheBuilder = Json.createObjectBuilder();
for(final Map.Entry<ArtifactId, Region> entry : this.getFeatureToRegionCache().entrySet()) {
cacheBuilder.add(entry.getKey().toMvnId(), entry.getValue().name());
}
objBuilder.add(InternalConstants.KEY_REGION_CACHE, cacheBuilder);
}
if ( this.getMode() != Mode.STRICT ) {
objBuilder.add(InternalConstants.KEY_MODE, this.getMode().name());
}
if ( !this.getConfigurationDescriptionAdditions().isEmpty() ) {
final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
for(final Map.Entry<String, ConfigurationDescriptionAddition> entry : this.getConfigurationDescriptionAdditions().entrySet()) {
propBuilder.add(entry.getKey(), entry.getValue().createJson());
}
objBuilder.add(InternalConstants.KEY_CONFIGURATION_ADDITIONS, propBuilder);
}
if ( !this.getFactoryConfigurationDescriptionAdditions().isEmpty() ) {
final JsonObjectBuilder propBuilder = Json.createObjectBuilder();
for(final Map.Entry<String, FactoryConfigurationDescriptionAddition> entry : this.getFactoryConfigurationDescriptionAdditions().entrySet()) {
propBuilder.add(entry.getKey(), entry.getValue().createJson());
}
objBuilder.add(InternalConstants.KEY_FACTORY_ADDITIONS, propBuilder);
}
return objBuilder;
}
/**
* Get the map of configuration description additions
* @return The map, might be empty
* @since 1.8
*/
public Map<String, ConfigurationDescriptionAddition> getConfigurationDescriptionAdditions() {
return this.configurationAdditions;
}
/**
* Get the map of factory configuration description additions
* @return The map, might be empty
* @since 1.8
*/
public Map<String, FactoryConfigurationDescriptionAddition> getFactoryConfigurationDescriptionAdditions() {
return this.factoryAdditions;
}
}