aries-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dav...@apache.org
Subject svn commit: r1642910 [1/3] - in /aries/trunk/subsystem: subsystem-api/ subsystem-api/src/main/java/org/apache/aries/subsystem/ subsystem-bundle/ subsystem-core/ subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ subsystem-core/src/ma...
Date Tue, 02 Dec 2014 15:38:39 GMT
Author: davidb
Date: Tue Dec  2 15:38:38 2014
New Revision: 1642910

URL: http://svn.apache.org/r1642910
Log:
ARIES-1252 Support custom content in subsystems/.esa files

Custom content in .esa files is handled via Whiteboard service handlers.
To add a handler for custom content, register a org.apache.aries.subsystem.ContentHandler service. This service will be called back for install/start/stop/uninstall actions to be performed.

The service must be registered with the service property
  org.aries.subsystem.contenthandler.type
to declare the content type that it should handle. This allows to register handlers for content types different than the default osgi.bundle/osgi.fragment and osgi.subsystem.* ones.

For more details take a look at the CustomContentHandlerTest.testCustomContentHandler() itest. This creates an .esa file that contains a bundle and also a custom content file called 'custom1.sausages'. The custom content is declared in the Subsystem-Content header via the standard 'type' attribute. To link it with a resource in the .esa also provide the embedded-resource attribute, e.g.:
Subsystem-Content:
  customContent1;embedded-resource=custom1.sausages;type=foo.sausages

Using a proper resource type (rather than directly using the file name extension) makes it possible to also provide this custom resource from a repository if needed.

The CustomContentHandlerTest has a test content handler that is called back with the content of the resource, its name, the current subsystem and the coordination.
Providing the coordination allows the custom handler to define a compensation in case the installation fails elsewhere, or fail the
installation itself.

In addition, this commit also provides support for handling configuration files as custom content. Two different configuration file types are supported. Plain properties files (which are limited to string values) and the Felix ConfigAdmin config file format, which supports other datatypes as values [1]. 

To embed a Felix config admin file for PID org.foo.Bar, use felix.cm.config as the resource type as the following in the Subsystem-Content header:
 org.foo.Bar;embedded-resource=org.foo.Bar.cfg;type=felix.cm.config

To embed a properties configuration file for PID com.blah.Blah declare it with the osgi.config.properties type:
  Subsystem-Content: com.blah.Blah;embedded-resource=com.blah.Blah.cfg;type=osgi.config.properties

The ConfigAdminPropsFileContentHandlerTest shows this in action.

[1] See the javadoc of http://svn.apache.org/repos/asf/felix/trunk/configadmin/src/main/java/org/apache/felix/cm/file/ConfigurationHandler.java for syntax.


Added:
    aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/ContentHandler.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigAdminContentHandler.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigurationHandler.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceInstaller.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceUninstaller.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResources.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/FileResource.java
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/META-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/META-INF/MANIFEST.MF
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/subsystem/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/subsystem/itests/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/subsystem/itests/cmcontent/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/subsystem/itests/cmcontent/impl/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/subsystem/itests/cmcontent/impl/Activator.java
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/subsystem/itests/cmcontent/impl/BarManagedService.java
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/cmContentBundleZ/org/apache/aries/subsystem/itests/cmcontent/impl/BlahManagedService.java
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleA/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleA/META-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleA/META-INF/MANIFEST.MF
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleB/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleB/META-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleB/META-INF/MANIFEST.MF
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleC/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleC/META-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleC/META-INF/MANIFEST.MF
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleD/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleD/META-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/bundles/customContentBundleD/META-INF/MANIFEST.MF
    aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/ConfigAdminPropsFileContentHandlerTest.java
    aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/CustomContentHandlerTest.java
    aries/trunk/subsystem/subsystem-itests/src/test/resources/cmContent/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/cmContent/OSGI-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/cmContent/OSGI-INF/SUBSYSTEM.MF
    aries/trunk/subsystem/subsystem-itests/src/test/resources/cmContent/com.blah.Blah.cfg
    aries/trunk/subsystem/subsystem-itests/src/test/resources/cmContent/org.foo.Bar.cfg
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent/OSGI-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent/OSGI-INF/SUBSYSTEM.MF
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent/custom1.sausages
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent1/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent1/OSGI-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent1/OSGI-INF/SUBSYSTEM.MF
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent1/custom2.sausages
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent2/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent2/OSGI-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent2/OSGI-INF/SUBSYSTEM.MF
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent2/custom3.sausages
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent3/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent3/OSGI-INF/
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent3/OSGI-INF/SUBSYSTEM.MF
    aries/trunk/subsystem/subsystem-itests/src/test/resources/customContent3/custom4.sausages
Modified:
    aries/trunk/subsystem/subsystem-api/pom.xml
    aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/packageinfo
    aries/trunk/subsystem/subsystem-bundle/pom.xml
    aries/trunk/subsystem/subsystem-core/pom.xml
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/Activator.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/OsgiIdentityCapability.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/RawSubsystemResource.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResourceInstaller.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/ResourceUninstaller.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StartAction.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/StopAction.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResource.java
    aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/SubsystemResourceInstaller.java
    aries/trunk/subsystem/subsystem-itests/pom.xml
    aries/trunk/subsystem/subsystem-itests/src/test/java/org/apache/aries/subsystem/itests/SubsystemTest.java

Modified: aries/trunk/subsystem/subsystem-api/pom.xml
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-api/pom.xml?rev=1642910&r1=1642909&r2=1642910&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-api/pom.xml (original)
+++ aries/trunk/subsystem/subsystem-api/pom.xml Tue Dec  2 15:38:38 2014
@@ -32,7 +32,7 @@
     <artifactId>org.apache.aries.subsystem.api</artifactId>
     <packaging>bundle</packaging>
     <name>Apache Aries Subsystem API</name>
-    <version>1.0.1-SNAPSHOT</version>
+    <version>1.1.0-SNAPSHOT</version>
     <description>Subsystems API.</description>
 
     <scm>
@@ -68,6 +68,12 @@
             <version>5.0.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.enterprise</artifactId>
+            <version>5.0.0</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
     <build>

Added: aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/ContentHandler.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/ContentHandler.java?rev=1642910&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/ContentHandler.java (added)
+++ aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/ContentHandler.java Tue Dec  2 15:38:38 2014
@@ -0,0 +1,70 @@
+/*
+ * Licensed 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.aries.subsystem;
+
+import java.io.InputStream;
+
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.subsystem.Subsystem;
+
+/**
+ * A handler for custom content in Subsystems. This handler must be registered as Whiteboard
+ * services with the {@link #CONTENT_TYPE_PROPERTY} property indicating the content type this
+ * handler must be invoked for. <p>
+ *
+ * Custom content embedded inside an subsystem archive (e.g. {@code .esa} file) must be declared
+ * in the {@code Subsystem-Content} header where the {@link #EMBEDDED_RESOURCE_ATTRIBUTE} can
+ * be used to associate it with the name of a file inside the archive.
+ */
+public interface ContentHandler {
+    static final String CONTENT_TYPE_PROPERTY = "org.aries.subsystem.contenthandler.type";
+    static final String EMBEDDED_RESOURCE_ATTRIBUTE = "embedded-resource";
+
+    /**
+     * Install this custom content.
+     * @param is An input stream to the content.
+     * @param symbolicName The name of the content.
+     * @param contentType The type of the content.
+     * @param subsystem The target subsystem.
+     * @param coordination The current coordination. Can be used to register a compensation in case of
+     * failure or to fail the installation action.
+     */
+    void install(InputStream is, String symbolicName, String contentType, Subsystem subsystem, Coordination coordination);
+
+    /**
+     * Start this custom content.
+     * @param symbolicName The name of the content.
+     * @param contentType The type of the content.
+     * @param subsystem The target subsystem.
+     * @param coordination The current coordination. Can be used to register a compensation in case of
+     * failure or to fail the start action.
+     */
+    void start(String symbolicName, String contentType, Subsystem subsystem, Coordination coordination);
+
+    /**
+     * Stop this custom content.
+     * @param symbolicName The name of the content.
+     * @param contentType The type of the content.
+     * @param subsystem The target subsystem.
+     */
+    void stop(String symbolicName, String contentType, Subsystem subsystem);
+
+    /**
+     * Uninstall this custom content.
+     * @param symbolicName The name of the content.
+     * @param contentType The type of the content.
+     * @param subsystem The target subsystem.
+     */
+    void uninstall(String symbolicName, String contentType, Subsystem subsystem);
+}

Modified: aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/packageinfo
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/packageinfo?rev=1642910&r1=1642909&r2=1642910&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/packageinfo (original)
+++ aries/trunk/subsystem/subsystem-api/src/main/java/org/apache/aries/subsystem/packageinfo Tue Dec  2 15:38:38 2014
@@ -16,4 +16,4 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-version 1.0.0
+version 1.1.0

