SLING-7787 : Support official JSON format for configurations
diff --git a/pom.xml b/pom.xml
index 7b1d5d7..a1b66c7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,14 +55,19 @@
<Bundle-Activator>
org.apache.sling.installer.core.impl.Activator
</Bundle-Activator>
+ <Conditional-Package>
+ org.apache.felix.configurator.impl.json,
+ org.apache.felix.configurator.impl.model
+ </Conditional-Package>
<Embed-Dependency>
+ geronimo-json_1.0_spec,johnzon-core,org.apache.felix.converter,
org.apache.felix.configadmin;inline="org/apache/felix/cm/file/ConfigurationHandler.*",
org.apache.sling.commons.osgi;inline="org/apache/sling/commons/osgi/ManifestHeader*.*|org/apache/sling/commons/osgi/PropertiesUtil.*|org/apache/sling/commons/osgi/SortingServiceTracker.*"
</Embed-Dependency>
- <Provide-Capability><![CDATA[
- osgi.service;objectClass:List<String>="org.apache.sling.installer.api.OsgiInstaller,org.apache.sling.installer.api.ResourceChangeListener,org.apache.sling.installer.api.info.InfoProvider,org.apache.sling.installer.api.tasks.RetryHandler",
- osgi.service;objectClass:List<String>="org.apache.sling.installer.api.event.InstallationListener,org.apache.sling.installer.api.jmx.InstallerMBean"
- ]]></Provide-Capability>
+ <Provide-Capability><![CDATA[
+ osgi.service;objectClass:List<String>="org.apache.sling.installer.api.OsgiInstaller,org.apache.sling.installer.api.ResourceChangeListener,org.apache.sling.installer.api.info.InfoProvider,org.apache.sling.installer.api.tasks.RetryHandler",
+ osgi.service;objectClass:List<String>="org.apache.sling.installer.api.event.InstallationListener,org.apache.sling.installer.api.jmx.InstallerMBean"
+ ]]></Provide-Capability>
</instructions>
</configuration>
</plugin>
@@ -150,6 +155,31 @@
<version>1.8.12</version>
<scope>provided</scope>
</dependency>
+ <!-- We use classes from the configurator, json api and converter to read JSON config files -->
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.configurator</artifactId>
+ <version>1.0.4</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.converter</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.geronimo.specs</groupId>
+ <artifactId>geronimo-json_1.0_spec</artifactId>
+ <version>1.0-alpha-1</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.johnzon</groupId>
+ <artifactId>johnzon-core</artifactId>
+ <version>1.0.0</version>
+ <scope>provided</scope>
+ </dependency>
<!-- Basic dependencies for Unit Tests -->
<dependency>
<groupId>junit</groupId>
diff --git a/src/main/java/org/apache/sling/installer/core/impl/InternalResource.java b/src/main/java/org/apache/sling/installer/core/impl/InternalResource.java
index 1f3e595..9d6bb1d 100644
--- a/src/main/java/org/apache/sling/installer/core/impl/InternalResource.java
+++ b/src/main/java/org/apache/sling/installer/core/impl/InternalResource.java
@@ -19,16 +19,22 @@
package org.apache.sling.installer.core.impl;
import java.io.BufferedInputStream;
+import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.URI;
+import java.net.URL;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import org.apache.felix.cm.file.ConfigurationHandler;
+import org.apache.felix.configurator.impl.json.JSONUtil;
+import org.apache.felix.configurator.impl.json.TypeConverter;
+import org.apache.felix.configurator.impl.model.ConfigurationFile;
import org.apache.sling.installer.api.InstallableResource;
/**
@@ -95,7 +101,7 @@
(InstallableResource.TYPE_PROPERTIES.equals(type) ||
((type == null || InstallableResource.TYPE_FILE.equals(type)) && isConfigExtension(resource.getId())))) {
try {
- dict = readDictionary(is, getExtension(resource.getId()));
+ dict = readDictionary(is, scheme, resource.getId());
} catch (final IOException ioe) {
throw (IOException)new IOException("Unable to read dictionary from input stream: " + resource.getId()).initCause(ioe);
}
@@ -179,7 +185,7 @@
return null;
}
- final Dictionary<String, Object> result = new Hashtable<String, Object>();
+ final Dictionary<String, Object> result = new Hashtable<>();
final Enumeration<String> e = d.keys();
while(e.hasMoreElements()) {
final String key = e.nextElement();
@@ -214,63 +220,124 @@
* We use the same logic as Apache Felix FileInstall here:
* - *.cfg files are treated as property files
* - *.config files are handled by the Apache Felix ConfigAdmin file reader
+ * And all *.json files are read using code from Apache Felix Configurator
* @param is
* @param extension
* @throws IOException
*/
private static Dictionary<String, Object> readDictionary(
- final InputStream is, final String extension)
+ final InputStream is, final String scheme, final String id)
throws IOException {
- final Hashtable<String, Object> ht = new Hashtable<String, Object>();
- final BufferedInputStream in = new BufferedInputStream(is);
- try {
- if ( !extension.equals("config") ) {
- final Properties p = new Properties();
- in.mark(1);
- boolean isXml = in.read() == '<';
- in.reset();
- if (isXml) {
- p.loadFromXML(in);
- } else {
- p.load(in);
- }
- final Enumeration<Object> i = p.keys();
- while ( i.hasMoreElements() ) {
- final Object key = i.nextElement();
- ht.put(key.toString(), p.get(key));
- }
+ final String extension = getExtension(id);
+ if ( "json".equals(extension) ) {
+ final String name = scheme.concat(":").concat(id);
+ String configId;
+ int pos = id.lastIndexOf('/');
+ if ( pos == -1 ) {
+ configId = id;
} else {
- // check for initial comment line
- in.mark(256);
- final int firstChar = in.read();
- if ( firstChar == '#' ) {
- int b;
- while ((b = in.read()) != '\n' ) {
- if ( b == -1 ) {
- throw new IOException("Unable to read configuration.");
+ configId = id.substring(pos + 1);
+ }
+ pos = configId.indexOf('-');
+ if ( pos != -1 ) {
+ configId = configId.substring(0, pos).concat("~").concat(configId.substring(pos+1));
+ }
+ if ( isConfigExtension(configId) ) {
+ configId = configId.substring(0, configId.lastIndexOf('.'));
+ }
+ final TypeConverter typeConverter = new TypeConverter(null);
+ final JSONUtil.Report report = new JSONUtil.Report();
+
+ // read from input stream
+ final String contents;
+ try(final BufferedReader buf = new BufferedReader(
+ new InputStreamReader(is, "UTF-8"))) {
+
+ final StringBuilder sb = new StringBuilder();
+
+ sb.append("{ \"");
+ sb.append(configId);
+ sb.append("\" : ");
+ String line;
+
+ while ((line = buf.readLine()) != null) {
+ sb.append(line);
+ sb.append('\n');
+ }
+ sb.append("}");
+
+ contents = sb.toString();
+ }
+
+ final URL url = new URL("file://" + configId);
+
+ final ConfigurationFile config = JSONUtil.readJSON(typeConverter, name, url, 0, contents, report);
+
+ if ( !report.errors.isEmpty() || !report.warnings.isEmpty() ) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Errors in configuration:");
+ 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());
+ }
+ return config.getConfigurations().get(0).getProperties();
+
+ } else {
+ final Hashtable<String, Object> ht = new Hashtable<>();
+
+ try (final BufferedInputStream in = new BufferedInputStream(is)) {
+
+ if ("config".equals(extension) ) {
+ // check for initial comment line
+ in.mark(256);
+ final int firstChar = in.read();
+ if ( firstChar == '#' ) {
+ int b;
+ while ((b = in.read()) != '\n' ) {
+ if ( b == -1 ) {
+ throw new IOException("Unable to read configuration.");
+ }
}
+ } else {
+ in.reset();
+ }
+ @SuppressWarnings("unchecked")
+ final Dictionary<String, Object> config = ConfigurationHandler.read(in);
+ final Enumeration<String> i = config.keys();
+ while ( i.hasMoreElements() ) {
+ final String key = i.nextElement();
+ ht.put(key, config.get(key));
}
} else {
+ final Properties p = new Properties();
+ in.mark(1);
+ boolean isXml = in.read() == '<';
in.reset();
- }
- @SuppressWarnings("unchecked")
- final Dictionary<String, Object> config = ConfigurationHandler.read(in);
- final Enumeration<String> i = config.keys();
- while ( i.hasMoreElements() ) {
- final String key = i.nextElement();
- ht.put(key, config.get(key));
+ if (isXml) {
+ p.loadFromXML(in);
+ } else {
+ p.load(in);
+ }
+ final Enumeration<Object> i = p.keys();
+ while ( i.hasMoreElements() ) {
+ final Object key = i.nextElement();
+ ht.put(key.toString(), p.get(key));
+ }
}
}
- } finally {
- try { in.close(); } catch (IOException ignore) {}
+ return ht;
}
-
- return ht;
}
private static boolean isConfigExtension(final String url) {
final String ext = getExtension(url);
- return "config".equals(ext) || "properties".equals(ext) || "cfg".equals(ext);
+ return "config".equals(ext) || "properties".equals(ext) || "cfg".equals(ext) || "json".equals(ext);
}
/**
diff --git a/src/test/java/org/apache/sling/installer/core/impl/InternalResourceTest.java b/src/test/java/org/apache/sling/installer/core/impl/InternalResourceTest.java
index fd68ba0..8120a47 100644
--- a/src/test/java/org/apache/sling/installer/core/impl/InternalResourceTest.java
+++ b/src/test/java/org/apache/sling/installer/core/impl/InternalResourceTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Hashtable;
@@ -47,7 +48,7 @@
}
private Dictionary<String, Object> getSimpleDict() {
- final Hashtable<String, Object> dict = new Hashtable<String, Object>();
+ final Hashtable<String, Object> dict = new Hashtable<>();
dict.put("a", "a");
dict.put("b", 2);
@@ -88,4 +89,24 @@
assertEquals(InstallableResource.DEFAULT_PRIORITY, ir.getPriority());
}
}
+
+ @Test public void testJSONForConfigurations() throws IOException {
+ final String JSONConfig = "{\n" +
+ "\"service.ranking:Integer\" : \"20\",\n" +
+ "\"string.prop\" : \"hello world\",\n" +
+ "\"value\" : true\n" +
+ "}";
+ final InstallableResource rsrc = new InstallableResource("my.config.json",
+ new ByteArrayInputStream(JSONConfig.getBytes("UTF-8")), null, "digest",
+ InstallableResource.TYPE_FILE, null);
+
+ final InternalResource ir = InternalResource.create(SCHEME, rsrc);
+ assertEquals(InstallableResource.TYPE_FILE, ir.getType());
+ assertNull(ir.getInputStream());
+ assertNotNull(ir.getDictionary());
+ assertEquals(3, ir.getDictionary().size());
+ assertEquals(20, ir.getDictionary().get("service.ranking"));
+ assertEquals("hello world", ir.getDictionary().get("string.prop"));
+ assertEquals(Boolean.TRUE, ir.getDictionary().get("value"));
+ }
}