blob: 1d113e37d67b3e1c030a7bbaa38cc038e9e878dd [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.io.json;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.BiConsumer;
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonStructure;
import javax.json.JsonWriter;
import org.apache.felix.configurator.impl.json.JSMin;
import org.apache.felix.configurator.impl.json.JSONUtil;
import org.apache.felix.configurator.impl.json.TypeConverter;
import org.apache.felix.configurator.impl.model.Config;
import org.apache.felix.utils.resource.CapabilityImpl;
import org.apache.felix.utils.resource.RequirementImpl;
import org.apache.sling.feature.Artifact;
import org.apache.sling.feature.ArtifactId;
import org.apache.sling.feature.Bundles;
import org.apache.sling.feature.Configuration;
import org.apache.sling.feature.Configurations;
import org.apache.sling.feature.Extension;
import org.apache.sling.feature.ExtensionState;
import org.apache.sling.feature.ExtensionType;
import org.apache.sling.feature.Extensions;
import org.apache.sling.feature.MatchingRequirement;
import org.apache.sling.feature.Prototype;
import org.osgi.resource.Capability;
import org.osgi.resource.Resource;
/**
* Common methods for JSON reading.
*/
abstract class JSONReaderBase {
/** The optional location. */
protected final String location;
/** Exception prefix containing the location (if set) */
protected final String exceptionPrefix;
/**
* Private constructor
* @param location Optional location
*/
JSONReaderBase(final String location) {
this.location = location;
if ( location == null ) {
exceptionPrefix = "";
} else {
exceptionPrefix = location + " : ";
}
}
protected String minify(final Reader reader) throws IOException {
// minify JSON (remove comments)
final String contents;
try ( final Writer out = new StringWriter()) {
final JSMin min = new JSMin(reader, out);
min.jsmin();
contents = out.toString();
}
return contents;
}
/**
* Get the JSON object as a map, removing all comments that start with a '#' character
* @param json The JSON object to process
* @return A map representing the JSON object.
*/
protected Map<String, Object> getJsonMap(JsonObject json) {
@SuppressWarnings("unchecked")
Map<String, Object> m = (Map<String, Object>) JSONUtil.getValue(json);
removeComments(m);
return m;
}
private void removeComments(Map<String, Object> m) {
for(Iterator<Map.Entry<String, Object>> it = m.entrySet().iterator(); it.hasNext(); ) {
Entry<String, ?> entry = it.next();
if (entry.getKey().startsWith("#")) {
it.remove();
} else if (entry.getValue() instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> embedded = (Map<String, Object>) entry.getValue();
removeComments(embedded);
} else if (entry.getValue() instanceof Collection) {
Collection<?> embedded = (Collection<?>) entry.getValue();
removeComments(embedded);
}
}
}
@SuppressWarnings("unchecked")
private void removeComments(Collection<?> embedded) {
for (Object el : embedded) {
if (el instanceof Collection) {
removeComments((Collection<?>) el);
} else if (el instanceof Map) {
removeComments((Map<String, Object>) el);
}
}
}
protected String getProperty(final Map<String, Object> map, final String key) throws IOException {
final Object val = map.get(key);
if ( val != null ) {
checkType(key, val, String.class);
return val.toString();
}
return null;
}
/**
* Read the variables section
* @param map The map describing the feature or application
* @param kvMap The variables will be written to this Key Value Map
* @return The same variables as a normal map
* @throws IOException If the json is invalid.
*/
protected Map<String, String> readVariables(Map<String, Object> map, Map<String,String> kvMap) throws IOException {
HashMap<String, String> variables = new HashMap<>();
if (map.containsKey(JSONConstants.FEATURE_VARIABLES)) {
final Object variablesObj = map.get(JSONConstants.FEATURE_VARIABLES);
checkType(JSONConstants.FEATURE_VARIABLES, variablesObj, Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> vars = (Map<String, Object>) variablesObj;
for (final Map.Entry<String, Object> entry : vars.entrySet()) {
Object val = entry.getValue();
checkType("variable value", val, String.class, Boolean.class, Number.class, null);
String key = entry.getKey();
if (kvMap.get(key) != null) {
throw new IOException(this.exceptionPrefix + "Duplicate variable " + key);
}
String value = val == null ? null : val.toString();
kvMap.put(key, value);
variables.put(key, value);
}
}
return variables;
}
/**
* Read the bundles / start levels section
* @param map The map describing the feature
* @param container The bundles container
* @param configContainer The configurations container
* @throws IOException If the json is invalid.
*/
protected void readBundles(
final Map<String, Object> map,
final Bundles container,
final Configurations configContainer) throws IOException {
if ( map.containsKey(JSONConstants.FEATURE_BUNDLES)) {
final Object bundlesObj = map.get(JSONConstants.FEATURE_BUNDLES);
checkType(JSONConstants.FEATURE_BUNDLES, bundlesObj, List.class);
final List<Artifact> list = new ArrayList<>();
readArtifacts(JSONConstants.FEATURE_BUNDLES, "bundle", list, bundlesObj, configContainer);
for(final Artifact a : list) {
if ( container.containsExact(a.getId())) {
throw new IOException(exceptionPrefix + "Duplicate identical bundle " + a.getId().toMvnId());
}
try {
// check start order
a.getStartOrder();
} catch ( final IllegalArgumentException nfe) {
throw new IOException(exceptionPrefix + "Illegal start order '" + a.getMetadata().get(Artifact.KEY_START_ORDER) + "'");
}
container.add(a);
}
}
}
protected void readArtifacts(final String section,
final String artifactType,
final List<Artifact> artifacts,
final Object listObj,
final Configurations container)
throws IOException {
checkType(section, listObj, List.class);
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>) listObj;
for(final Object entry : list) {
final Artifact artifact;
checkType(artifactType, entry, Map.class, String.class);
if ( entry instanceof String ) {
// skip comments
if ( entry.toString().startsWith("#") ) {
continue;
}
artifact = new Artifact(ArtifactId.parse((String) entry));
} else {
@SuppressWarnings("unchecked")
final Map<String, Object> bundleObj = (Map<String, Object>) entry;
if ( !bundleObj.containsKey(JSONConstants.ARTIFACT_ID) ) {
throw new IOException(exceptionPrefix + " " + artifactType + " is missing required artifact id");
}
checkType(artifactType + " " + JSONConstants.ARTIFACT_ID, bundleObj.get(JSONConstants.ARTIFACT_ID), String.class);
final ArtifactId id = ArtifactId.parse(bundleObj.get(JSONConstants.ARTIFACT_ID).toString());
artifact = new Artifact(id);
for(final Map.Entry<String, Object> metadataEntry : bundleObj.entrySet()) {
final String key = metadataEntry.getKey();
if ( JSONConstants.ARTIFACT_KNOWN_PROPERTIES.contains(key) ) {
continue;
}
checkType(artifactType + " metadata " + key, metadataEntry.getValue(), String.class, Number.class, Boolean.class);
artifact.getMetadata().put(key, metadataEntry.getValue().toString());
}
if ( bundleObj.containsKey(JSONConstants.FEATURE_CONFIGURATIONS) ) {
checkType(artifactType + " configurations", bundleObj.get(JSONConstants.FEATURE_CONFIGURATIONS), Map.class);
addConfigurations(bundleObj, artifact, container);
}
}
artifacts.add(artifact);
}
}
protected void addConfigurations(final Map<String, Object> map,
final Artifact artifact,
final Configurations container) throws IOException {
final JSONUtil.Report report = new JSONUtil.Report();
@SuppressWarnings("unchecked")
final List<Config> configs = JSONUtil.readConfigurationsJSON(new TypeConverter(null),
0, "", (Map<String, ?>)map.get(JSONConstants.FEATURE_CONFIGURATIONS), report);
if ( !report.errors.isEmpty() || !report.warnings.isEmpty() ) {
final StringBuilder builder = new StringBuilder(exceptionPrefix);
builder.append("Errors in configurations:");
for(final String w : report.warnings) {
builder.append("\n");
builder.append(w);
}
for(final String e : report.errors) {
builder.append("\n");
builder.append(e);
}
throw new IOException(builder.toString());
}
for(final Config c : configs) {
final Configuration config = new Configuration(c.getPid());
final Enumeration<String> keyEnum = c.getProperties().keys();
while ( keyEnum.hasMoreElements() ) {
final String key = keyEnum.nextElement();
final Object val = c.getProperties().get(key);
config.getProperties().put(key, val);
}
if ( config.getProperties().get(Configuration.PROP_ARTIFACT_ID) != null ) {
throw new IOException(exceptionPrefix + "Configuration must not define property " + Configuration.PROP_ARTIFACT_ID);
}
if ( artifact != null ) {
config.getProperties().put(Configuration.PROP_ARTIFACT_ID, artifact.getId().toMvnId());
}
for(final Configuration current : container) {
if ( current.equals(config) ) {
throw new IOException(exceptionPrefix + "Duplicate configuration " + config);
}
}
container.add(config);
}
}
protected void readConfigurations(final Map<String, Object> map,
final Configurations container) throws IOException {
if ( map.containsKey(JSONConstants.FEATURE_CONFIGURATIONS) ) {
checkType(JSONConstants.FEATURE_CONFIGURATIONS, map.get(JSONConstants.FEATURE_CONFIGURATIONS), Map.class);
addConfigurations(map, null, container);
}
}
protected void readFrameworkProperties(final Map<String, Object> map,
final Map<String,String> container) throws IOException {
if ( map.containsKey(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES) ) {
final Object propsObj= map.get(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES);
checkType(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES, propsObj, Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> props = (Map<String, Object>) propsObj;
for(final Map.Entry<String, Object> entry : props.entrySet()) {
checkType("framework property value", entry.getValue(), String.class, Boolean.class, Number.class);
if ( container.get(entry.getKey()) != null ) {
throw new IOException(this.exceptionPrefix + "Duplicate framework property " + entry.getKey());
}
container.put(entry.getKey(), entry.getValue().toString());
}
}
}
protected void readExtensions(final Map<String, Object> map,
final List<String> keywords,
final Extensions container,
final Configurations configContainer) throws IOException {
final Set<String> keySet = new HashSet<>(map.keySet());
keySet.removeAll(keywords);
// the remaining keys are considered extensions!
for(final String key : keySet) {
final int pos = key.indexOf(':');
final String postfix = pos == -1 ? null : key.substring(pos + 1);
final int sep = (postfix == null ? key.indexOf('|') : postfix.indexOf('|'));
final String name;
final String type;
final String state;
if ( pos == -1 ) {
type = ExtensionType.ARTIFACTS.name();
if ( sep == -1 ) {
name = key;
state = ExtensionState.OPTIONAL.name();
} else {
name = key.substring(0, sep);
state = key.substring(sep + 1);
}
} else {
name = key.substring(0, pos);
if ( sep == -1 ) {
type = postfix;
state = ExtensionState.OPTIONAL.name();
} else {
type = postfix.substring(0, sep);
state = postfix.substring(sep + 1);
}
}
if ( JSONConstants.FEATURE_KNOWN_PROPERTIES.contains(name) ) {
throw new IOException(this.exceptionPrefix + "Extension is using reserved name : " + name);
}
if ( container.getByName(name) != null ) {
throw new IOException(exceptionPrefix + "Duplicate extension with name " + name);
}
final ExtensionType extType = ExtensionType.valueOf(type);
final ExtensionState extState;
if (ExtensionState.OPTIONAL.name().equalsIgnoreCase(state)) {
extState = ExtensionState.OPTIONAL;
} else if (ExtensionState.REQUIRED.name().equalsIgnoreCase(state)) {
extState = ExtensionState.REQUIRED;
} else if (ExtensionState.TRANSIENT.name().equalsIgnoreCase(state)) {
extState = ExtensionState.TRANSIENT;
} else {
final boolean opt = Boolean.valueOf(state).booleanValue();
extState = opt ? ExtensionState.REQUIRED : ExtensionState.OPTIONAL;
}
final Extension ext = new Extension(extType, name, extState);
final Object value = map.get(key);
switch ( extType ) {
case ARTIFACTS : final List<Artifact> list = new ArrayList<>();
readArtifacts("Extension " + name, "artifact", list, value, configContainer);
for(final Artifact a : list) {
if ( ext.getArtifacts().contains(a) ) {
throw new IOException(exceptionPrefix + "Duplicate artifact in extension " + name + " : " + a.getId().toMvnId());
}
ext.getArtifacts().add(a);
}
break;
case JSON : checkType("JSON Extension " + name, value, Map.class, List.class);
final JsonStructure struct = build(value);
try ( final StringWriter w = new StringWriter()) {
final JsonWriter jw = Json.createWriter(w);
jw.write(struct);
w.flush();
ext.setJSON(w.toString());
}
break;
case TEXT : checkType("Text Extension " + name, value, String.class, List.class);
if ( value instanceof String ) {
// string
ext.setText(value.toString());
} else {
// list (array of strings)
@SuppressWarnings("unchecked")
final List<Object> l = (List<Object>)value;
final StringBuilder sb = new StringBuilder();
for(final Object o : l) {
checkType("Text Extension " + name + ", value " + o, o, String.class);
sb.append(o.toString());
sb.append('\n');
}
ext.setText(sb.toString());
}
break;
}
container.add(ext);
}
}
private JsonStructure build(final Object value) {
if ( value instanceof List ) {
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>)value;
final JsonArrayBuilder builder = Json.createArrayBuilder();
for(final Object obj : list) {
if ( obj instanceof String ) {
builder.add(obj.toString());
} else if ( obj instanceof Long ) {
builder.add((Long)obj);
} else if ( obj instanceof Double ) {
builder.add((Double)obj);
} else if (obj instanceof Boolean ) {
builder.add((Boolean)obj);
} else if ( obj instanceof Map ) {
builder.add(build(obj));
} else if ( obj instanceof List ) {
builder.add(build(obj));
}
}
return builder.build();
} else if ( value instanceof Map ) {
@SuppressWarnings("unchecked")
final Map<String, Object> map = (Map<String, Object>)value;
final JsonObjectBuilder builder = Json.createObjectBuilder();
for(final Map.Entry<String, Object> entry : map.entrySet()) {
if ( entry.getValue() instanceof String ) {
builder.add(entry.getKey(), entry.getValue().toString());
} else if ( entry.getValue() instanceof Long ) {
builder.add(entry.getKey(), (Long)entry.getValue());
} else if ( entry.getValue() instanceof Double ) {
builder.add(entry.getKey(), (Double)entry.getValue());
} else if ( entry.getValue() instanceof Boolean ) {
builder.add(entry.getKey(), (Boolean)entry.getValue());
} else if ( entry.getValue() instanceof Map ) {
builder.add(entry.getKey(), build(entry.getValue()));
} else if ( entry.getValue() instanceof List ) {
builder.add(entry.getKey(), build(entry.getValue()));
}
}
return builder.build();
}
return null;
}
/**
* Check if the value is one of the provided types
* @param key A key for the error message
* @param val The value to check
* @param types The allowed types, can also include {@code null} if null is an allowed value.
* @throws IOException If the val is not of the specified types
*/
protected void checkType(final String key, final Object val, Class<?>...types) throws IOException {
boolean valid = false;
for(final Class<?> c : types) {
if (c == null) {
if ( val == null) {
valid = true;
break;
}
} else if ( c.isInstance(val) ) {
valid = true;
break;
}
}
if ( !valid ) {
throw new IOException(this.exceptionPrefix + "Key " + key + " is not one of the allowed types " + Arrays.toString(types) + " : " + val.getClass());
}
}
protected Prototype readPrototype(final Map<String, Object> map) throws IOException {
if ( map.containsKey(JSONConstants.FEATURE_PROTOTYPE)) {
final Object prototypeObj = map.get(JSONConstants.FEATURE_PROTOTYPE);
checkType(JSONConstants.FEATURE_PROTOTYPE, prototypeObj, Map.class, String.class);
final Prototype prototype;
if ( prototypeObj instanceof String ) {
final ArtifactId id = ArtifactId.parse(prototypeObj.toString());
prototype = new Prototype(id);
} else {
@SuppressWarnings("unchecked")
final Map<String, Object> obj = (Map<String, Object>) prototypeObj;
if ( !obj.containsKey(JSONConstants.ARTIFACT_ID) ) {
throw new IOException(exceptionPrefix + " prototype is missing required artifact id");
}
checkType("Prototype " + JSONConstants.ARTIFACT_ID, obj.get(JSONConstants.ARTIFACT_ID), String.class);
final ArtifactId id = ArtifactId.parse(obj.get(JSONConstants.ARTIFACT_ID).toString());
prototype = new Prototype(id);
if ( obj.containsKey(JSONConstants.PROTOTYPE_REMOVALS) ) {
checkType("Prototype removals", obj.get(JSONConstants.PROTOTYPE_REMOVALS), Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> removalObj = (Map<String, Object>) obj.get(JSONConstants.PROTOTYPE_REMOVALS);
if ( removalObj.containsKey(JSONConstants.FEATURE_BUNDLES) ) {
checkType("Prototype removal bundles", removalObj.get(JSONConstants.FEATURE_BUNDLES), List.class);
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>)removalObj.get(JSONConstants.FEATURE_BUNDLES);
for(final Object val : list) {
checkType("Prototype removal bundles", val, String.class);
if ( val.toString().startsWith("#")) {
continue;
}
prototype.getBundleRemovals().add(ArtifactId.parse(val.toString()));
}
}
if ( removalObj.containsKey(JSONConstants.FEATURE_CONFIGURATIONS) ) {
checkType("Prototype removal configuration", removalObj.get(JSONConstants.FEATURE_CONFIGURATIONS), List.class);
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>)removalObj.get(JSONConstants.FEATURE_CONFIGURATIONS);
for(final Object val : list) {
checkType("Prototype removal configuration", val, String.class);
prototype.getConfigurationRemovals().add(val.toString());
}
}
if ( removalObj.containsKey(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES) ) {
checkType("Prototype removal framework properties", removalObj.get(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES), List.class);
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>)removalObj.get(JSONConstants.FEATURE_FRAMEWORK_PROPERTIES);
for(final Object val : list) {
checkType("Prototype removal framework properties", val, String.class);
prototype.getFrameworkPropertiesRemovals().add(val.toString());
}
}
if ( removalObj.containsKey(JSONConstants.PROTOTYPE_EXTENSION_REMOVALS) ) {
checkType("Prototype removal extensions", removalObj.get(JSONConstants.PROTOTYPE_EXTENSION_REMOVALS), List.class);
@SuppressWarnings("unchecked")
final List<Object> list = (List<Object>)removalObj.get(JSONConstants.PROTOTYPE_EXTENSION_REMOVALS);
for(final Object val : list) {
checkType("Prototype removal extension", val, String.class, Map.class);
if ( val instanceof String ) {
if ( val.toString().startsWith("#")) {
continue;
}
prototype.getExtensionRemovals().add(val.toString());
} else {
@SuppressWarnings("unchecked")
final Map<String, Object> removalMap = (Map<String, Object>)val;
final Object nameObj = removalMap.get("name");
checkType("Prototype removal extension", nameObj, String.class);
if ( removalMap.containsKey("artifacts") ) {
checkType("Prototype removal extension artifacts", removalMap.get("artifacts"), List.class);
@SuppressWarnings("unchecked")
final List<Object> artifactList = (List<Object>)removalMap.get("artifacts");
final List<ArtifactId> ids = new ArrayList<>();
for(final Object aid : artifactList) {
checkType("Prototype removal extension artifact", aid, String.class);
ids.add(ArtifactId.parse(aid.toString()));
}
prototype.getArtifactExtensionRemovals().put(nameObj.toString(), ids);
} else {
prototype.getExtensionRemovals().add(nameObj.toString());
}
}
}
}
}
}
return prototype;
}
return null;
}
protected void readRequirements(Map<String, Object> map, final List<MatchingRequirement> container)
throws IOException {
if ( map.containsKey(JSONConstants.FEATURE_REQUIREMENTS)) {
final Object reqObj = map.get(JSONConstants.FEATURE_REQUIREMENTS);
checkType(JSONConstants.FEATURE_REQUIREMENTS, reqObj, List.class);
@SuppressWarnings("unchecked")
final List<Object> requirements = (List<Object>)reqObj;
for(final Object req : requirements) {
checkType("Requirement", req, Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> obj = (Map<String, Object>) req;
if ( !obj.containsKey(JSONConstants.REQCAP_NAMESPACE) ) {
throw new IOException(this.exceptionPrefix + "Namespace is missing for requirement");
}
checkType("Requirement namespace", obj.get(JSONConstants.REQCAP_NAMESPACE), String.class);
Map<String, Object> attrMap = new HashMap<>();
if ( obj.containsKey(JSONConstants.REQCAP_ATTRIBUTES) ) {
checkType("Requirement attributes", obj.get(JSONConstants.REQCAP_ATTRIBUTES), Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> attrs = (Map<String, Object>)obj.get(JSONConstants.REQCAP_ATTRIBUTES);
attrs.forEach(rethrowBiConsumer((key, value) -> ManifestUtils.unmarshalAttribute(key, value, attrMap::put)));
}
Map<String, String> dirMap = new HashMap<>();
if ( obj.containsKey(JSONConstants.REQCAP_DIRECTIVES) ) {
checkType("Requirement directives", obj.get(JSONConstants.REQCAP_DIRECTIVES), Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> dirs = (Map<String, Object>)obj.get(JSONConstants.REQCAP_DIRECTIVES);
dirs.forEach(rethrowBiConsumer((key, value) -> ManifestUtils.unmarshalDirective(key, value, dirMap::put)));
}
final MatchingRequirement r = new MatchingRequirementImpl(null,
obj.get(JSONConstants.REQCAP_NAMESPACE).toString(), dirMap, attrMap);
container.add(r);
}
}
}
protected void readCapabilities(Map<String, Object> map, final List<Capability> container) throws IOException {
if ( map.containsKey(JSONConstants.FEATURE_CAPABILITIES)) {
final Object capObj = map.get(JSONConstants.FEATURE_CAPABILITIES);
checkType(JSONConstants.FEATURE_CAPABILITIES, capObj, List.class);
@SuppressWarnings("unchecked")
final List<Object> capabilities = (List<Object>)capObj;
for(final Object cap : capabilities) {
checkType("Capability", cap, Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> obj = (Map<String, Object>) cap;
if ( !obj.containsKey(JSONConstants.REQCAP_NAMESPACE) ) {
throw new IOException(this.exceptionPrefix + "Namespace is missing for capability");
}
checkType("Capability namespace", obj.get(JSONConstants.REQCAP_NAMESPACE), String.class);
Map<String, Object> attrMap = new HashMap<>();
if ( obj.containsKey(JSONConstants.REQCAP_ATTRIBUTES) ) {
checkType("Capability attributes", obj.get(JSONConstants.REQCAP_ATTRIBUTES), Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> attrs = (Map<String, Object>)obj.get(JSONConstants.REQCAP_ATTRIBUTES);
attrs.forEach(rethrowBiConsumer((key, value) -> ManifestUtils.unmarshalAttribute(key, value, attrMap::put)));
}
Map<String, String> dirMap = new HashMap<>();
if ( obj.containsKey(JSONConstants.REQCAP_DIRECTIVES) ) {
checkType("Capability directives", obj.get(JSONConstants.REQCAP_DIRECTIVES), Map.class);
@SuppressWarnings("unchecked")
final Map<String, Object> dirs = (Map<String, Object>) obj.get(JSONConstants.REQCAP_DIRECTIVES);
dirs.forEach(rethrowBiConsumer((key, value) -> ManifestUtils.unmarshalDirective(key, value, dirMap::put)));
}
final Capability c = new CapabilityImpl(null, obj.get(JSONConstants.REQCAP_NAMESPACE).toString(), dirMap, attrMap);
container.add(c);
}
}
}
@FunctionalInterface
private interface BiConsumer_WithExceptions<T, V, E extends Exception> {
void accept(T t, V u) throws E;
}
private static <T, V, E extends Exception> BiConsumer<T, V> rethrowBiConsumer(BiConsumer_WithExceptions<T, V, E> biConsumer) {
return (t, u) -> {
try {
biConsumer.accept(t, u);
} catch (Exception exception) {
throwAsUnchecked(exception);
}
};
}
@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
throw (E) exception;
}
private static class MatchingRequirementImpl extends RequirementImpl implements MatchingRequirement {
public MatchingRequirementImpl(Resource res, String ns, Map<String, String> dirs, Map<String, Object> attrs) {
super(res, ns, dirs, attrs);
}
}
}