Modified: aries/trunk/subsystem/subsystem-bundle/pom.xml
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-bundle/pom.xml?rev=1642910&r1=1642909&r2=1642910&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-bundle/pom.xml (original)
+++ aries/trunk/subsystem/subsystem-bundle/pom.xml Tue Dec  2 15:38:38 2014
@@ -32,7 +32,7 @@
     <artifactId>org.apache.aries.subsystem</artifactId>
     <packaging>bundle</packaging>
     <name>Apache Aries Subsystems Bundle</name>
-    <version>1.0.1-SNAPSHOT</version>
+    <version>1.1.0-SNAPSHOT</version>
     <description>This is a standalone bundle to support Aries Subsystems.</description>
 
     <scm>
@@ -52,7 +52,7 @@
             *
         </aries.osgi.import>
         <aries.osgi.export.pkg>
-            org.apache.aries.subsystem;version="1.0",
+            org.apache.aries.subsystem;version="1.1",
             org.osgi.service.repository;version="1.0",
             org.osgi.service.subsystem;version="1.0"
         </aries.osgi.export.pkg>
@@ -66,12 +66,12 @@
         <dependency>
             <groupId>org.apache.aries.subsystem</groupId>
             <artifactId>org.apache.aries.subsystem.api</artifactId>
-            <version>1.0.1-SNAPSHOT</version>
+            <version>1.1.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.aries.subsystem</groupId>
             <artifactId>org.apache.aries.subsystem.core</artifactId>
-            <version>1.1.1-SNAPSHOT</version>
+            <version>1.2.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.aries</groupId>

Modified: aries/trunk/subsystem/subsystem-core/pom.xml
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/pom.xml?rev=1642910&r1=1642909&r2=1642910&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/pom.xml (original)
+++ aries/trunk/subsystem/subsystem-core/pom.xml Tue Dec  2 15:38:38 2014
@@ -32,7 +32,7 @@
     <artifactId>org.apache.aries.subsystem.core</artifactId>
     <packaging>bundle</packaging>
     <name>Apache Aries Subsystem Core</name>
-    <version>1.1.1-SNAPSHOT</version>
+    <version>1.2.0-SNAPSHOT</version>
     <description>Subsystems Core.</description>
 
     <scm>
@@ -63,7 +63,7 @@
         <dependency>
             <groupId>org.apache.aries.subsystem</groupId>
             <artifactId>org.apache.aries.subsystem.api</artifactId>
