blob: f23e4fc342a1748d9fefb006bb3307220449d35f [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.osgi.impl;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonArrayBuilder;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;
import org.apache.felix.cm.json.impl.JsonSupport;
import org.apache.felix.cm.json.impl.TypeConverter;
import org.osgi.service.feature.BuilderFactory;
import org.osgi.service.feature.Feature;
import org.osgi.service.feature.FeatureArtifact;
import org.osgi.service.feature.FeatureArtifactBuilder;
import org.osgi.service.feature.FeatureBuilder;
import org.osgi.service.feature.FeatureBundle;
import org.osgi.service.feature.FeatureBundleBuilder;
import org.osgi.service.feature.FeatureConfiguration;
import org.osgi.service.feature.FeatureConfigurationBuilder;
import org.osgi.service.feature.FeatureExtension;
import org.osgi.service.feature.FeatureExtensionBuilder;
import org.osgi.service.feature.FeatureService;
import org.osgi.service.feature.ID;
public class FeatureServiceImpl implements FeatureService {
private final BuilderFactoryImpl builderFactory = new BuilderFactoryImpl();
public BuilderFactory getBuilderFactory() {
return builderFactory;
}
@Override
public ID getIDfromMavenCoordinates(String mavenID) {
return IDImpl.fromMavenID(mavenID);
}
@Override
public ID getID(String groupId, String artifactId, String version) {
return new IDImpl(groupId, artifactId, version, null, null);
}
@Override
public ID getID(String groupId, String artifactId, String version, String type) {
return new IDImpl(groupId, artifactId, version, type, null);
}
@Override
public ID getID(String groupId, String artifactId, String version, String type, String classifier) {
return new IDImpl(groupId, artifactId, version, type, classifier);
}
public Feature readFeature(Reader jsonReader) throws IOException {
JsonObject json = Json.createReader(
JsonSupport.createCommentRemovingReader(jsonReader)).readObject();
String id = json.getString("id");
FeatureBuilder builder = builderFactory.newFeatureBuilder(getIDfromMavenCoordinates(id));
builder.setName(json.getString("name", null));
builder.setDescription(json.getString("description", null));
builder.setDocURL(json.getString("docURL", null));
builder.setLicense(json.getString("license", null));
builder.setSCM(json.getString("scm", null));
builder.setVendor(json.getString("vendor", null));
builder.setComplete(json.getBoolean("complete", false));
builder.addBundles(getBundles(json));
builder.addCategories(getCategories(json));
builder.addConfigurations(getConfigurations(json));
builder.addExtensions(getExtensions(json));
return builder.build();
}
private FeatureBundle[] getBundles(JsonObject json) {
JsonArray ja = json.getJsonArray("bundles");
if (ja == null)
return new FeatureBundle[] {};
List<FeatureBundle> bundles = new ArrayList<>();
for (JsonValue val : ja) {
if (val.getValueType() == JsonValue.ValueType.OBJECT) {
JsonObject jo = val.asJsonObject();
String bid = jo.getString("id");
FeatureBundleBuilder builder = builderFactory.newBundleBuilder(getIDfromMavenCoordinates(bid));
for (Map.Entry<String, JsonValue> entry : jo.entrySet()) {
if (entry.getKey().equals("id"))
continue;
JsonValue value = entry.getValue();
Object v;
switch (value.getValueType()) {
case NUMBER:
v = ((JsonNumber) value).longValueExact();
break;
case STRING:
v = ((JsonString) value).getString();
break;
default:
v = value.toString();
}
builder.addMetadata(entry.getKey(), v);
}
bundles.add(builder.build());
}
}
return bundles.toArray(new FeatureBundle[0]);
}
private String[] getCategories(JsonObject json) {
JsonArray ja = json.getJsonArray("categories");
if (ja == null)
return new String[] {};
List<String> cats = ja.getValuesAs(JsonString::getString);
return cats.toArray(new String[] {});
}
private FeatureConfiguration[] getConfigurations(JsonObject json) {
JsonObject jo = json.getJsonObject("configurations");
if (jo == null)
return new FeatureConfiguration[] {};
List<FeatureConfiguration> configs = new ArrayList<>();
for (Map.Entry<String, JsonValue> entry : jo.entrySet()) {
String p = entry.getKey();
String factoryPid = null;
int idx = p.indexOf('~');
if (idx > 0) {
factoryPid = p.substring(0, idx);
p = p.substring(idx + 1);
}
FeatureConfigurationBuilder builder;
if (factoryPid == null) {
builder = builderFactory.newConfigurationBuilder(p);
} else {
builder = builderFactory.newConfigurationBuilder(factoryPid, p);
}
JsonObject values = entry.getValue().asJsonObject();
for (Map.Entry<String, JsonValue> value : values.entrySet()) {
String key = value.getKey();
String typeInfo = null;
int cidx = key.indexOf(':');
if (cidx > 0) {
typeInfo = key.substring(cidx + 1);
key = key.substring(0, cidx);
}
JsonValue val = value.getValue();
// TODO ensure that binary support works as well
Object v = TypeConverter.convertObjectToType(val, typeInfo);
builder.addValue(key, v);
}
configs.add(builder.build());
}
return configs.toArray(new FeatureConfiguration[] {});
}
private FeatureExtension[] getExtensions(JsonObject json) {
JsonObject jo = json.getJsonObject("extensions");
if (jo == null)
return new FeatureExtension[] {};
List<FeatureExtension> extensions = new ArrayList<>();
for (Map.Entry<String,JsonValue> entry : jo.entrySet()) {
JsonObject exData = entry.getValue().asJsonObject();
FeatureExtension.Type type;
if (exData.containsKey("text")) {
type = FeatureExtension.Type.TEXT;
} else if (exData.containsKey("artifacts")) {
type = FeatureExtension.Type.ARTIFACTS;
} else if (exData.containsKey("json")) {
type = FeatureExtension.Type.JSON;
} else {
throw new IllegalStateException("Invalid extension: " + entry);
}
String k = exData.getString("kind", "optional");
FeatureExtension.Kind kind = FeatureExtension.Kind.valueOf(k.toUpperCase());
FeatureExtensionBuilder builder = builderFactory.newExtensionBuilder(entry.getKey(), type, kind);
switch (type) {
case TEXT:
exData.getJsonArray("text")
.stream()
.filter(jv -> jv.getValueType() == JsonValue.ValueType.STRING)
.map(jv -> ((JsonString) jv).getString())
.forEach(builder::addText);
break;
case ARTIFACTS:
exData.getJsonArray("artifacts")
.stream()
.filter(jv -> jv.getValueType() == JsonValue.ValueType.OBJECT)
.map(jv -> (JsonObject) jv)
.forEach(md -> {
Map<String, JsonValue> v = new HashMap<>(md);
JsonString idVal = (JsonString) v.remove("id");
ID id = getIDfromMavenCoordinates(idVal.getString());
FeatureArtifactBuilder fab = builderFactory.newArtifactBuilder(id);
for (Map.Entry<String,JsonValue> mde : v.entrySet()) {
JsonValue val = mde.getValue();
switch (val.getValueType()) {
case STRING:
fab.addMetadata(mde.getKey(), ((JsonString) val).getString());
break;
case FALSE:
fab.addMetadata(mde.getKey(), false);
break;
case TRUE:
fab.addMetadata(mde.getKey(), true);
break;
case NUMBER:
JsonNumber num = (JsonNumber) val;
if (num.toString().contains(".")) {
fab.addMetadata(mde.getKey(), num.doubleValue());
} else {
fab.addMetadata(mde.getKey(), num.longValue());
}
break;
default:
// do nothing
break;
}
}
builder.addArtifact(fab.build());
});
break;
case JSON:
builder.setJSON(exData.getJsonObject("json").toString());
break;
}
extensions.add(builder.build());
}
return extensions.toArray(new FeatureExtension[] {});
}
public void writeFeature(Feature feature, Writer jsonWriter) throws IOException {
// LinkedHashMap to give it some order, we'd like 'id' and 'name' first.
Map<String,Object> attrs = new LinkedHashMap<>();
attrs.put("id", feature.getID().toString());
feature.getName().ifPresent(n -> attrs.put("name", n));
feature.getDescription().ifPresent(d -> attrs.put("description", d));
feature.getDocURL().ifPresent(d -> attrs.put("docURL", d));
feature.getLicense().ifPresent(l -> attrs.put("license", l));
feature.getSCM().ifPresent(s -> attrs.put("scm", s));
feature.getVendor().ifPresent(v -> attrs.put("vendor", v));
JsonObjectBuilder json = Json.createObjectBuilder(attrs);
JsonArray bundles = getBundles(feature);
if (bundles != null) {
json.add("bundles", bundles);
}
JsonObject configs = getConfigurations(feature);
if (configs != null) {
json.add("configurations", configs);
}
JsonObject extensions = getExtensions(feature);
if (extensions != null) {
json.add("extensions", extensions);
}
// TODO add variables
// TODO add frameworkproperties
JsonObject fo = json.build();
JsonGeneratorFactory gf = Json.createGeneratorFactory(Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
try (JsonGenerator gr = gf.createGenerator(jsonWriter)) {
gr.write(fo);
}
}
private JsonArray getBundles(Feature feature) {
List<FeatureBundle> bundles = feature.getBundles();
if (bundles == null || bundles.size() == 0)
return null;
JsonArrayBuilder ab = Json.createArrayBuilder();
for (FeatureBundle bundle : bundles) {
Map<String, Object> attrs = new LinkedHashMap<>();
attrs.put("id", bundle.getID().toString());
attrs.putAll(bundle.getMetadata());
ab.add(Json.createObjectBuilder(attrs));
}
return ab.build();
}
private JsonObject getConfigurations(Feature feature) {
Map<String, FeatureConfiguration> configs = feature.getConfigurations();
if (configs == null || configs.size() == 0)
return null;
JsonObjectBuilder ob = Json.createObjectBuilder();
for (Map.Entry<String,FeatureConfiguration> cfg : configs.entrySet()) {
JsonObjectBuilder cb = Json.createObjectBuilder();
for (Map.Entry<String,Object> prop : cfg.getValue().getValues().entrySet()) {
Map.Entry<String, JsonValue> je = TypeConverter.convertObjectToTypedJsonValue(prop.getValue());
String tk = je.getKey();
cb.add(TypeConverter.NO_TYPE_INFO.equals(tk) ? prop.getKey() : prop.getKey() + ":" + tk, je.getValue());
}
ob.add(cfg.getKey(), cb.build());
}
return ob.build();
}
private JsonObject getExtensions(Feature feature) {
Map<String, FeatureExtension> extensions = feature.getExtensions();
if (extensions == null || extensions.size() == 0)
return null;
JsonObjectBuilder ob = Json.createObjectBuilder();
for (Map.Entry<String,FeatureExtension> entry : extensions.entrySet()) {
FeatureExtension extVal = entry.getValue();
JsonObjectBuilder vb = Json.createObjectBuilder();
vb.add("kind", extVal.getKind().toString().toLowerCase());
switch (extVal.getType()) {
case TEXT:
vb.add("text", Json.createArrayBuilder(extVal.getText()).build());
break;
case ARTIFACTS:
JsonArrayBuilder arr = Json.createArrayBuilder();
for (FeatureArtifact art : extVal.getArtifacts()) {
Map<String,Object> attrs = new LinkedHashMap<>();
attrs.put("id", art.getID().toString());
attrs.putAll(art.getMetadata());
arr.add(Json.createObjectBuilder(attrs)).build();
}
vb.add("artifacts", arr.build());
break;
case JSON:
vb.add("json", Json.createReader(new StringReader(extVal.getJSON())).readValue());
break;
}
ob.add(entry.getKey(), vb.build());
}
return ob.build();
}
}