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());
        }


Reply via email to