-            <version>1.0.0</version>
+            <version>1.1.0-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.apache.aries</groupId>

Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigAdminContentHandler.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigAdminContentHandler.java?rev=1642910&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigAdminContentHandler.java (added)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigAdminContentHandler.java Tue Dec  2 15:38:38 2014
@@ -0,0 +1,111 @@
+/*
+ * Licensed 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.aries.subsystem.core.content;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.aries.subsystem.ContentHandler;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.subsystem.Subsystem;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ConfigAdminContentHandler implements ContentHandler {
+    public static final String FELIXCM_CONTENT_TYPE = "felix.cm.config";
+    public static final String PROPERTIES_CONTENT_TYPE = "osgi.config.properties";
+    public static final String[] CONTENT_TYPES = {PROPERTIES_CONTENT_TYPE, FELIXCM_CONTENT_TYPE};
+
+    private final ServiceTracker<ConfigurationAdmin,ConfigurationAdmin> cmTracker;
+    private Map<String, Dictionary<String, Object>> configurations = new ConcurrentHashMap<String, Dictionary<String, Object>>();
+
+    public ConfigAdminContentHandler(BundleContext ctx) {
+        cmTracker = new ServiceTracker<ConfigurationAdmin, ConfigurationAdmin>(
+                ctx, ConfigurationAdmin.class, null);
+        cmTracker.open();
+    }
+
+    public void shutDown() {
+        cmTracker.close();
+    }
+
+    @Override @SuppressWarnings({ "unchecked", "rawtypes" })
+    public void install(InputStream is, String symbolicName, String contentType, Subsystem subsystem, Coordination coordination) {
+        Dictionary configuration = null;
+        try {
+            if (PROPERTIES_CONTENT_TYPE.equals(contentType)) {
+                Properties p = new Properties();
+                p.load(is);
+                configuration = p;
+            } else if (FELIXCM_CONTENT_TYPE.equals(contentType)) {
+                configuration = ConfigurationHandler.read(is);
+            }
+        } catch (IOException e) {
+            coordination.fail(new Exception("Problem loading configuration " +
+                    symbolicName + " for subsystem " + subsystem.getSymbolicName(), e));
+            return;
+        } finally {
+            try { is.close(); } catch (IOException ioe) {}
+        }
+
+        if (configuration != null) {
+            configurations.put(symbolicName, configuration);
+        }
+    }
+
+    @Override
+    public void start(String symbolicName, String contentType, Subsystem subsystem, Coordination coordination) {
+        Dictionary<String, Object> configuration = configurations.get(symbolicName);
+        if (configuration == null) {
+            coordination.fail(new Exception("Cannot start configuration " + symbolicName + " for subsystem " + subsystem.getSymbolicName() +
+                    " it was not previously loaded"));
+            return;
+        }
+
+        try {
+            ConfigurationAdmin cm = cmTracker.getService();
+            if (cm == null) {
+                coordination.fail(new Exception("No Configuration Admin Service found. Cannot apply configuration " +
+                        symbolicName + " to subsystem " + subsystem.getSymbolicName()));
+                return;
+            }
+            Configuration conf = cm.getConfiguration(symbolicName, null);
+            conf.update(configuration);
+
+            // Update has happened, we can forget the configuration data now
+            configurations.remove(symbolicName);
+        } catch (IOException e) {
+            coordination.fail(new Exception("Problem applying configuration " + symbolicName + " in subsystem " +
+                    subsystem.getSymbolicName(), e));
+        }
+    }
+
+    @Override
+    public void stop(String symbolicName, String contentType, Subsystem subsystem) {
+        // We don't remove the configuration on stop, as this is generally not desired.
+        // Specifically, other changes may have been made to the configuration that we
+        // don't want to wipe out.
+    }
+
+    @Override
+    public void uninstall(String symbolicName, String contentType, Subsystem subsystem) {
+        // Nothing to uninstall
+    }
+}

Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigurationHandler.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigurationHandler.java?rev=1642910&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigurationHandler.java (added)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/content/ConfigurationHandler.java Tue Dec  2 15:38:38 2014
@@ -0,0 +1,801 @@
+/*
+ * 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.aries.subsystem.core.content;
+
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PushbackReader;
+import java.io.Writer;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/* This class was taken from the Felix Codebase to read Felix ConfigAdmin .config files. */
+
+/**
+ * The <code>ConfigurationHandler</code> class implements configuration reading
+ * form a <code>java.io.InputStream</code> and writing to a
+ * <code>java.io.OutputStream</code> on behalf of the
+ * {@link FilePersistenceManager} class.
+ *
+ * <pre>
+ * cfg = prop &quot;=&quot; value .
+ *  prop = symbolic-name . // 1.4.2 of OSGi Core Specification
+ *  symbolic-name = token { &quot;.&quot; token } .
+ *  token = { [ 0..9 ] | [ a..z ] | [ A..Z ] | '_' | '-' } .
+ *  value = [ type ] ( &quot;[&quot; values &quot;]&quot; | &quot;(&quot; values &quot;)&quot; | simple ) .
+ *  values = simple { &quot;,&quot; simple } .
+ *  simple = &quot;&quot;&quot; stringsimple &quot;&quot;&quot; .
+ *  type = // 1-char type code .
+ *  stringsimple = // quoted string representation of the value .
+ * </pre>
+ */
+public class ConfigurationHandler
+{
+    protected static final String ENCODING = "UTF-8";
+
+    protected static final int TOKEN_NAME = 'N';
+    protected static final int TOKEN_EQ = '=';
+    protected static final int TOKEN_ARR_OPEN = '[';
+    protected static final int TOKEN_ARR_CLOS = ']';
+    protected static final int TOKEN_VEC_OPEN = '(';
+    protected static final int TOKEN_VEC_CLOS = ')';
+    protected static final int TOKEN_COMMA = ',';
+    protected static final int TOKEN_VAL_OPEN = '"'; // '{';
+    protected static final int TOKEN_VAL_CLOS = '"'; // '}';
+
+    // simple types (string & primitive wrappers)
+    protected static final int TOKEN_SIMPLE_STRING = 'T';
+    protected static final int TOKEN_SIMPLE_INTEGER = 'I';
+    protected static final int TOKEN_SIMPLE_LONG = 'L';
+    protected static final int TOKEN_SIMPLE_FLOAT = 'F';
+    protected static final int TOKEN_SIMPLE_DOUBLE = 'D';
+    protected static final int TOKEN_SIMPLE_BYTE = 'X';
+    protected static final int TOKEN_SIMPLE_SHORT = 'S';
+    protected static final int TOKEN_SIMPLE_CHARACTER = 'C';
+    protected static final int TOKEN_SIMPLE_BOOLEAN = 'B';
+
+    // primitives
+    protected static final int TOKEN_PRIMITIVE_INT = 'i';
+    protected static final int TOKEN_PRIMITIVE_LONG = 'l';
+    protected static final int TOKEN_PRIMITIVE_FLOAT = 'f';
+    protected static final int TOKEN_PRIMITIVE_DOUBLE = 'd';
+    protected static final int TOKEN_PRIMITIVE_BYTE = 'x';
+    protected static final int TOKEN_PRIMITIVE_SHORT = 's';
+    protected static final int TOKEN_PRIMITIVE_CHAR = 'c';
+    protected static final int TOKEN_PRIMITIVE_BOOLEAN = 'b';
+
+    protected static final String CRLF = "\r\n";
+
+    protected static final Map code2Type;
+    protected static final Map type2Code;
+
+    // set of valid characters for "symblic-name"
+    private static final BitSet NAME_CHARS;
+    private static final BitSet TOKEN_CHARS;
+
+    static
+    {
+        type2Code = new HashMap();
+
+        // simple (exclusive String whose type code is not written)
+        type2Code.put( Integer.class, new Integer( TOKEN_SIMPLE_INTEGER ) );
+        type2Code.put( Long.class, new Integer( TOKEN_SIMPLE_LONG ) );
+        type2Code.put( Float.class, new Integer( TOKEN_SIMPLE_FLOAT ) );
+        type2Code.put( Double.class, new Integer( TOKEN_SIMPLE_DOUBLE ) );
+        type2Code.put( Byte.class, new Integer( TOKEN_SIMPLE_BYTE ) );
+        type2Code.put( Short.class, new Integer( TOKEN_SIMPLE_SHORT ) );
+        type2Code.put( Character.class, new Integer( TOKEN_SIMPLE_CHARACTER ) );
+        type2Code.put( Boolean.class, new Integer( TOKEN_SIMPLE_BOOLEAN ) );
+
+        // primitives
+        type2Code.put( Integer.TYPE, new Integer( TOKEN_PRIMITIVE_INT ) );
+        type2Code.put( Long.TYPE, new Integer( TOKEN_PRIMITIVE_LONG ) );
+        type2Code.put( Float.TYPE, new Integer( TOKEN_PRIMITIVE_FLOAT ) );
+        type2Code.put( Double.TYPE, new Integer( TOKEN_PRIMITIVE_DOUBLE ) );
+        type2Code.put( Byte.TYPE, new Integer( TOKEN_PRIMITIVE_BYTE ) );
+        type2Code.put( Short.TYPE, new Integer( TOKEN_PRIMITIVE_SHORT ) );
+        type2Code.put( Character.TYPE, new Integer( TOKEN_PRIMITIVE_CHAR ) );
+        type2Code.put( Boolean.TYPE, new Integer( TOKEN_PRIMITIVE_BOOLEAN ) );
+
+        // reverse map to map type codes to classes, string class mapping
+        // to be added manually, as the string type code is not written and
+        // hence not included in the type2Code map
+        code2Type = new HashMap();
+        for ( Iterator ti = type2Code.entrySet().iterator(); ti.hasNext(); )
+        {
+            Map.Entry entry = ( Map.Entry ) ti.next();
+            code2Type.put( entry.getValue(), entry.getKey() );
+        }
+        code2Type.put( new Integer( TOKEN_SIMPLE_STRING ), String.class );
+
+        NAME_CHARS = new BitSet();
+        for ( int i = '0'; i <= '9'; i++ )
+            NAME_CHARS.set( i );
+        for ( int i = 'a'; i <= 'z'; i++ )
+            NAME_CHARS.set( i );
+        for ( int i = 'A'; i <= 'Z'; i++ )
+            NAME_CHARS.set( i );
+        NAME_CHARS.set( '_' );
+        NAME_CHARS.set( '-' );
+        NAME_CHARS.set( '.' );
+        NAME_CHARS.set( '\\' );
+
+        TOKEN_CHARS = new BitSet();
+        TOKEN_CHARS.set( TOKEN_EQ );
+        TOKEN_CHARS.set( TOKEN_ARR_OPEN );
+        TOKEN_CHARS.set( TOKEN_ARR_CLOS );
+        TOKEN_CHARS.set( TOKEN_VEC_OPEN );
+        TOKEN_CHARS.set( TOKEN_VEC_CLOS );
+        TOKEN_CHARS.set( TOKEN_COMMA );
+        TOKEN_CHARS.set( TOKEN_VAL_OPEN );
+        TOKEN_CHARS.set( TOKEN_VAL_CLOS );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_STRING );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_INTEGER );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_LONG );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_FLOAT );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_DOUBLE );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_BYTE );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_SHORT );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_CHARACTER );
+        TOKEN_CHARS.set( TOKEN_SIMPLE_BOOLEAN );
+
+        // primitives
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_INT );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_LONG );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_FLOAT );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_DOUBLE );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_BYTE );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_SHORT );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_CHAR );
+        TOKEN_CHARS.set( TOKEN_PRIMITIVE_BOOLEAN );
+    }
+
+
+    /**
+     * Writes the configuration data from the <code>Dictionary</code> to the
+     * given <code>OutputStream</code>.
+     * <p>
+     * This method writes at the current location in the stream and does not
+     * close the outputstream.
+     *
+     * @param out
+     *            The <code>OutputStream</code> to write the configurtion data
+     *            to.
+     * @param properties
+     *            The <code>Dictionary</code> to write.
+     * @throws IOException
+     *             If an error occurrs writing to the output stream.
+     */
+    public static void write( OutputStream out, Dictionary properties ) throws IOException
+    {
+        BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( out, ENCODING ) );
+
+        for ( Enumeration ce = properties.keys(); ce.hasMoreElements(); )
+        {
+            String key = ( String ) ce.nextElement();
+
+            // cfg = prop "=" value "." .
+            writeQuoted( bw, key );
+            bw.write( TOKEN_EQ );
+            writeValue( bw, properties.get( key ) );
+            bw.write( CRLF );
+        }
+
+        bw.flush();
+    }
+
+
+    /**
+     * Reads configuration data from the given <code>InputStream</code> and
+     * returns a new <code>Dictionary</code> object containing the data.
+     * <p>
+     * This method reads from the current location in the stream upto the end of
+     * the stream but does not close the stream at the end.
+     *
+     * @param ins
+     *            The <code>InputStream</code> from which to read the
+     *            configuration data.
+     * @return A <code>Dictionary</code> object containing the configuration
+     *         data. This object may be empty if the stream contains no
+     *         configuration data.
+     * @throws IOException
+     *             If an error occurrs reading from the stream. This exception
+     *             is also thrown if a syntax error is encountered.
+     */
+    public static Dictionary read( InputStream ins ) throws IOException
+    {
+        return new ConfigurationHandler().readInternal( ins );
+    }
+
+
+    // private constructor, this class is not to be instantiated from the
+    // outside
+    private ConfigurationHandler()
+    {
+    }
+
+    // ---------- Configuration Input Implementation ---------------------------
+
+    private int token;
+    private String tokenValue;
+    private int line;
+    private int pos;
+
+
+    private Dictionary readInternal( InputStream ins ) throws IOException
+    {
+        BufferedReader br = new BufferedReader( new InputStreamReader( ins, ENCODING ) );
+        PushbackReader pr = new PushbackReader( br, 1 );
+
+        token = 0;
+        tokenValue = null;
+        line = 0;
+        pos = 0;
+
+        Hashtable configuration = new Hashtable();
+        token = 0;
+        while ( nextToken( pr ) == TOKEN_NAME )
+        {
+            String key = tokenValue;
+
+            // expect equal sign
+            if ( nextToken( pr ) != TOKEN_EQ )
+            {
+                throw readFailure( token, TOKEN_EQ );
+            }
+
+            // expect the token value
+            Object value = readValue( pr );
+            if ( value != null )
+            {
+                configuration.put( key, value );
+            }
+        }
+
+        return configuration;
+    }
+
+
+    /**
+     * value = type ( "[" values "]" | "(" values ")" | simple ) . values =
+     * value { "," value } . simple = "{" stringsimple "}" . type = // 1-char
+     * type code . stringsimple = // quoted string representation of the value .
+     *
+     * @param pr
+     * @return
+     * @throws IOException
+     */
+    private Object readValue( PushbackReader pr ) throws IOException
+    {
+        // read (optional) type code
+        int type = read( pr );
+
+        // read value kind code if type code is not a value kinde code
+        int code;
+        if ( code2Type.containsKey( new Integer( type ) ) )
+        {
+            code = read( pr );
+        }
+        else
+        {
+            code = type;
+            type = TOKEN_SIMPLE_STRING;
+        }
+
+        switch ( code )
+        {
+            case TOKEN_ARR_OPEN:
+                return readArray( type, pr );
+
+            case TOKEN_VEC_OPEN:
+                return readCollection( type, pr );
+
+            case TOKEN_VAL_OPEN:
+                Object value = readSimple( type, pr );
+                ensureNext( pr, TOKEN_VAL_CLOS );
+                return value;
+
+            default:
+                return null;
+        }
+    }
+
+
+    private Object readArray( int typeCode, PushbackReader pr ) throws IOException
+    {
+        List list = new ArrayList();
+        for ( ;; )
+        {
+            int c = read(pr);
+            if ( c == TOKEN_VAL_OPEN )
+            {
+                Object value = readSimple( typeCode, pr );
+                if ( value == null )
+                {
+                    // abort due to error
+                    return null;
+                }
+
+                ensureNext( pr, TOKEN_VAL_CLOS );
+
+                list.add( value );
+
+                c = read( pr );
+            }
+
+            if ( c == TOKEN_ARR_CLOS )
+            {
+                Class type = ( Class ) code2Type.get( new Integer( typeCode ) );
+                Object array = Array.newInstance( type, list.size() );
+                for ( int i = 0; i < list.size(); i++ )
+                {
+                    Array.set( array, i, list.get( i ) );
+                }
+                return array;
+            }
+            else if ( c < 0 )
+            {
+                return null;
+            }
+            else if ( c != TOKEN_COMMA )
+            {
+                return null;
+            }
+        }
+    }
+
+
+    private Collection readCollection( int typeCode, PushbackReader pr ) throws IOException
+    {
+        Collection collection = new ArrayList();
+        for ( ;; )
+        {
+            int c = read( pr );
+            if ( c == TOKEN_VAL_OPEN )
+            {
+                Object value = readSimple( typeCode, pr );
+                if ( value == null )
+                {
+                    // abort due to error
+                    return null;
+                }
+
+                ensureNext( pr, TOKEN_VAL_CLOS );
+
+                collection.add( value );
+
+                c = read( pr );
+            }
+
+            if ( c == TOKEN_VEC_CLOS )
+            {
+                return collection;
+            }
+            else if ( c < 0 )
+            {
+                return null;
+            }
+            else if ( c != TOKEN_COMMA )
+            {
+                return null;
+            }
+        }
+    }
+
+
+    private Object readSimple( int code, PushbackReader pr ) throws IOException
+    {
+        switch ( code )
+        {
+            case -1:
+                return null;
+
+            case TOKEN_SIMPLE_STRING:
+                return readQuoted( pr );
+
+                // Simple/Primitive, only use wrapper classes
+            case TOKEN_SIMPLE_INTEGER:
+            case TOKEN_PRIMITIVE_INT:
+                return Integer.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_LONG:
+            case TOKEN_PRIMITIVE_LONG:
+                return Long.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_FLOAT:
+            case TOKEN_PRIMITIVE_FLOAT:
+                int fBits = Integer.parseInt( readQuoted( pr ) );
+                return new Float( Float.intBitsToFloat( fBits ) );
+
+            case TOKEN_SIMPLE_DOUBLE:
+            case TOKEN_PRIMITIVE_DOUBLE:
+                long dBits = Long.parseLong( readQuoted( pr ) );
+                return new Double( Double.longBitsToDouble( dBits ) );
+
+            case TOKEN_SIMPLE_BYTE:
+            case TOKEN_PRIMITIVE_BYTE:
+                return Byte.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_SHORT:
+            case TOKEN_PRIMITIVE_SHORT:
+                return Short.valueOf( readQuoted( pr ) );
+
+            case TOKEN_SIMPLE_CHARACTER:
+            case TOKEN_PRIMITIVE_CHAR:
+                String cString = readQuoted( pr );
+                if ( cString != null && cString.length() > 0 )
+                {
+                    return new Character( cString.charAt( 0 ) );
+                }
+                return null;
+
+            case TOKEN_SIMPLE_BOOLEAN:
+            case TOKEN_PRIMITIVE_BOOLEAN:
+                return Boolean.valueOf( readQuoted( pr ) );
+
+                // unknown type code
+            default:
+                return null;
+        }
+    }
+
+
+    private void ensureNext( PushbackReader pr, int expected ) throws IOException
+    {
+        int next = read( pr );
+        if ( next != expected )
+        {
+            readFailure( next, expected );
+        }
+    }
+
+
+    private boolean checkNext( PushbackReader pr, int expected ) throws IOException
+    {
+        int next = read( pr );
+        if ( next < 0 )
+        {
+            return false;
+        }
+
+        if ( next == expected )
+        {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    private String readQuoted( PushbackReader pr ) throws IOException
+    {
+        StringBuffer buf = new StringBuffer();
+        for ( ;; )
+        {
+            int c = read( pr );
+            switch ( c )
+            {
+                // escaped character
+                case '\\':
+                    c = read( pr );
+                    switch ( c )
+                    {
+                        // well known escapes
+                        case 'b':
+                            buf.append( '\b' );
+                            break;
+                        case 't':
+                            buf.append( '\t' );
+                            break;
+                        case 'n':
+                            buf.append( '\n' );
+                            break;
+                        case 'f':
+                            buf.append( '\f' );
+                            break;
+                        case 'r':
+                            buf.append( '\r' );
+                            break;
+                        case 'u':// need 4 characters !
+                            char[] cbuf = new char[4];
+                            if ( read( pr, cbuf ) == 4 )
+                            {
+                                c = Integer.parseInt( new String( cbuf ), 16 );
+                                buf.append( ( char ) c );
+                            }
+                            break;
+
+                        // just an escaped character, unescape
+                        default:
+                            buf.append( ( char ) c );
+                    }
+                    break;
+
+                // eof
+                case -1: // fall through
+
+                // separator token
+                case TOKEN_EQ:
+                case TOKEN_VAL_CLOS:
+                    pr.unread( c );
+                    return buf.toString();
+
+                // no escaping
+                default:
+                    buf.append( ( char ) c );
+            }
+        }
+    }
+
+
+    private int nextToken( PushbackReader pr ) throws IOException
+    {
+        int c = ignorableWhiteSpace( pr );
+
+        // immediately return EOF
+        if ( c < 0 )
+        {
+            return ( token = c );
+        }
+
+        // check whether there is a name
+        if ( NAME_CHARS.get( c ) || !TOKEN_CHARS.get( c ) )
+        {
+            // read the property name
+            pr.unread( c );
+            tokenValue = readQuoted( pr );
+            return ( token = TOKEN_NAME );
+        }
+
+        // check another token
+        if ( TOKEN_CHARS.get( c ) )
+        {
+            return ( token = c );
+        }
+
+        // unexpected character -> so what ??
+        return ( token = -1 );
+    }
+
+
+    private int ignorableWhiteSpace( PushbackReader pr ) throws IOException
+    {
+        int c = read( pr );
+        while ( c >= 0 && Character.isWhitespace( ( char ) c ) )
+        {
+            c = read( pr );
+        }
+        return c;
+    }
+
+
+    private int read( PushbackReader pr ) throws IOException
+    {
+        int c = pr.read();
+        if ( c == '\r' )
+        {
+            int c1 = pr.read();
+            if ( c1 != '\n' )
+            {
+                pr.unread( c1 );
+            }
+            c = '\n';
+        }
+
+        if ( c == '\n' )
+        {
+            line++;
+            pos = 0;
+        }
+        else
+        {
+            pos++;
+        }
+
+        return c;
+    }
+
+
+    private int read( PushbackReader pr, char[] buf ) throws IOException
+    {
+        for ( int i = 0; i < buf.length; i++ )
+        {
+            int c = read( pr );
+            if ( c >= 0 )
+            {
+                buf[i] = ( char ) c;
+            }
+            else
+            {
+                return i;
+            }
+        }
+
+        return buf.length;
+    }
+
+
+    private IOException readFailure( int current, int expected )
+    {
+        return new IOException( "Unexpected token " + current + "; expected: " + expected + " (line=" + line + ", pos="
+            + pos + ")" );
+    }
+
+
+    // ---------- Configuration Output Implementation --------------------------
+
+    private static void writeValue( Writer out, Object value ) throws IOException
+    {
+        Class clazz = value.getClass();
+        if ( clazz.isArray() )
+        {
+            writeArray( out, value );
+        }
+        else if ( value instanceof Collection )
+        {
+            writeCollection( out, ( Collection ) value );
+        }
+        else
+        {
+            writeType( out, clazz );
+            writeSimple( out, value );
+        }
+    }
+
+
+    private static void writeArray( Writer out, Object arrayValue ) throws IOException
+    {
+        int size = Array.getLength( arrayValue );
+        writeType( out, arrayValue.getClass().getComponentType() );
+        out.write( TOKEN_ARR_OPEN );
+        for ( int i = 0; i < size; i++ )
+        {
+            if ( i > 0 )
+                out.write( TOKEN_COMMA );
+            writeSimple( out, Array.get( arrayValue, i ) );
+        }
+        out.write( TOKEN_ARR_CLOS );
+    }
+
+
+    private static void writeCollection( Writer out, Collection collection ) throws IOException
+    {
+        if ( collection.isEmpty() )
+        {
+            out.write( TOKEN_VEC_OPEN );
+            out.write( TOKEN_VEC_CLOS );
+        }
+        else
+        {
+            Iterator ci = collection.iterator();
+            Object firstElement = ci.next();
+
+            writeType( out, firstElement.getClass() );
+            out.write( TOKEN_VEC_OPEN );
+            writeSimple( out, firstElement );
+
+            while ( ci.hasNext() )
+            {
+                out.write( TOKEN_COMMA );
+                writeSimple( out, ci.next() );
+            }
+            out.write( TOKEN_VEC_CLOS );
+        }
+    }
+
+
+    private static void writeType( Writer out, Class valueType ) throws IOException
+    {
+        Integer code = ( Integer ) type2Code.get( valueType );
+        if ( code != null )
+        {
+            out.write( ( char ) code.intValue() );
+        }
+    }
+
+
+    private static void writeSimple( Writer out, Object value ) throws IOException
+    {
+        if ( value instanceof Double )
+        {
+            double dVal = ( ( Double ) value ).doubleValue();
+            value = new Long( Double.doubleToRawLongBits( dVal ) );
+        }
+        else if ( value instanceof Float )
+        {
+            float fVal = ( ( Float ) value ).floatValue();
+            value = new Integer( Float.floatToRawIntBits( fVal ) );
+        }
+
+        out.write( TOKEN_VAL_OPEN );
+        writeQuoted( out, String.valueOf( value ) );
+        out.write( TOKEN_VAL_CLOS );
+    }
+
+
+    private static void writeQuoted( Writer out, String simple ) throws IOException
+    {
+        if ( simple == null || simple.length() == 0 )
+        {
+            return;
+        }
+
+        char c = 0;
+        int len = simple.length();
+        for ( int i = 0; i < len; i++ )
+        {
+            c = simple.charAt( i );
+            switch ( c )
+            {
+                case '\\':
+                case TOKEN_VAL_CLOS:
+                case ' ':
+                case TOKEN_EQ:
+                    out.write( '\\' );
+                    out.write( c );
+                    break;
+
+                // well known escapes
+                case '\b':
+                    out.write( "\\b" );
+                    break;
+                case '\t':
+                    out.write( "\\t" );
+                    break;
+                case '\n':
+                    out.write( "\\n" );
+                    break;
+                case '\f':
+                    out.write( "\\f" );
+                    break;
+                case '\r':
+                    out.write( "\\r" );
+                    break;
+
+                // other escaping
+                default:
+                    if ( c < ' ' )
+                    {
+                        String t = "000" + Integer.toHexString( c );
+                        out.write( "\\u" + t.substring( t.length() - 4 ) );
+                    }
+                    else
+                    {
+                        out.write( c );
+                    }
+            }
+        }
+    }
+}

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/Activator.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/Activator.java?rev=1642910&r1=1642909&r2=1642910&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/Activator.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/Activator.java Tue Dec  2 15:38:38 2014
@@ -19,6 +19,8 @@ import java.util.Dictionary;
 import java.util.HashSet;
 import java.util.Hashtable;
 
+import org.apache.aries.subsystem.ContentHandler;
+import org.apache.aries.subsystem.core.content.ConfigAdminContentHandler;
 import org.apache.aries.util.filesystem.IDirectoryFinder;
 import org.eclipse.equinox.region.RegionDigraph;
 import org.osgi.framework.BundleActivator;
@@ -50,24 +52,25 @@ public class Activator implements Bundle
     public static final String LOG_EXIT = "Method exit: {}, returning {}";
 
     private static Activator instance;
-	
+
 	public static synchronized Activator getInstance() {
 		logger.debug(LOG_ENTRY, "getInstance");
 		checkInstance();
 		logger.debug(LOG_EXIT, "getInstance", instance);
 		return instance;
 	}
-	
+
 	private static synchronized void checkInstance() {
 		logger.debug(LOG_ENTRY, "checkInstance");
 		if (instance == null)
 			throw new IllegalStateException("The activator has not been initialized or has been shutdown");
 		logger.debug(LOG_EXIT, "checkInstance");
 	}
-	
+
 	// @GuardedBy("this")
 	private BundleEventHook bundleEventHook;
 	private volatile BundleContext bundleContext;
+    private volatile ConfigAdminContentHandler configAdminHandler;
 	private volatile Coordinator coordinator;
     private volatile Object modelledResourceManager;
     private volatile ServiceModeller serviceModeller;
@@ -77,19 +80,19 @@ public class Activator implements Bundle
 	private ServiceTracker<?,?> serviceTracker;
 
 	private volatile Subsystems subsystems;
-	
+
 	private final Collection<ServiceRegistration<?>> registrations = new HashSet<ServiceRegistration<?>>();
 	private final Collection<Repository> repositories = Collections.synchronizedSet(new HashSet<Repository>());
 	private final Collection<IDirectoryFinder> finders = Collections.synchronizedSet(new HashSet<IDirectoryFinder>());
-	
+
 	public BundleContext getBundleContext() {
 		return bundleContext;
 	}
-	
+
 	public Coordinator getCoordinator() {
 		return coordinator;
 	}
-	
+
     public ServiceModeller getServiceModeller() {
         return serviceModeller;
     }
@@ -97,30 +100,30 @@ public class Activator implements Bundle
     public RegionDigraph getRegionDigraph() {
 		return regionDigraph;
 	}
-	
+
 	public Collection<Repository> getRepositories() {
 		return Collections.unmodifiableCollection(repositories);
 	}
-	
+
 	public Collection<IDirectoryFinder> getIDirectoryFinders() {
 		return Collections.unmodifiableCollection(finders);
 	}
-	
+
 	public Resolver getResolver() {
 		return resolver;
 	}
-	
+
 	public Subsystems getSubsystems() {
 		return subsystems;
 	}
-	
+
 	public SubsystemServiceRegistrar getSubsystemServiceRegistrar() {
 		logger.debug(LOG_ENTRY, "getSubsystemServiceRegistrar");
 		SubsystemServiceRegistrar result = registrar;
 		logger.debug(LOG_EXIT, "getSubsystemServiceRegistrar", result);
 		return result;
 	}
-	
+
 	public Repository getSystemRepository() {
 		return new SystemRepository(getSubsystems().getRootSubsystem());
 	}
@@ -142,7 +145,7 @@ public class Activator implements Bundle
 		bundleContext = null;
 		logger.debug(LOG_EXIT, "stop");
 	}
