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"));
+    }
 }