-	
+
 	private void activate() {
 		if (isActive() || !hasRequiredServices())
 			return;
@@ -152,12 +155,16 @@ public class Activator implements Bundle
 		subsystems = new Subsystems();
 		registerBundleEventHook();
 		registrations.add(bundleContext.registerService(ResolverHookFactory.class, new SubsystemResolverHookFactory(subsystems), null));
+        Dictionary<String, Object> handlerProps = new Hashtable<String, Object>();
+        handlerProps.put(ContentHandler.CONTENT_TYPE_PROPERTY, ConfigAdminContentHandler.CONTENT_TYPES);
+        configAdminHandler = new ConfigAdminContentHandler(bundleContext);
+        registrations.add(bundleContext.registerService(ContentHandler.class, configAdminHandler, handlerProps));
 		registrar = new SubsystemServiceRegistrar(bundleContext);
 		BasicSubsystem root = subsystems.getRootSubsystem();
 		bundleEventHook.activate();
 		root.start();
 	}
-	
+
 	private void deactivate() {
 		if (!isActive())
 			return;
@@ -171,12 +178,13 @@ public class Activator implements Bundle
 				logger.debug("Service had already been unregistered", e);
 			}
 		}
+        configAdminHandler.shutDown();
 		bundleEventHook.processPendingEvents();
 		synchronized (Activator.class) {
 			instance = null;
 		}
 	}
-	
+
 	private <T> T findAlternateServiceFor(Class<T> service) {
 		Object[] services = serviceTracker.getServices();
 		if (services == null)
@@ -186,11 +194,11 @@ public class Activator implements Bundle
 					return service.cast(alternate);
 		return null;
 	}
-	
+
 	private Filter generateServiceFilter() throws InvalidSyntaxException {
 		return FrameworkUtil.createFilter(generateServiceFilterString());
 	}
-	
+
 	private String generateServiceFilterString() {
 		return new StringBuilder("(|(")
 				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
@@ -206,26 +214,26 @@ public class Activator implements Bundle
 				.append(org.osgi.framework.Constants.OBJECTCLASS).append('=')
 				.append(IDirectoryFinder.class.getName()).append("))").toString();
 	}
-	
+
 	private boolean hasRequiredServices() {
 		return coordinator != null &&
 				regionDigraph != null &&
 				resolver != null;
 	}
-	
+
 	private boolean isActive() {
 		synchronized (Activator.class) {
 			return instance != null && getSubsystems() != null;
 		}
 	}
-	
+
 	private void registerBundleEventHook() {
 		Dictionary<String, Object> properties = new Hashtable<String, Object>(1);
 		properties.put(org.osgi.framework.Constants.SERVICE_RANKING, Integer.MAX_VALUE);
 		bundleEventHook = new BundleEventHook();
 		registrations.add(bundleContext.registerService(EventHook.class, bundleEventHook, properties));
 	}
-	
+
 	/* Begin ServiceTrackerCustomizer methods */
 
 	@Override
@@ -318,6 +326,6 @@ public class Activator implements Bundle
             }
         }
 	}
-	
+
 	/* End ServiceTrackerCustomizer methods */
 }

Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceInstaller.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceInstaller.java?rev=1642910&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceInstaller.java (added)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceInstaller.java Tue Dec  2 15:38:38 2014
@@ -0,0 +1,51 @@
+/*
+ * Licensed 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.aries.subsystem.core.internal;
+
+import java.io.InputStream;
+
+import org.apache.aries.subsystem.ContentHandler;
+import org.osgi.framework.ServiceReference;
+import org.osgi.resource.Resource;
+import org.osgi.service.coordinator.Coordination;
+import org.osgi.service.repository.RepositoryContent;
+
+public class CustomResourceInstaller extends ResourceInstaller {
+    private final ServiceReference<ContentHandler> handlerRef;
+    private final String type;
+
+    public CustomResourceInstaller(Coordination coordination, Resource resource, String type,
+            BasicSubsystem subsystem, ServiceReference<ContentHandler> handlerRef) {
+        super(coordination, resource, subsystem);
+        this.handlerRef = handlerRef;
+        this.type = type;
+    }
+
+    @Override
+    public Resource install() throws Exception {
+        try {
+            ContentHandler handler = subsystem.getBundleContext().getService(handlerRef);
+            if (handler != null) {
+                InputStream is = ((RepositoryContent) resource).getContent();
+                handler.install(is, ResourceHelper.getSymbolicNameAttribute(resource), type, subsystem, coordination);
+                addReference(resource);
+                return resource;
+            } else {
+                throw new Exception("Custom content handler not found: " + handlerRef);
+            }
+        } finally {
+            subsystem.getBundleContext().ungetService(handlerRef);
+        }
+    }
+}

Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceUninstaller.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceUninstaller.java?rev=1642910&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceUninstaller.java (added)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResourceUninstaller.java Tue Dec  2 15:38:38 2014
@@ -0,0 +1,43 @@
+/*
+ * Licensed 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.aries.subsystem.core.internal;
+
+import org.apache.aries.subsystem.ContentHandler;
+import org.osgi.framework.ServiceReference;
+import org.osgi.resource.Resource;
+
+public class CustomResourceUninstaller extends ResourceUninstaller {
+    private final ServiceReference<ContentHandler> handlerRef;
+    private final String type;
+
+    public CustomResourceUninstaller(Resource resource, String type,
+            BasicSubsystem subsystem, ServiceReference<ContentHandler> handlerRef) {
+        super(resource, subsystem);
+        this.handlerRef = handlerRef;
+        this.type = type;
+    }
+
+    @Override
+    public void uninstall() {
+        removeReference();
+        try {
+            ContentHandler handler = subsystem.getBundleContext().getService(handlerRef);
+            if (handler != null) {
+                handler.uninstall(ResourceHelper.getSymbolicNameAttribute(resource), type, subsystem);
+            }
+        } finally {
+            subsystem.getBundleContext().ungetService(handlerRef);
+        }
+    }
+}

Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResources.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResources.java?rev=1642910&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResources.java (added)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/CustomResources.java Tue Dec  2 15:38:38 2014
@@ -0,0 +1,45 @@
+/*
+ * Licensed 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.aries.subsystem.core.internal;
+
+import org.apache.aries.subsystem.ContentHandler;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+class CustomResources {
+    private CustomResources() {
+        // Only static methods
+    }
+
+    /**
+     * Find a custom content handler in the context of a given subsystem. Custom content handlers are
+     * services of type {@link ContentHandler} with the service registration property
+     * {@link ContentHandler#CONTENT_TYPE_PROPERTY} set to the type being handled.
+     * @param subsystem The subsystem that provides the context to look up the service.
+     * @param type The content type to find the handler for.
+     * @return The Service Reference for the Content Handler for the type or {@code null} if not found.
+     */
+    static ServiceReference<ContentHandler> getCustomContentHandler(BasicSubsystem subsystem, String type) {
+        try {
+            for(ServiceReference<ContentHandler> ref :
+                    subsystem.getBundleContext().getServiceReferences(ContentHandler.class,
+                    "(" + ContentHandler.CONTENT_TYPE_PROPERTY + "=" + type + ")")) {
+                return ref;
+            }
+            return null;
+        } catch (InvalidSyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

Added: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/FileResource.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/FileResource.java?rev=1642910&view=auto
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/FileResource.java (added)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/FileResource.java Tue Dec  2 15:38:38 2014
@@ -0,0 +1,73 @@
+/*
+ * Licensed 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.aries.subsystem.core.internal;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.aries.util.filesystem.IFile;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+import org.osgi.service.repository.RepositoryContent;
+
+public class FileResource implements Resource, RepositoryContent {
+    private final IFile file;
+    private volatile Map<String, List<Capability>> capabilities;
+
+    public FileResource(IFile file) {
+        this.file = file;
+    }
+
+    @Override
+    public List<Capability> getCapabilities(String namespace) {
+        List<Capability> caps = capabilities.get(namespace);
+        if (caps != null)
+            return caps;
+        else
+            return Collections.emptyList();
+    }
+
+    @Override
+    public List<Requirement> getRequirements(String namespace) {
+        return Collections.emptyList();
+    }
+
+    public void setCapabilities(List<Capability> capabilities) {
+        Map<String, List<Capability>> m = new HashMap<String, List<Capability>>();
+        for (Capability c : capabilities) {
+            List<Capability> l = m.get(c.getNamespace());
+            if (l == null) {
+                l = new ArrayList<Capability>();
+                m.put(c.getNamespace(), l);
+            }
+            l.add(c);
+        }
+        this.capabilities = m;
+    }
+
+    @Override
+    public InputStream getContent() {
+        try {
+            return file.open();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/OsgiIdentityCapability.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/OsgiIdentityCapability.java?rev=1642910&r1=1642909&r2=1642910&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/OsgiIdentityCapability.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/OsgiIdentityCapability.java Tue Dec  2 15:38:38 2014
@@ -27,31 +27,36 @@ import org.osgi.framework.namespace.Iden
 import org.osgi.resource.Resource;
 
 public class OsgiIdentityCapability extends AbstractCapability {
-	private final Map<String, Object> attributes = new HashMap<String, Object>();
+	private final Map<String, Object> attributes;
 	private final Resource resource;
-	
+
 	public OsgiIdentityCapability(Resource resource, String symbolicName) {
 		this(resource, symbolicName, Version.emptyVersion);
 	}
-	
+
 	public OsgiIdentityCapability(Resource resource, String symbolicName, Version version) {
 		this(resource, symbolicName, version, IdentityNamespace.TYPE_BUNDLE);
 	}
-	
+
 	public OsgiIdentityCapability(Resource resource, String symbolicName, Version version, String identityType) {
+	    this(resource, symbolicName, version, identityType, new HashMap<String, Object>());
+	}
+
+    public OsgiIdentityCapability(Resource resource, String symbolicName, Version version, String identityType, Map<String, Object> attrs) {
 		this.resource = resource;
+		attributes = attrs;
 		attributes.put(
-				IdentityNamespace.IDENTITY_NAMESPACE, 
+				IdentityNamespace.IDENTITY_NAMESPACE,
 				symbolicName);
 		attributes.put(
-				IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, 
+				IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE,
 				version);
 		attributes.put(
-				IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, 
+				IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE,
 				identityType);
 		// TODO Add directives, particularly "effective" and "singleton".
 	}
-	
+
 	public OsgiIdentityCapability(Resource resource, SubsystemManifest manifest) {
 		this(
 				resource,
@@ -59,7 +64,7 @@ public class OsgiIdentityCapability exte
 				manifest.getSubsystemVersionHeader().getVersion(),
 				manifest.getSubsystemTypeHeader().getType());
 	}
-	
+
 	public OsgiIdentityCapability(Resource resource, BundleManifest manifest) {
 		this(
 				resource,

Modified: aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/RawSubsystemResource.java
URL: http://svn.apache.org/viewvc/aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/RawSubsystemResource.java?rev=1642910&r1=1642909&r2=1642910&view=diff
==============================================================================
--- aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/RawSubsystemResource.java (original)
+++ aries/trunk/subsystem/subsystem-core/src/main/java/org/apache/aries/subsystem/core/internal/RawSubsystemResource.java Tue Dec  2 15:38:38 2014
@@ -29,12 +29,15 @@ import java.util.jar.Manifest;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.apache.aries.subsystem.ContentHandler;
+import org.apache.aries.subsystem.core.archive.Attribute;
 import org.apache.aries.subsystem.core.archive.DeploymentManifest;
 import org.apache.aries.subsystem.core.archive.Header;
 import org.apache.aries.subsystem.core.archive.ImportPackageHeader;
 import org.apache.aries.subsystem.core.archive.RequireBundleHeader;
 import org.apache.aries.subsystem.core.archive.RequireCapabilityHeader;
 import org.apache.aries.subsystem.core.archive.SubsystemContentHeader;
+import org.apache.aries.subsystem.core.archive.SubsystemContentHeader.Clause;
 import org.apache.aries.subsystem.core.archive.SubsystemImportServiceHeader;
 import org.apache.aries.subsystem.core.archive.SubsystemManifest;
 import org.apache.aries.subsystem.core.archive.SubsystemSymbolicNameHeader;
@@ -64,35 +67,35 @@ import org.slf4j.LoggerFactory;
 
 public class RawSubsystemResource implements Resource {
 	private static final Logger logger = LoggerFactory.getLogger(RawSubsystemResource.class);
-	
+
 	private static final Pattern PATTERN = Pattern.compile("([^@/\\\\]+)(?:@(.+))?.esa");
 	private static final String APPLICATION_IMPORT_SERVICE_HEADER = "Application-ImportService";
-	
+
 	private static SubsystemManifest computeExistingSubsystemManifest(IDirectory directory) throws IOException {
 		Manifest manifest = ManifestProcessor.obtainManifestFromAppDir(directory, "OSGI-INF/SUBSYSTEM.MF");
 		if (manifest == null)
 			return null;
 		return new SubsystemManifest(manifest);
 	}
-	
+
 	private static SubsystemManifest computeNewSubsystemManifest() {
 		return new SubsystemManifest.Builder().build();
 	}
-	
+
 	private static SubsystemManifest computeSubsystemManifest(IDirectory directory) throws IOException {
 		SubsystemManifest result = computeExistingSubsystemManifest(directory);
 		if (result == null)
 			result = computeNewSubsystemManifest();
 		return result;
 	}
-	
+
 	private static String convertFileToLocation(IFile file) throws MalformedURLException {
 		String result = convertFileNameToLocation(file.getName());
 		if (result == null)
 			result = file.toURL().toString();
 		return result;
 	}
-	
+
 	private static String convertFileNameToLocation(String fileName) {
 		Matcher matcher = PATTERN.matcher(fileName);
 		if (!matcher.matches())
@@ -101,25 +104,27 @@ public class RawSubsystemResource implem
 		return new SubsystemUri(matcher.group(1), version == null ? null
 				: Version.parseVersion(version), null).toString();
 	}
-	
+
 	private final List<Capability> capabilities;
 	private final DeploymentManifest deploymentManifest;
 	private final long id;
 	private final Repository localRepository;
 	private final Location location;
+    private final BasicSubsystem parentSubsystem;
 	private final List<Requirement> requirements;
 	private final Collection<Resource> resources;
 	private final Resource fakeImportServiceResource;
 	private final SubsystemManifest subsystemManifest;
-	
-	public RawSubsystemResource(String location, IDirectory content) throws URISyntaxException, IOException, ResolutionException {
+
+	public RawSubsystemResource(String location, IDirectory content, BasicSubsystem parent) throws URISyntaxException, IOException, ResolutionException {
 		id = SubsystemIdentifier.getNextId();
 		this.location = new Location(location);
+		this.parentSubsystem = parent;
 		if (content == null)
 			content = this.location.open();
 		try {
-			resources = computeResources(content);
-			SubsystemManifest manifest = computeSubsystemManifest(content);
+            SubsystemManifest manifest = computeSubsystemManifest(content);
+            resources = computeResources(content, manifest);
 			fakeImportServiceResource = createFakeResource(manifest);
 			localRepository = computeLocalRepository();
 			manifest = computeSubsystemManifestBeforeRequirements(manifest);
@@ -132,12 +137,12 @@ public class RawSubsystemResource implem
 			IOUtils.close(content.toCloseable());
 		}
 	}
-	
-	public RawSubsystemResource(File file) throws IOException, URISyntaxException, ResolutionException {
-		this(FileSystem.getFSRoot(file));
+
+	public RawSubsystemResource(File file, BasicSubsystem parent) throws IOException, URISyntaxException, ResolutionException {
+		this(FileSystem.getFSRoot(file), parent);
 	}
-	
-	public RawSubsystemResource(IDirectory idir) throws IOException, URISyntaxException, ResolutionException {
+
+	public RawSubsystemResource(IDirectory idir, BasicSubsystem parent) throws IOException, URISyntaxException, ResolutionException {
 		resources = Collections.emptyList();
 		fakeImportServiceResource = null; // not needed for persistent subsystems
 		localRepository = computeLocalRepository();
@@ -147,6 +152,7 @@ public class RawSubsystemResource implem
 		deploymentManifest = initializeDeploymentManifest(idir);
 		id = Long.parseLong(deploymentManifest.getHeaders().get(DeploymentManifest.ARIESSUBSYSTEM_ID).getValue());
 		location = new Location(deploymentManifest.getHeaders().get(DeploymentManifest.ARIESSUBSYSTEM_LOCATION).getValue());
+		parentSubsystem = parent;
 	}
 
 	private static Resource createFakeResource(SubsystemManifest manifest) {
@@ -217,19 +223,19 @@ public class RawSubsystemResource implem
 		result.trimToSize();
 		return Collections.unmodifiableList(result);
 	}
-	
+
 	public DeploymentManifest getDeploymentManifest() {
 		return deploymentManifest;
 	}
-	
+
 	public long getId() {
 		return id;
 	}
-	
+
 	public Repository getLocalRepository() {
 		return localRepository;
 	}
-	
+
 	public Location getLocation() {
 		return location;
 	}
@@ -245,71 +251,71 @@ public class RawSubsystemResource implem
 		result.trimToSize();
 		return Collections.unmodifiableList(result);
 	}
-	
+
 	public Collection<Resource> getResources() {
 		return Collections.unmodifiableCollection(resources);
 	}
-	
+
 	public SubsystemManifest getSubsystemManifest() {
 		return subsystemManifest;
 	}
-	
+
 	@Override
 	public int hashCode() {
 		int result = 17;
 		result = 31 * result + getLocation().hashCode();
 		return result;
 	}
-	
+
 	private void addHeader(SubsystemManifest.Builder builder, Header<?> header) {
 		if (header == null)
 			return;
 		builder.header(header);
 	}
-	
+
 	private void addImportPackageHeader(SubsystemManifest.Builder builder) {
 		addHeader(builder, computeImportPackageHeader());
 	}
-	
+
 	private void addRequireBundleHeader(SubsystemManifest.Builder builder) {
 		addHeader(builder, computeRequireBundleHeader());
 	}
-	
+
 	private void addRequireCapabilityHeader(SubsystemManifest.Builder builder) {
 		addHeader(builder, computeRequireCapabilityHeader());
 	}
-	
+
 	private void addSubsystemContentHeader(SubsystemManifest.Builder builder, SubsystemManifest manifest) {
 		addHeader(builder, computeSubsystemContentHeader(manifest));
 	}
-	
+
 	private void addSubsystemImportServiceHeader(SubsystemManifest.Builder builder) {
 		addHeader(builder, computeSubsystemImportServiceHeader());
 	}
-	
+
 	private void addSubsystemSymbolicNameHeader(SubsystemManifest.Builder builder, SubsystemManifest manifest) {
 		addHeader(builder, computeSubsystemSymbolicNameHeader(manifest));
 	}
-	
+
 	private void addSubsystemVersionHeader(SubsystemManifest.Builder builder, SubsystemManifest manifest) {
 		addHeader(builder, computeSubsystemVersionHeader(manifest));
 	}
-	
+
 	private List<Capability> computeCapabilities() {
 		return subsystemManifest.toCapabilities(this);
 	}
-	
+
 	private DeploymentManifest computeDeploymentManifest(IDirectory directory) throws IOException {
 		return computeExistingDeploymentManifest(directory);
 	}
-	
+
 	private DeploymentManifest computeExistingDeploymentManifest(IDirectory directory) throws IOException {
 		Manifest manifest = ManifestProcessor.obtainManifestFromAppDir(directory, "OSGI-INF/DEPLOYMENT.MF");
 		if (manifest == null)
 			return null;
 		return new DeploymentManifest(manifest);
 	}
-	
+
 	private ImportPackageHeader computeImportPackageHeader() {
 		if (requirements.isEmpty())
 			return null;
@@ -324,7 +330,7 @@ public class RawSubsystemResource implem
 		clauses.trimToSize();
 		return new ImportPackageHeader(clauses);
 	}
-	
+
 	private Repository computeLocalRepository() {
 		if (fakeImportServiceResource != null) {
 			Collection<Resource> temp = new ArrayList<Resource>(resources);
@@ -333,7 +339,7 @@ public class RawSubsystemResource implem
 		}
 		return new LocalRepository(resources);
 	}
-	
+
 	private RequireBundleHeader computeRequireBundleHeader() {
 		if (requirements.isEmpty())
 			return null;
@@ -348,14 +354,14 @@ public class RawSubsystemResource implem
 		clauses.trimToSize();
 		return new RequireBundleHeader(clauses);
 	}
-	
+
 	private RequireCapabilityHeader computeRequireCapabilityHeader() {
 		if (requirements.isEmpty())
 			return null;
 		ArrayList<RequireCapabilityHeader.Clause> clauses = new ArrayList<RequireCapabilityHeader.Clause>();
 		for (Requirement requirement : requirements) {
 			String namespace = requirement.getNamespace();
-			if (namespace.startsWith("osgi.") && 
+			if (namespace.startsWith("osgi.") &&
 					// Don't filter out the osgi.ee namespace.
 					!namespace.equals(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE))
 				continue;
@@ -366,7 +372,7 @@ public class RawSubsystemResource implem
 		clauses.trimToSize();
 		return new RequireCapabilityHeader(clauses);
 	}
-	
+
 	private List<Requirement> computeRequirements(SubsystemManifest manifest) throws ResolutionException {
 		if (isComposite(manifest))
 			return manifest.toRequirements(this);
@@ -387,58 +393,85 @@ public class RawSubsystemResource implem
 		}
 		return new DependencyCalculator(resources).calculateDependencies();
 	}
-	
-	private Collection<Resource> computeResources(IDirectory directory) throws IOException, URISyntaxException, ResolutionException {
+
+	private Collection<Resource> computeResources(IDirectory directory, SubsystemManifest manifest) throws IOException, URISyntaxException, ResolutionException {
 		List<IFile> files = directory.listFiles();
 		if (files.isEmpty())
 			return Collections.emptyList();
 		ArrayList<Resource> result = new ArrayList<Resource>(files.size());
 		for (IFile file : directory.listFiles()) {
-			String name = file.getName();
-			if (file.isFile()) {
-				// Subsystem resources must end with ".esa".
-				if (name.endsWith(".esa"))
-					result.add(new RawSubsystemResource(convertFileToLocation(file), file.convertNested()));
-				else {
-					// Assume all other resources are bundles.
-					try {
-						result.add(new BundleResource(file));
-					}
-					catch (Exception e) {
-						// Ignore if the resource is an invalid bundle or not a bundle at all.
-						if (logger.isDebugEnabled()) {
-							logger.debug("File \"" + file.getName() + "\" in subsystem with location \"" + location + "\" will be ignored because it is not recognized as a supported resource", e);
-						}
-					}
-				}
-			}
-			else {
-				if (name.endsWith(".esa"))
-					result.add(new RawSubsystemResource(convertFileToLocation(file), file.convert()));
-				else {
-					try {
-						result.add(new BundleResource(file));
-					}
-					catch (Exception e) {
-						// Ignore
-						if (logger.isDebugEnabled()) {
-							logger.debug("File \"" + file.getName() + "\" in subsystem with location \"" + location + "\" will be ignored because it is not recognized as a supported resource", e);
-						}
-					}
-				}
-			}
+            if (file.isFile()) {
+                addResource(file, file.convertNested(), manifest, result);
+            } else {
+                addResource(file, file.convert(), manifest, result);
+            }
 		}
 		result.trimToSize();
 		return result;
 	}
-	
-	private SubsystemContentHeader computeSubsystemContentHeader(SubsystemManifest manifest) {
+
+    private void addResource(IFile file, IDirectory content, SubsystemManifest manifest, ArrayList<Resource> result) throws URISyntaxException,
+            IOException, ResolutionException, MalformedURLException {
+        String name = file.getName();
+        if (name.endsWith(".esa")) {
+        	result.add(new RawSubsystemResource(convertFileToLocation(file), content, parentSubsystem));
+        } else if (name.endsWith(".jar")) {
+            result.add(new BundleResource(file));
+        } else {
+            // This is a different type of file. Add a file resource for it if there is a custom content handler for it.
+            FileResource fr = new FileResource(file);
+            fr.setCapabilities(computeFileCapabilities(fr, file, manifest));
+            List<Capability> idcaps = fr.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE);
+            if (idcaps.size() > 0) {
+                Capability idcap = idcaps.get(0);
+                Object type = idcap.getAttributes().get(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE);
+                if (type instanceof String && parentSubsystem != null) {
+                    if (CustomResources.getCustomContentHandler(parentSubsystem, (String) type) != null) {
+                        // Yes, there is a custom content handler, add it.
+                        result.add(fr);
+                        return;
+                    }
+                }
+            }
+
+            // There is no custom handler for this resource, let's check if it turns out to be a bundle
+            try {
+                result.add(new BundleResource(file));
+            } catch (Exception e) {
+                // Ignore if the resource is an invalid bundle or not a bundle at all.
+                if (logger.isDebugEnabled()) {
+                    logger.debug("File \"" + file.getName() + "\" in subsystem with location \"" + location + "\" will be ignored because it is not recognized as a supported resource", e);
+                }
+            }
+        }
+    }
+
+    private List<Capability> computeFileCapabilities(FileResource resource, IFile file, SubsystemManifest manifest) {
+        SubsystemContentHeader ssch = manifest.getSubsystemContentHeader();
+        if (ssch == null)
+            return Collections.emptyList();
+
+        for (Clause c : ssch.getClauses()) {
+            Attribute er = c.getAttribute(ContentHandler.EMBEDDED_RESOURCE_ATTRIBUTE);
+            if (er != null) {
+                if (file.getName().equals(er.getValue())) {
+                    Map<String, Object> attrs = new HashMap<String, Object>();
+                    attrs.put(ContentHandler.EMBEDDED_RESOURCE_ATTRIBUTE, er.getValue());
+                    return Collections.<Capability> singletonList(
+                            new OsgiIdentityCapability(resource, c.getSymbolicName(), c.getVersionRange().getLeft(), c.getType(), attrs));
+                }
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    private SubsystemContentHeader computeSubsystemContentHeader(SubsystemManifest manifest) {
 		SubsystemContentHeader header = manifest.getSubsystemContentHeader();
 		if (header == null && !resources.isEmpty())
 			header = SubsystemContentHeader.newInstance(resources);
 		return header;
 	}
-	
+
 	private SubsystemImportServiceHeader computeSubsystemImportServiceHeader() {
 		if (requirements.isEmpty())
 			return null;
@@ -453,7 +486,7 @@ public class RawSubsystemResource implem
 		clauses.trimToSize();
 		return new SubsystemImportServiceHeader(clauses);
 	}
-	
+
 	private SubsystemManifest computeSubsystemManifestAfterRequirements(SubsystemManifest manifest) {
 		if (isComposite(manifest))
 			return manifest;
@@ -464,7 +497,7 @@ public class RawSubsystemResource implem
 		addSubsystemImportServiceHeader(builder);
 		return builder.build();
 	}
-	
+
 	private SubsystemManifest computeSubsystemManifestBeforeRequirements(SubsystemManifest manifest) {
 		SubsystemManifest.Builder builder = new SubsystemManifest.Builder().manifest(manifest);
 		addSubsystemSymbolicNameHeader(builder, manifest);
@@ -472,7 +505,7 @@ public class RawSubsystemResource implem
 		addSubsystemContentHeader(builder, manifest);
 		return builder.build();
 	}
-	
+
 	private SubsystemSymbolicNameHeader computeSubsystemSymbolicNameHeader(SubsystemManifest manifest) {
 		SubsystemSymbolicNameHeader header = manifest.getSubsystemSymbolicNameHeader();
 		if (header != null)
@@ -482,14 +515,14 @@ public class RawSubsystemResource implem
 			symbolicName = "org.apache.aries.subsystem." + id;
 		return new SubsystemSymbolicNameHeader(symbolicName);
 	}
-	
+
 	private SubsystemVersionHeader computeSubsystemVersionHeader(SubsystemManifest manifest) {
 		SubsystemVersionHeader header = manifest.getSubsystemVersionHeader();
 		if (header.getVersion().equals(Version.emptyVersion) && location.getVersion() != null)
 			header = new SubsystemVersionHeader(location.getVersion());
 		return header;
 	}
-	
+
 	private DeploymentManifest initializeDeploymentManifest(IDirectory idir)
 			throws IOException {
 		Manifest manifest = ManifestProcessor.obtainManifestFromAppDir(idir,
@@ -504,7 +537,7 @@ public class RawSubsystemResource implem
 					.state(State.INSTALLING)
 					.build();
 	}
-	
+
 	private SubsystemManifest initializeSubsystemManifest(IDirectory idir)
 			throws IOException {
 		Manifest manifest = ManifestProcessor.obtainManifestFromAppDir(idir,
@@ -522,7 +555,7 @@ public class RawSubsystemResource implem
 							+ SubsystemTypeHeader.PROVISION_POLICY_ACCEPT_DEPENDENCIES)
 					.build();
 	}
-	
+
 	private boolean isComposite(SubsystemManifest manifest) {
 		return SubsystemConstants.SUBSYSTEM_TYPE_COMPOSITE.equals(manifest.getSubsystemTypeHeader().getType());
 	}



Mime
View raw message