[FELIX-4445] PojoSR code initial commit

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1583367 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pojosr/DEPENDENCIES b/pojosr/DEPENDENCIES
new file mode 100644
index 0000000..dc8e92b
--- /dev/null
+++ b/pojosr/DEPENDENCIES
@@ -0,0 +1,28 @@
+pojosr Framework
+
+I. Included Third-Party Software
+
+This product includes software developed at
+The Apache Software Foundation 
+(http://www.apache.org)
+Copyright (c) Apache Software Foundation
+Licensded under the Apache License 2.0
+
+This product includes software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2009).
+Licensed under the Apache License 2.0.
+
+II. Used Third-Party Software
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2009).
+Licensed under the Apache License 2.0.
+
+This product uses software developed at
+The Codehaus (http://www.codehaus.org)
+Licensed under the Apache License 2.0.
+
+III. License Summary
+- Apache License 2.0
diff --git a/pojosr/LICENSE b/pojosr/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/pojosr/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/pojosr/NOTICE b/pojosr/NOTICE
new file mode 100644
index 0000000..6548480
--- /dev/null
+++ b/pojosr/NOTICE
@@ -0,0 +1,10 @@
+pojosr Framework
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2009).
+Licensed under the Apache License 2.0.
diff --git a/pojosr/pom.xml b/pojosr/pom.xml
new file mode 100644
index 0000000..0b7ff8f
--- /dev/null
+++ b/pojosr/pom.xml
@@ -0,0 +1,144 @@
+<!--
+
+    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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.sonatype.oss</groupId>
+    <artifactId>oss-parent</artifactId>
+    <version>7</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <packaging>bundle</packaging>
+  <name>Pojo Service Registry</name>
+  <groupId>com.googlecode.pojosr</groupId>
+  <artifactId>de.kalpatec.pojosr.framework</artifactId>
+  <version>0.3.0-SNAPSHOT</version>
+  <description>A service registry that enables OSGi style service registry programs without using an OSGi framework.</description>
+  <url>http://pojosr.googlecode.com/</url>
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <scm>
+    <url>http://pojosr.googlecode.com/svn/trunk/</url>
+    <connection>scm:svn:http://pojosr.googlecode.com/svn/trunk/framework/</connection>
+    <developerConnection>scm:svn:https://pojosr.googlecode.com/svn/trunk/framework/</developerConnection>
+  </scm>
+  <developers>
+    <developer>
+      <id>karlpauls</id>
+      <name>Karl Pauls</name>
+      <email>karlpauls@gmail.com</email>
+    </developer>
+  </developers>
+  <dependencies>
+      <dependency>
+          <groupId>org.osgi</groupId>
+          <artifactId>org.osgi.core</artifactId>
+          <version>4.3.1</version>
+      </dependency>
+      <dependency>
+          <groupId>org.osgi</groupId>
+          <artifactId>org.osgi.compendium</artifactId>
+          <version>4.2.0</version>
+      </dependency> 
+  </dependencies>
+  <repositories />
+  <pluginRepositories />
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-release-plugin</artifactId>
+        <configuration>
+          <tagBase>https://pojosr.googlecode.com/svn/tags</tagBase>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <target>1.5</target>
+          <source>1.5</source>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <version>2.1.2</version>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>jar-no-fork</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.8</version>
+        <executions>
+          <execution>
+            <id>attach-javadoc</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <version>2.3.4</version>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>de.kalpatec.pojosr.framework</Bundle-SymbolicName>
+            <Bundle-Name>Pojo Service Registry</Bundle-Name>
+            <Bundle-Vendor>Apache Software Foundation</Bundle-Vendor>
+            <Private-Package>org.osgi.framework.*, org.osgi.service.url, org.osgi.service.packageadmin, org.osgi.service.startlevel, org.osgi.util.tracker, de.kalpatec.pojosr.framework.*</Private-Package>
+            <Import-Package>!*</Import-Package>
+            <Include-Resource>META-INF/LICENSE=LICENSE,META-INF/NOTICE=NOTICE,META-INF/DEPENDENCIES=DEPENDENCIES,{src/main/resources/}</Include-Resource> 
+            <Main-Class>de.kalpatec.pojosr.framework.PojoSR</Main-Class>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+     <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+      </resource>
+      <resource>
+      <directory>.</directory>
+      <targetPath>META-INF</targetPath>
+      <includes>
+        <include>LICENSE*</include>
+        <include>NOTICE*</include>
+        <include>DEPENDENCIES*</include>
+      </includes>
+    </resource>
+    </resources> 
+  </build>
+</project>
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/DirRevision.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/DirRevision.java
new file mode 100644
index 0000000..7772585
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/DirRevision.java
@@ -0,0 +1,70 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Map;
+
+import de.kalpatec.pojosr.framework.felix.framework.util.StringMap;
+
+class DirRevision extends Revision
+{
+    private final File m_file;
+
+    public DirRevision(File file)
+    {
+        m_file = file;
+    }
+
+    @Override
+    public long getLastModified()
+    {
+        return m_file.lastModified();
+    }
+
+    public Enumeration getEntries()
+    {
+        return new FileEntriesEnumeration(m_file);
+    }
+
+    @Override
+    public URL getEntry(String entryName)
+    {
+        try
+        {
+		    if (entryName != null) {
+            File file = (new File(m_file, (entryName.startsWith("/")) ? entryName.substring(1) : entryName));
+            if (file.exists()) {
+                return file.toURL();
+            } 
+			}
+        }
+        catch (MalformedURLException e)
+        {
+            e.printStackTrace();
+        }
+            return null;
+
+    }
+
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/EntriesEnumeration.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/EntriesEnumeration.java
new file mode 100644
index 0000000..879dae3
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/EntriesEnumeration.java
@@ -0,0 +1,68 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+
+class EntriesEnumeration implements Enumeration
+{
+    private final Enumeration m_enumeration;
+	private final String m_prefix;
+	private volatile Object current;
+
+    public EntriesEnumeration(Enumeration enumeration)
+    {
+        this(enumeration, null);
+    }
+	
+	public EntriesEnumeration(Enumeration enumeration, String prefix)
+	{
+	   m_enumeration = enumeration;
+	   m_prefix = prefix;
+	}
+
+    public boolean hasMoreElements() {
+				while ((current == null) && m_enumeration.hasMoreElements()) {
+					String result = (String) ((ZipEntry) m_enumeration.nextElement()).getName();
+					if (m_prefix != null){
+						if (result.startsWith(m_prefix)) {
+							current = result.substring(m_prefix.length());
+						}
+					}
+					else {
+						current = result;
+					}
+				}
+				return (current != null);
+			}
+
+			public Object nextElement() {
+				try {
+					if (hasMoreElements()) {
+						return current;
+					}
+					else {
+						return m_enumeration.nextElement();
+					}
+				} finally { 
+					current = null;
+				}
+			}
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/EntryFilterEnumeration.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/EntryFilterEnumeration.java
new file mode 100644
index 0000000..d91092e
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/EntryFilterEnumeration.java
@@ -0,0 +1,236 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.SimpleFilter;
+
+class EntryFilterEnumeration<T> implements Enumeration<T>
+{
+    private final Enumeration<String> m_enumeration;
+    private final Revision m_revision;
+    private final String m_path;
+    private final List<String> m_filePattern;
+    private final boolean m_recurse;
+    private final boolean m_isURLValues;
+    private final Set<String> m_dirEntries = new HashSet<String>();
+    private final List<T> m_nextEntries = new ArrayList<T>(2);
+
+    public EntryFilterEnumeration(Revision rev, boolean includeFragments,
+            String path, String filePattern, boolean recurse,
+            boolean isURLValues)
+    {
+        m_revision = rev;
+        m_enumeration = rev.getEntries();
+        m_recurse = recurse;
+        m_isURLValues = isURLValues;
+
+        // Sanity check the parameters.
+        if (path == null)
+        {
+            throw new IllegalArgumentException(
+                    "The path for findEntries() cannot be null.");
+        }
+        // Strip leading '/' if present.
+        if ((path.length() > 0) && (path.charAt(0) == '/'))
+        {
+            path = path.substring(1);
+        }
+        // Add a '/' to the end if not present.
+        if ((path.length() > 0) && (path.charAt(path.length() - 1) != '/'))
+        {
+            path = path + "/";
+        }
+        m_path = path;
+
+        // File pattern defaults to "*" if not specified.
+        filePattern = (filePattern == null) ? "*" : filePattern;
+
+        m_filePattern = SimpleFilter.parseSubstring(filePattern);
+
+        findNext();
+    }
+
+    public synchronized boolean hasMoreElements()
+    {
+        return (m_nextEntries.size() != 0);
+    }
+
+    public synchronized T nextElement()
+    {
+        if (m_nextEntries.size() == 0)
+        {
+            throw new NoSuchElementException("No more entries.");
+        }
+        T last = m_nextEntries.remove(0);
+        findNext();
+        return last;
+    }
+
+    private void findNext()
+    {
+        // This method filters the content entry enumeration, such that
+        // it only displays the contents of the directory specified by
+        // the path argument either recursively or not; much like using
+        // "ls -R" or "ls" to list the contents of a directory, respectively.
+        if (m_enumeration == null)
+        {
+            return;
+        }
+        if (m_nextEntries.size() == 0)
+        {
+            while (m_enumeration.hasMoreElements() && m_nextEntries.size() == 0)
+            {
+                // Get the current entry to determine if it should be filtered
+                // or not.
+                String entryName = (String) m_enumeration.nextElement();
+                // Check to see if the current entry is a descendent of the
+                // specified path.
+                if (!entryName.equals(m_path) && entryName.startsWith(m_path))
+                {
+                    // Cached entry URL. If we are returning URLs, we use this
+                    // cached URL to avoid doing multiple URL lookups from a
+                    // module
+                    // when synthesizing directory URLs.
+                    URL entryURL = null;
+
+                    // If the current entry is in a subdirectory of the
+                    // specified path,
+                    // get the index of the slash character.
+                    int dirSlashIdx = entryName.indexOf('/', m_path.length());
+
+                    // JAR files are supposed to contain entries for
+                    // directories,
+                    // but not all do. So calculate the directory for this entry
+                    // and see if we've already seen an entry for the directory.
+                    // If not, synthesize an entry for the directory. If we are
+                    // doing a recursive match, we need to synthesize each
+                    // matching
+                    // subdirectory of the entry.
+                    if (dirSlashIdx >= 0)
+                    {
+                        // Start synthesizing directories for the current entry
+                        // at the subdirectory after the initial path.
+                        int subDirSlashIdx = dirSlashIdx;
+                        String dir;
+                        do
+                        {
+                            // Calculate the subdirectory name.
+                            dir = entryName.substring(0, subDirSlashIdx + 1);
+                            // If we have not seen this directory before, then
+                            // record
+                            // it and potentially synthesize an entry for it.
+                            if (!m_dirEntries.contains(dir))
+                            {
+                                // Record it.
+                                m_dirEntries.add(dir);
+                                // If the entry is actually a directory entry
+                                // (i.e.,
+                                // it ends with a slash), then we don't need to
+                                // synthesize an entry since it exists;
+                                // otherwise,
+                                // synthesize an entry if it matches the file
+                                // pattern.
+                                if (entryName.length() != (subDirSlashIdx + 1))
+                                {
+                                    // See if the file pattern matches the last
+                                    // element of the path.
+                                    if (SimpleFilter.compareSubstring(
+                                            m_filePattern,
+                                            getLastPathElement(dir)))
+                                    {
+                                        if (m_isURLValues)
+                                        {
+                                            entryURL = (entryURL == null) ? m_revision
+                                                    .getEntry(entryName)
+                                                    : entryURL;
+                                            try
+                                            {
+                                                m_nextEntries.add((T) new URL(
+                                                        entryURL, "/" + dir));
+                                            }
+                                            catch (MalformedURLException ex)
+                                            {
+                                            }
+                                        }
+                                        else
+                                        {
+                                            m_nextEntries.add((T) dir);
+                                        }
+                                    }
+                                }
+                            }
+                            // Now prepare to synthesize the next subdirectory
+                            // if we are matching recursively.
+                            subDirSlashIdx = entryName.indexOf('/',
+                                    dir.length());
+                        }
+                        while (m_recurse && (subDirSlashIdx >= 0));
+                    }
+
+                    // Now we actually need to check if the current entry itself
+                    // should
+                    // be filtered or not. If we are recursive or the current
+                    // entry
+                    // is a child (not a grandchild) of the initial path, then
+                    // we need
+                    // to check if it matches the file pattern.
+                    if (m_recurse || (dirSlashIdx < 0)
+                            || (dirSlashIdx == entryName.length() - 1))
+                    {
+                        // See if the file pattern matches the last element of
+                        // the path.
+                        if (SimpleFilter.compareSubstring(m_filePattern,
+                                getLastPathElement(entryName)))
+                        {
+                            if (m_isURLValues)
+                            {
+                                entryURL = (entryURL == null) ? m_revision
+                                        .getEntry(entryName) : entryURL;
+                                m_nextEntries.add((T) entryURL);
+                            }
+                            else
+                            {
+                                m_nextEntries.add((T) entryName);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static String getLastPathElement(String entryName)
+    {
+        int endIdx = (entryName.charAt(entryName.length() - 1) == '/') ? entryName
+                .length() - 1 : entryName.length();
+        int startIdx = (entryName.charAt(entryName.length() - 1) == '/') ? entryName
+                .lastIndexOf('/', endIdx - 1) + 1 : entryName.lastIndexOf('/',
+                endIdx) + 1;
+        return entryName.substring(startIdx, endIdx);
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/FileEntriesEnumeration.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/FileEntriesEnumeration.java
new file mode 100644
index 0000000..f6478eb
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/FileEntriesEnumeration.java
@@ -0,0 +1,88 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+class FileEntriesEnumeration implements Enumeration
+{
+    private final File m_dir;
+    private final File[] m_children;
+    private int m_counter = 0;
+
+    public FileEntriesEnumeration(File dir)
+    {
+        m_dir = dir;
+        m_children = listFilesRecursive(m_dir);
+    }
+
+    public synchronized boolean hasMoreElements()
+    {
+        return (m_children != null) && (m_counter < m_children.length);
+    }
+
+    public synchronized Object nextElement()
+    {
+        if ((m_children == null) || (m_counter >= m_children.length))
+        {
+            throw new NoSuchElementException("No more entry paths.");
+        }
+
+        // Convert the file separator character to slashes.
+        String abs = m_children[m_counter].getAbsolutePath().replace(
+                File.separatorChar, '/');
+
+        // Remove the leading path of the reference directory, since the
+        // entry paths are supposed to be relative to the root.
+        StringBuffer sb = new StringBuffer(abs);
+        sb.delete(0, m_dir.getAbsolutePath().length() + 1);
+        // Add a '/' to the end of directory entries.
+        if (m_children[m_counter].isDirectory())
+        {
+            sb.append('/');
+        }
+        m_counter++;
+        return sb.toString();
+    }
+
+    private File[] listFilesRecursive(File dir)
+    {
+        File[] children = dir.listFiles();
+        File[] combined = children;
+        for (int i = 0; i < children.length; i++)
+        {
+            if (children[i].isDirectory())
+            {
+                File[] grandchildren = listFilesRecursive(children[i]);
+                if (grandchildren.length > 0)
+                {
+                    File[] tmp = new File[combined.length
+                            + grandchildren.length];
+                    System.arraycopy(combined, 0, tmp, 0, combined.length);
+                    System.arraycopy(grandchildren, 0, tmp, combined.length,
+                            grandchildren.length);
+                    combined = tmp;
+                }
+            }
+        }
+        return combined;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/JarRevision.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/JarRevision.java
new file mode 100644
index 0000000..cbfaff0
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/JarRevision.java
@@ -0,0 +1,131 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.io.*;
+import java.net.URLStreamHandler;
+import java.net.URLConnection;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.jar.JarFile;
+import java.util.jar.JarEntry;
+import java.util.zip.ZipEntry;
+
+import de.kalpatec.pojosr.framework.felix.framework.util.StringMap;
+
+class JarRevision extends Revision
+{
+    private final long m_lastModified;
+    private final JarFile m_jar;
+    private final URL m_url;
+	private final String m_urlString;
+	private final String m_prefix;
+
+    public JarRevision(JarFile jar, URL url, String prefix, long lastModified)
+    {
+        m_jar = jar;
+        m_url = url;
+		m_urlString =  m_url.toExternalForm();
+		m_prefix = prefix;
+        if (lastModified > 0)
+        {
+            m_lastModified = lastModified;
+        }
+        else
+        {
+            m_lastModified = System.currentTimeMillis();
+        }
+    }
+
+    @Override
+    public long getLastModified()
+    {
+        return m_lastModified;
+    }
+
+    public Enumeration getEntries()
+    {
+        return new EntriesEnumeration(m_jar.entries(), m_prefix);
+    }
+
+    @Override
+    public URL getEntry(String entryName)
+    {
+        try
+        {
+		    if("/".equals(entryName) || "".equals(entryName) || " ".equals(entryName)) {
+			    return new URL("jar:" + m_urlString + "!/" + ((m_prefix == null) ? "" : m_prefix));
+			}
+            if (entryName != null)
+			{ 
+				final String target = ((entryName.startsWith("/")) ? entryName.substring(1) : entryName);
+				final JarEntry entry = m_jar.getJarEntry(((m_prefix == null) ? "" : m_prefix) + target);
+				if ( entry != null) {
+								URL result = new URL(null, "jar:" + m_urlString + "!/" + ((m_prefix == null) ? "" : m_prefix) + target, new URLStreamHandler() {
+									
+									
+									protected URLConnection openConnection(final URL u) throws IOException {
+										return new java.net.JarURLConnection(u) {
+											
+											public JarFile getJarFile() {
+											    return m_jar;
+											}
+											public void connect() throws IOException {
+												// TODO Auto-generated method stub
+												
+											}
+											
+											public InputStream getInputStream()
+													throws IOException {
+													
+												String extF = u.toExternalForm();
+												JarEntry targetEntry = entry;
+												if (!extF.endsWith(target)) {
+												    extF = extF.substring(extF.indexOf('!') + 2);
+												    if (m_prefix != null) {
+														if (!extF.startsWith(m_prefix)) {
+														    extF = m_prefix + extF;
+														}
+													}
+													targetEntry = m_jar.getJarEntry(extF);
+												}
+												return m_jar.getInputStream(targetEntry);
+											}
+										};
+									}
+								});
+								return result;
+							}
+							else {
+								if (entryName.endsWith("/")) {
+								     return new URL("jar:" + m_urlString + "!/" + ((m_prefix == null) ? "" : m_prefix) + target);
+								}
+							}
+			}
+        }
+        catch (IOException e)
+        {
+            e.printStackTrace();
+        }
+            return null;
+
+    }
+
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSR.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSR.java
new file mode 100644
index 0000000..13c5ff7
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSR.java
@@ -0,0 +1,513 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.io.File;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.Version;
+import org.osgi.service.packageadmin.ExportedPackage;
+import org.osgi.service.packageadmin.PackageAdmin;
+import org.osgi.service.packageadmin.RequiredBundle;
+import org.osgi.service.startlevel.StartLevel;
+
+import de.kalpatec.pojosr.framework.felix.framework.ServiceRegistry;
+import de.kalpatec.pojosr.framework.felix.framework.util.EventDispatcher;
+import de.kalpatec.pojosr.framework.launch.BundleDescriptor;
+import de.kalpatec.pojosr.framework.launch.ClasspathScanner;
+import de.kalpatec.pojosr.framework.launch.PojoServiceRegistry;
+import de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory;
+
+public class PojoSR implements PojoServiceRegistry
+{
+    private final BundleContext m_context;
+    private final ServiceRegistry m_reg = new ServiceRegistry(
+            new ServiceRegistry.ServiceRegistryCallbacks()
+            {
+
+                public void serviceChanged(ServiceEvent event,
+                        Dictionary oldProps)
+                {
+                    m_dispatcher.fireServiceEvent(event, oldProps, null);
+                }
+            });
+
+    private final EventDispatcher m_dispatcher = new EventDispatcher(m_reg);
+    private final Map<Long, Bundle> m_bundles =new HashMap<Long, Bundle>();
+    private final Map<String, Bundle> m_symbolicNameToBundle = new HashMap<String, Bundle>();
+    private final Map bundleConfig;
+    public PojoSR(Map config) throws Exception
+    {
+        final Map<String, String> headers = new HashMap<String, String>();
+        headers.put(Constants.BUNDLE_SYMBOLICNAME,
+                "de.kalpatec.pojosr.framework");
+        headers.put(Constants.BUNDLE_VERSION, "0.3.0-SNAPSHOT");
+        headers.put(Constants.BUNDLE_NAME, "System Bundle");
+        headers.put(Constants.BUNDLE_MANIFESTVERSION, "2");
+		headers.put(Constants.BUNDLE_VENDOR, "kalpatec");
+        bundleConfig = new HashMap(config);
+        final Bundle b = new PojoSRBundle(new Revision()
+        {
+
+            @Override
+            public long getLastModified()
+            {
+                // TODO Auto-generated method stub
+                return System.currentTimeMillis();
+            }
+
+            @Override
+            public Enumeration getEntries()
+            {
+                return new Properties().elements();
+            }
+
+            @Override
+            public URL getEntry(String entryName)
+            {
+                return getClass().getClassLoader().getResource(entryName);
+            }
+        }, headers, new Version(0, 0, 1), "file:pojosr", m_reg, m_dispatcher,
+                null, 0, "de.kalpatec.pojosr.framework", m_bundles, getClass()
+                        .getClassLoader(), bundleConfig)
+        {
+        	@Override
+        	public synchronized void start() throws BundleException {
+        		if (m_state != Bundle.RESOLVED) {
+        			return;
+        		}
+        		m_dispatcher.startDispatching();
+        		m_state = Bundle.STARTING;
+
+                m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STARTING,
+                        this));
+                m_context = new PojoSRBundleContext(this, m_reg, m_dispatcher,
+                                m_bundles, bundleConfig);
+                int i = 0;
+                for (Bundle b : m_bundles.values()) {
+                	i++;
+                	try
+                    {
+                        if (b != this)
+                        {
+                            b.start();
+                        }
+                    }
+                    catch (Throwable t)
+                    {
+                    	System.out.println("Unable to start bundle: " + i );
+                    	t.printStackTrace();
+                    }
+                }
+                m_state = Bundle.ACTIVE;
+                m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STARTED,
+                        this));
+
+                m_dispatcher.fireFrameworkEvent(new FrameworkEvent(FrameworkEvent.STARTED, this, null));
+        		super.start();
+        	};
+            @Override
+            public synchronized void stop() throws BundleException
+            {
+            	if ((m_state == Bundle.STOPPING) || m_state == Bundle.RESOLVED) {
+            		return;
+
+            	}
+            	else if (m_state != Bundle.ACTIVE) {
+            		throw new BundleException("Can't stop pojosr because it is not ACTIVE");
+            	}
+            	final Bundle systemBundle = this;
+            	Runnable r = new Runnable() {
+
+					public void run() {
+		                m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPING,
+		                		systemBundle));
+		                for (Bundle b : m_bundles.values())
+		                {
+		                    try
+		                    {
+		                        if (b != systemBundle)
+		                        {
+		                            b.stop();
+		                        }
+		                    }
+		                    catch (Throwable t)
+		                    {
+		                        t.printStackTrace();
+		                    }
+		                }
+		                m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPED,
+		                		systemBundle));
+		                m_state = Bundle.RESOLVED;
+		                m_dispatcher.stopDispatching();
+					}
+				};
+				m_state = Bundle.STOPPING;
+				if ("true".equalsIgnoreCase(System.getProperty("de.kalpatec.pojosr.framework.events.sync"))) {
+					r.run();
+				}
+				else {
+					new Thread(r).start();
+				}
+            }
+        };
+        m_symbolicNameToBundle.put(b.getSymbolicName(), b);
+        b.start();
+        b.getBundleContext().registerService(StartLevel.class.getName(),
+                new StartLevel()
+                {
+
+                    public void setStartLevel(int startlevel)
+                    {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    public void setInitialBundleStartLevel(int startlevel)
+                    {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    public void setBundleStartLevel(Bundle bundle,
+                            int startlevel)
+                    {
+                        // TODO Auto-generated method stub
+
+                    }
+
+                    public boolean isBundlePersistentlyStarted(Bundle bundle)
+                    {
+                        // TODO Auto-generated method stub
+                        return true;
+                    }
+
+                    public boolean isBundleActivationPolicyUsed(Bundle bundle)
+                    {
+                        // TODO Auto-generated method stub
+                        return false;
+                    }
+
+                    public int getStartLevel()
+                    {
+                        // TODO Auto-generated method stub
+                        return 1;
+                    }
+
+                    public int getInitialBundleStartLevel()
+                    {
+                        // TODO Auto-generated method stub
+                        return 1;
+                    }
+
+                    public int getBundleStartLevel(Bundle bundle)
+                    {
+                        // TODO Auto-generated method stub
+                        return 1;
+                    }
+                }, null);
+
+        b.getBundleContext().registerService(PackageAdmin.class.getName(),
+                new PackageAdmin()
+                {
+
+                    public boolean resolveBundles(Bundle[] bundles)
+                    {
+                        // TODO Auto-generated method stub
+                        return true;
+                    }
+
+                    public void refreshPackages(Bundle[] bundles)
+                    {
+                        m_dispatcher.fireFrameworkEvent(new FrameworkEvent(
+                                FrameworkEvent.PACKAGES_REFRESHED, b, null));
+                    }
+
+                    public RequiredBundle[] getRequiredBundles(
+                            String symbolicName)
+                    {
+                        return null;
+                    }
+
+                    public Bundle[] getHosts(Bundle bundle)
+                    {
+                        // TODO Auto-generated method stub
+                        return null;
+                    }
+
+                    public Bundle[] getFragments(Bundle bundle)
+                    {
+                        // TODO Auto-generated method stub
+                        return null;
+                    }
+
+                    public ExportedPackage[] getExportedPackages(String name)
+                    {
+                        // TODO Auto-generated method stub
+                        return null;
+                    }
+
+                    public ExportedPackage[] getExportedPackages(Bundle bundle)
+                    {
+                        // TODO Auto-generated method stub
+                        return null;
+                    }
+
+                    public ExportedPackage getExportedPackage(String name)
+                    {
+                        // TODO Auto-generated method stub
+                        return null;
+                    }
+
+                    public Bundle[] getBundles(String symbolicName,
+                            String versionRange)
+                    {
+					    Bundle result = m_symbolicNameToBundle.get((symbolicName != null) ? symbolicName.trim() : symbolicName);
+						if (result != null) {
+							return new Bundle[] {result};
+						}
+						return null;
+                    }
+
+                    public int getBundleType(Bundle bundle)
+                    {
+                        return 0;
+                    }
+
+                    public Bundle getBundle(Class clazz)
+                    {
+                        return m_context.getBundle();
+                    }
+                }, null);
+        m_context = b.getBundleContext();
+
+        List<BundleDescriptor> scan = (List<BundleDescriptor>) config
+                .get(PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS);
+
+        if (scan != null)
+        {
+            startBundles(scan);
+		}
+    }
+
+	public void startBundles(List<BundleDescriptor> scan) throws Exception {
+	 for (BundleDescriptor desc : scan)
+            {
+                URL u = new URL(desc.getUrl().toExternalForm()
+                        + "META-INF/MANIFEST.MF");
+                Revision r;
+                if (u.toExternalForm().startsWith("file:"))
+                {
+                    File root = new File(URLDecoder.decode(desc.getUrl()
+                            .getFile(), "UTF-8"));
+                    u = root.toURL();
+                    r = new DirRevision(root);
+                }
+                else
+                {
+                    URLConnection uc = u.openConnection();
+                    if (uc instanceof JarURLConnection)
+                    {
+					    String target = ((JarURLConnection) uc).getJarFileURL().toExternalForm();
+						String prefix = null;
+						if (!("jar:" + target + "!/").equals(desc.getUrl().toExternalForm())) {
+						    prefix = desc.getUrl().toExternalForm().substring(("jar:" + target + "!/").length());
+						}
+                        r = new JarRevision(
+                                ((JarURLConnection) uc).getJarFile(),
+                                ((JarURLConnection) uc).getJarFileURL(),
+								prefix,
+                                uc.getLastModified());
+                    }
+                    else
+                    {
+                        r = new URLRevision(desc.getUrl(), desc.getUrl()
+                                .openConnection().getLastModified());
+                    }
+                }
+                Map<String, String> bundleHeaders = desc.getHeaders();
+				Version osgiVersion = null;
+				try {
+					osgiVersion = Version.parseVersion(bundleHeaders.get(Constants.BUNDLE_VERSION));
+				} catch (Exception ex) {
+					ex.printStackTrace();
+					osgiVersion = Version.emptyVersion;
+				}
+                String sym = bundleHeaders.get(Constants.BUNDLE_SYMBOLICNAME);
+                    if (sym != null)
+                    {
+                        int idx = sym.indexOf(';');
+                        if (idx > 0)
+                        {
+                            sym = sym.substring(0, idx);
+                        }
+						sym = sym.trim();
+                    }
+
+                if ((sym == null)
+                        || !m_symbolicNameToBundle.containsKey( sym ))
+                {
+                    // TODO: framework - support multiple versions
+                    Bundle bundle = new PojoSRBundle(r, bundleHeaders,
+                            osgiVersion, desc.getUrl().toExternalForm(), m_reg,
+                            m_dispatcher,
+                            bundleHeaders.get(Constants.BUNDLE_ACTIVATOR),
+                            m_bundles.size(),
+                            sym,
+                            m_bundles, desc.getClassLoader(), bundleConfig);
+                    if (sym != null)
+                    {
+                        m_symbolicNameToBundle.put(bundle.getSymbolicName(),
+                                bundle);
+                    }
+                }
+
+            }
+
+
+        for (long i = 1; i < m_bundles.size(); i++)
+        {
+            try
+            {
+                m_bundles.get(i).start();
+            }
+            catch (Throwable e)
+            {
+                System.out.println("Unable to start bundle: " + i);
+                e.printStackTrace();
+            }
+        }
+
+	}
+
+    public static void main(String[] args) throws Exception
+    {
+    	Filter filter = null;
+    	Class main = null;
+    	for (int i = 0;(args != null) && (i < args.length) && (i < 2); i++) {
+	    	try {
+	    		filter = FrameworkUtil.createFilter(args[i]);
+	    	} catch (InvalidSyntaxException ie) {
+	    		try {
+	    			main = PojoSR.class.getClassLoader().loadClass(args[i]);
+	    		} catch (Exception ex) {
+	    			throw new IllegalArgumentException("Argument is neither a filter nor a class: " + args[i]);
+	    		}
+	    	}
+    	}
+        Map config = new HashMap();
+        config.put(
+                PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS,
+                (filter != null) ? new ClasspathScanner()
+                        .scanForBundles(filter.toString()) : new ClasspathScanner()
+                        .scanForBundles());
+        new PojoServiceRegistryFactoryImpl().newPojoServiceRegistry(config);
+        if (main != null) {
+        	int count = 0;
+        	if (filter != null) {
+        		count++;
+        	}
+        	if (main != null) {
+        		count++;
+        	}
+        	String[] newArgs = args;
+        	if (count > 0) {
+        		newArgs = new String[args.length - count];
+        		System.arraycopy(args, count, newArgs, 0, newArgs.length);
+        	}
+        	main.getMethod("main", String[].class).invoke(null, (Object) newArgs );
+        }
+    }
+
+    public BundleContext getBundleContext()
+    {
+        return m_context;
+    }
+
+    public void addServiceListener(ServiceListener listener, String filter)
+            throws InvalidSyntaxException
+    {
+        m_context.addServiceListener(listener, filter);
+    }
+
+    public void addServiceListener(ServiceListener listener)
+    {
+        m_context.addServiceListener(listener);
+    }
+
+    public void removeServiceListener(ServiceListener listener)
+    {
+        m_context.removeServiceListener(listener);
+    }
+
+    public ServiceRegistration registerService(String[] clazzes,
+            Object service, Dictionary properties)
+    {
+        return m_context.registerService(clazzes, service, properties);
+    }
+
+    public ServiceRegistration registerService(String clazz, Object service,
+            Dictionary properties)
+    {
+        return m_context.registerService(clazz, service, properties);
+    }
+
+    public ServiceReference[] getServiceReferences(String clazz, String filter)
+            throws InvalidSyntaxException
+    {
+        return m_context.getServiceReferences(clazz, filter);
+    }
+
+    public ServiceReference getServiceReference(String clazz)
+    {
+        return m_context.getServiceReference(clazz);
+    }
+
+    public Object getService(ServiceReference reference)
+    {
+        return m_context.getService(reference);
+    }
+
+    public boolean ungetService(ServiceReference reference)
+    {
+        return m_context.ungetService(reference);
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSRBundle.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSRBundle.java
new file mode 100644
index 0000000..3d61e04
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSRBundle.java
@@ -0,0 +1,659 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.framework.startlevel.BundleStartLevel;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRequirement;
+import org.osgi.framework.wiring.BundleRevision;
+import org.osgi.framework.wiring.BundleRevisions;
+import org.osgi.framework.wiring.BundleWire;
+import org.osgi.framework.wiring.BundleWiring;
+
+
+import de.kalpatec.pojosr.framework.felix.framework.ServiceRegistry;
+import de.kalpatec.pojosr.framework.felix.framework.util.EventDispatcher;
+import de.kalpatec.pojosr.framework.felix.framework.util.MapToDictionary;
+import de.kalpatec.pojosr.framework.felix.framework.util.StringMap;
+
+class PojoSRBundle implements Bundle, BundleRevisions, BundleRevision
+{
+    private final Revision m_revision;
+    private final Map<String, String> m_manifest;
+    private final Version m_version;
+    private final String m_location;
+    private final Map<Long, Bundle> m_bundles;
+    private final ServiceRegistry m_reg;
+    private final String m_activatorClass;
+    private final long m_id;
+    private final String m_symbolicName;
+    private volatile BundleActivator m_activator = null;
+    volatile int m_state = Bundle.RESOLVED;
+    volatile BundleContext m_context = null;
+    private final EventDispatcher m_dispatcher;
+    private final ClassLoader m_loader;
+    private final Map m_config;
+
+    Revision getRevision()
+    {
+        return m_revision;
+    }
+
+    public PojoSRBundle(Revision revision, Map<String, String> manifest,
+            Version version, String location, ServiceRegistry reg,
+            EventDispatcher dispatcher, String activatorClass, long id,
+            String symbolicName, Map<Long, Bundle> bundles, ClassLoader loader, Map config)
+    {
+        m_revision = revision;
+        m_manifest = manifest;
+        m_version = version;
+        m_location = location;
+        m_reg = reg;
+        m_dispatcher = dispatcher;
+        m_activatorClass = activatorClass;
+        m_id = id;
+        m_symbolicName = symbolicName;
+        bundles.put(m_id, this);
+        m_bundles = bundles;
+        m_loader = loader;
+        m_config = config;
+    }
+
+    public int getState()
+    {
+        return m_state;
+    }
+
+    public void start(int options) throws BundleException
+    {
+        // TODO: lifecycle - fix this
+        start();
+    }
+
+    public synchronized void start() throws BundleException
+    {
+        if (m_state != Bundle.RESOLVED)
+        {
+            if (m_state == Bundle.ACTIVE)
+            {
+                return;
+            }
+            throw new BundleException("Bundle is in wrong state for start");
+        }
+        try
+        {
+            m_state = Bundle.STARTING;
+
+            m_context = new PojoSRBundleContext(this, m_reg, m_dispatcher,
+                    m_bundles, m_config);
+            m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STARTING,
+                    this));
+            if (m_activatorClass != null)
+            {
+                m_activator = (BundleActivator) m_loader.loadClass(
+                        m_activatorClass).newInstance();
+                m_activator.start(m_context);
+            }
+            m_state = Bundle.ACTIVE;
+            m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STARTED,
+                    this));
+        }
+        catch (Throwable ex)
+        {
+            m_state = Bundle.RESOLVED;
+            m_activator = null;
+            m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPED,
+                    this));
+            throw new BundleException("Unable to start bundle", ex);
+        }
+    }
+
+    public void stop(int options) throws BundleException
+    {
+        // TODO: lifecycle - fix this
+        stop();
+    }
+
+    public synchronized void stop() throws BundleException
+    {
+        if (m_state != Bundle.ACTIVE)
+        {
+            if (m_state == Bundle.RESOLVED)
+            {
+                return;
+            }
+            throw new BundleException("Bundle is in wrong state for stop");
+        }
+        try
+        {
+            m_state = Bundle.STOPPING;
+            m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPING,
+                    this));
+            if (m_activator != null)
+            {
+                m_activator.stop(m_context);
+            }
+        }
+        catch (Throwable ex)
+        {
+            throw new BundleException("Error while stopping bundle", ex);
+        }
+        finally
+        {
+            m_reg.unregisterServices(this);
+            m_dispatcher.removeListeners(m_context);
+            m_activator = null;
+            m_context = null;
+            m_state = Bundle.RESOLVED;
+            m_dispatcher.fireBundleEvent(new BundleEvent(BundleEvent.STOPPED,
+                    this));
+        }
+    }
+
+    public void update(InputStream input) throws BundleException
+    {
+        throw new BundleException("pojosr bundles can't be updated");
+    }
+
+    public void update() throws BundleException
+    {
+        throw new BundleException("pojosr bundles can't be updated");
+    }
+
+    public void uninstall() throws BundleException
+    {
+        throw new BundleException("pojosr bundles can't be uninstalled");
+    }
+
+    public Dictionary getHeaders()
+    {
+        return getHeaders(Locale.getDefault().toString());
+    }
+
+    public long getBundleId()
+    {
+        return m_id;
+    }
+
+    public String getLocation()
+    {
+        return m_location;
+    }
+
+    public ServiceReference[] getRegisteredServices()
+    {
+        return m_reg.getRegisteredServices(this);
+    }
+
+    public ServiceReference[] getServicesInUse()
+    {
+        return m_reg.getServicesInUse(this);
+    }
+
+    public boolean hasPermission(Object permission)
+    {
+        // TODO: security - fix this
+        return true;
+    }
+
+    public URL getResource(String name)
+    {
+        // TODO: module - implement this based on the revision
+        URL result = m_loader.getResource(name);
+        return result;
+    }
+
+    public Dictionary getHeaders(String locale)
+    {
+        return new MapToDictionary(getCurrentLocalizedHeader(locale));
+    }
+
+    Map getCurrentLocalizedHeader(String locale)
+    {
+        Map result = null;
+
+        // Spec says empty local returns raw headers.
+        if ((locale == null) || (locale.length() == 0))
+        {
+            result = new StringMap(m_manifest, false);
+        }
+
+        // If we have no result, try to get it from the cached headers.
+        if (result == null)
+        {
+            synchronized (m_cachedHeaders)
+            {
+                // If the bundle is uninstalled, then the cached headers should
+                // only contain the localized headers for the default locale at
+                // the time of uninstall, so just return that.
+                if (getState() == Bundle.UNINSTALLED)
+                {
+                    result = (Map) m_cachedHeaders.values().iterator().next();
+                }
+                // If the bundle has been updated, clear the cached headers.
+                else if (getLastModified() > m_cachedHeadersTimestamp)
+                {
+                    m_cachedHeaders.clear();
+                }
+                // Otherwise, returned the cached headers if they exist.
+                else
+                {
+                    // Check if headers for this locale have already been
+                    // resolved
+                    if (m_cachedHeaders.containsKey(locale))
+                    {
+                        result = (Map) m_cachedHeaders.get(locale);
+                    }
+                }
+            }
+        }
+
+        // If the requested locale is not cached, then try to create it.
+        if (result == null)
+        {
+            // Get a modifiable copy of the raw headers.
+            Map headers = new StringMap(m_manifest, false);
+            // Assume for now that this will be the result.
+            result = headers;
+
+            // Check to see if we actually need to localize anything
+            boolean localize = false;
+            for (Iterator it = headers.values().iterator(); !localize
+                    && it.hasNext();)
+            {
+                if (((String) it.next()).startsWith("%"))
+                {
+                    localize = true;
+                }
+            }
+
+            if (!localize)
+            {
+                // If localization is not needed, just cache the headers and
+                // return
+                // them as-is. Not sure if this is useful
+                updateHeaderCache(locale, headers);
+            }
+            else
+            {
+                // Do localization here and return the localized headers
+                String basename = (String) headers
+                        .get(Constants.BUNDLE_LOCALIZATION);
+                if (basename == null)
+                {
+                    basename = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
+                }
+
+                // Create ordered list of files to load properties from
+                List resourceList = createLocalizationResourceList(basename,
+                        locale);
+
+                // Create a merged props file with all available props for this
+                // locale
+                boolean found = false;
+                Properties mergedProperties = new Properties();
+                for (Iterator it = resourceList.iterator(); it.hasNext();)
+                {
+                    URL temp = m_revision.getEntry(it.next() + ".properties");
+                    if (temp != null)
+                    {
+                        found = true;
+                        try
+                        {
+                            mergedProperties.load(temp.openConnection()
+                                    .getInputStream());
+                        }
+                        catch (IOException ex)
+                        {
+                            // File doesn't exist, just continue loop
+                        }
+                    }
+                }
+
+                // If the specified locale was not found, then the spec says we
+                // should
+                // return the default localization.
+                if (!found && !locale.equals(Locale.getDefault().toString()))
+                {
+                    result = getCurrentLocalizedHeader(Locale.getDefault()
+                            .toString());
+                }
+                // Otherwise, perform the localization based on the discovered
+                // properties and cache the result.
+                else
+                {
+                    // Resolve all localized header entries
+                    for (Iterator it = headers.entrySet().iterator(); it
+                            .hasNext();)
+                    {
+                        Map.Entry entry = (Map.Entry) it.next();
+                        String value = (String) entry.getValue();
+                        if (value.startsWith("%"))
+                        {
+                            String newvalue;
+                            String key = value
+                                    .substring(value.indexOf("%") + 1);
+                            newvalue = mergedProperties.getProperty(key);
+                            if (newvalue == null)
+                            {
+                                newvalue = key;
+                            }
+                            entry.setValue(newvalue);
+                        }
+                    }
+
+                    updateHeaderCache(locale, headers);
+                }
+            }
+        }
+
+        return result;
+    }
+
+    private void updateHeaderCache(String locale, Map localizedHeaders)
+    {
+        synchronized (m_cachedHeaders)
+        {
+            m_cachedHeaders.put(locale, localizedHeaders);
+            m_cachedHeadersTimestamp = System.currentTimeMillis();
+        }
+    }
+
+    private final Map m_cachedHeaders = new HashMap();
+    private long m_cachedHeadersTimestamp;
+
+    private static List createLocalizationResourceList(String basename,
+            String locale)
+    {
+        List result = new ArrayList(4);
+
+        StringTokenizer tokens;
+        StringBuffer tempLocale = new StringBuffer(basename);
+
+        result.add(tempLocale.toString());
+
+        if (locale.length() > 0)
+        {
+            tokens = new StringTokenizer(locale, "_");
+            while (tokens.hasMoreTokens())
+            {
+                tempLocale.append("_").append(tokens.nextToken());
+                result.add(tempLocale.toString());
+            }
+        }
+        return result;
+    }
+
+    public String getSymbolicName()
+    {
+        return m_symbolicName;
+    }
+
+    public Class<?> loadClass(String name) throws ClassNotFoundException
+    {
+        return m_loader.loadClass(name);
+    }
+
+    public Enumeration<URL> getResources(String name) throws IOException
+    {
+        // TODO: module - implement this based on the revision
+        return m_loader.getResources(name);
+    }
+
+    public Enumeration<String> getEntryPaths(String path)
+    {
+        return new EntryFilterEnumeration<String>(m_revision, false, path, null, false,
+                false);
+    }
+
+    public URL getEntry(String path)
+    {
+        URL result = m_revision.getEntry(path);
+        return result;
+    }
+
+    public long getLastModified()
+    {
+        return m_revision.getLastModified();
+    }
+
+    public Enumeration<URL> findEntries(String path, String filePattern,
+            boolean recurse)
+    {
+        // TODO: module - implement this based on the revision
+        return new EntryFilterEnumeration<URL>(m_revision, false, path, filePattern,
+                recurse, true);
+    }
+
+    public BundleContext getBundleContext()
+    {
+        return m_context;
+    }
+
+    public Map getSignerCertificates(int signersType)
+    {
+        // TODO: security - fix this
+        return new HashMap();
+    }
+
+    public Version getVersion()
+    {
+        return m_version;
+    }
+
+	public boolean equals(Object o)
+	{
+	     if (o instanceof PojoSRBundle) {
+		     return ((PojoSRBundle) o).m_id == m_id;
+		 }
+		 return false;
+	}
+
+    public int compareTo(Bundle o)
+    {
+        long thisBundleId = this.getBundleId();
+        long thatBundleId = o.getBundleId();
+        return (thisBundleId < thatBundleId ? -1 : (thisBundleId == thatBundleId ? 0 : 1));
+    }
+
+    public <A> A adapt(Class<A> type)
+    {
+        if (type == BundleStartLevel.class)
+        {
+            return (A) new BundleStartLevel() {
+
+                public Bundle getBundle()
+                {
+                    return PojoSRBundle.this;
+                }
+
+                public int getStartLevel()
+                {
+                    // TODO Implement this?
+                    return 1;
+                }
+
+                public void setStartLevel(int startlevel)
+                {
+                    // TODO Implement this?
+                }
+
+                public boolean isPersistentlyStarted()
+                {
+                    return true;
+                }
+
+                public boolean isActivationPolicyUsed()
+                {
+                    return false;
+                }};
+        }
+        else if (type == BundleRevisions.class)
+        {
+            return (A) this;
+        }
+        else if (type == BundleWiring.class)
+        {
+            return (A) this.getWiring();
+        }
+        return null;
+    }
+
+    public File getDataFile(String filename)
+    {
+        return m_context.getDataFile(filename);
+    }
+
+    public String toString()
+    {
+        String sym = getSymbolicName();
+        if (sym != null)
+        {
+            return sym + " [" + getBundleId() +"]";
+        }
+        return "[" + getBundleId() +"]";
+    }
+
+    public Bundle getBundle()
+    {
+        return this;
+    }
+
+    public List<BundleRevision> getRevisions()
+    {
+        return Arrays.asList((BundleRevision) this);
+    }
+
+    public List<BundleCapability> getDeclaredCapabilities(String namespace)
+    {
+        return Collections.emptyList();
+    }
+
+    public List<BundleRequirement> getDeclaredRequirements(String namespace)
+    {
+        return Collections.emptyList();
+    }
+
+    public int getTypes()
+    {
+        if (getHeaders().get(Constants.FRAGMENT_HOST) != null) {
+            return BundleRevision.TYPE_FRAGMENT;
+        }
+        return 0;
+    }
+
+    public BundleWiring getWiring()
+    {
+        return new BundleWiring()
+        {
+
+            public Bundle getBundle()
+            {
+                return PojoSRBundle.this;
+            }
+
+            public Collection<String> listResources(String path, String filePattern, int options)
+            {
+                Collection<String> result = new ArrayList<String>();
+                for (URL u : findEntries(path, filePattern, options))
+                {
+                    result.add(u.toString());
+                }
+                // TODO: implement this
+                return result;
+            }
+
+            public boolean isInUse()
+            {
+                return true;
+            }
+
+            public boolean isCurrent()
+            {
+                return true;
+            }
+
+            public BundleRevision getRevision()
+            {
+                return PojoSRBundle.this;
+            }
+
+            public List<BundleRequirement> getRequirements(String namespace)
+            {
+                return getDeclaredRequirements(namespace);
+            }
+
+            public List<BundleWire> getRequiredWires(String namespace)
+            {
+                return Collections.emptyList();
+            }
+
+            public List<BundleWire> getProvidedWires(String namespace)
+            {
+                return Collections.emptyList();
+            }
+
+            public ClassLoader getClassLoader()
+            {
+                return getClass().getClassLoader();
+            }
+
+            public List<BundleCapability> getCapabilities(String namespace)
+            {
+                return Collections.emptyList();
+            }
+
+            public List<URL> findEntries(String path, String filePattern, int options)
+            {
+                List<URL> result = new ArrayList<URL>();
+                for (Enumeration<URL> e = PojoSRBundle.this.findEntries(path, filePattern, options == BundleWiring.FINDENTRIES_RECURSE); e.hasMoreElements();)
+                {
+                    result.add(e.nextElement());
+                }
+                return result;
+            }
+        };
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSRBundleContext.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSRBundleContext.java
new file mode 100644
index 0000000..9829aad
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoSRBundleContext.java
@@ -0,0 +1,297 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.osgi.framework.AllServiceListener;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+import de.kalpatec.pojosr.framework.felix.framework.ServiceRegistry;
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.SimpleFilter;
+import de.kalpatec.pojosr.framework.felix.framework.util.EventDispatcher;
+
+class PojoSRBundleContext implements BundleContext
+{
+    private final Bundle m_bundle;
+    private final ServiceRegistry m_reg;
+    private final EventDispatcher m_dispatcher;
+    private final Map<Long, Bundle> m_bundles;
+    private final Map m_config;
+
+    public PojoSRBundleContext(Bundle bundle, ServiceRegistry reg,
+            EventDispatcher dispatcher, Map<Long, Bundle> bundles, Map config)
+    {
+        m_bundle = bundle;
+        m_reg = reg;
+        m_dispatcher = dispatcher;
+        m_bundles = bundles;
+        m_config = config;
+    }
+
+    public boolean ungetService(ServiceReference reference)
+    {
+        return m_reg.ungetService(m_bundle, reference);
+    }
+
+    public void removeServiceListener(ServiceListener listener)
+    {
+        m_dispatcher.removeListener(this, ServiceListener.class,
+                    listener);
+    }
+
+    public void removeFrameworkListener(FrameworkListener listener)
+    {
+        m_dispatcher
+                .removeListener(this, FrameworkListener.class, listener);
+    }
+
+    public void removeBundleListener(BundleListener listener)
+    {
+        m_dispatcher.removeListener(this, BundleListener.class, listener);
+    }
+
+    public ServiceRegistration registerService(String clazz, Object service,
+            Dictionary properties)
+    {
+        return m_reg.registerService(m_bundle, new String[] { clazz }, service,
+                properties);
+    }
+
+    public ServiceRegistration registerService(String[] clazzes,
+            Object service, Dictionary properties)
+    {
+        return m_reg.registerService(m_bundle, clazzes, service, properties);
+    }
+
+    public Bundle installBundle(String location) throws BundleException
+    {
+        throw new BundleException("pojosr can't do that");
+    }
+
+    public Bundle installBundle(String location, InputStream input)
+            throws BundleException
+    {
+
+        throw new BundleException("pojosr can't do that");
+    }
+
+    public ServiceReference[] getServiceReferences(String clazz, String filter)
+            throws InvalidSyntaxException
+    {
+        return getAllServiceReferences(clazz, filter);
+    }
+
+    public ServiceReference getServiceReference(String clazz)
+    {
+        try
+        {
+            return getBestServiceReference(getAllServiceReferences(clazz, null));
+        }
+        catch (InvalidSyntaxException e)
+        {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private ServiceReference getBestServiceReference(ServiceReference[] refs)
+    {
+        if (refs == null)
+        {
+            return null;
+        }
+
+        if (refs.length == 1)
+        {
+            return refs[0];
+        }
+
+        // Loop through all service references and return
+        // the "best" one according to its rank and ID.
+        ServiceReference bestRef = refs[0];
+        for (int i = 1; i < refs.length; i++)
+        {
+            if (bestRef.compareTo(refs[i]) < 0)
+            {
+                bestRef = refs[i];
+            }
+        }
+
+        return bestRef;
+    }
+
+    public Object getService(ServiceReference reference)
+    {
+        return m_reg.getService(m_bundle, reference);
+    }
+
+    public String getProperty(String key)
+    {
+        Object result = m_config.get(key);
+
+        return result == null ? System.getProperty(key) : result.toString();
+    }
+
+    public File getDataFile(String filename)
+    {
+        File root = new File("bundle" + m_bundle.getBundleId());
+        if (System.getProperty("org.osgi.framework.storage") != null)
+        {
+            root = new File(new File(System.getProperty("org.osgi.framework.storage")), root.getName());
+        }
+        root.mkdirs();
+        return filename.trim().length() > 0 ? new File(root, filename) : root;
+    }
+
+    public Bundle[] getBundles()
+    {
+        Bundle[] result = m_bundles.values().toArray(
+                new Bundle[m_bundles.size()]);
+        Arrays.sort(result, new Comparator<Bundle>()
+        {
+
+            public int compare(Bundle o1, Bundle o2)
+            {
+                return (int) (o1.getBundleId() - o2.getBundleId());
+            }
+        });
+        return result;
+    }
+
+    public Bundle getBundle(long id)
+    {
+        return m_bundles.get(id);
+    }
+
+    public Bundle getBundle()
+    {
+        return m_bundle;
+    }
+
+    public ServiceReference[] getAllServiceReferences(String clazz,
+            String filter) throws InvalidSyntaxException
+    {
+        SimpleFilter simple = null;
+        if (filter != null)
+        {
+            try
+            {
+                simple = SimpleFilter.parse(filter);
+            }
+            catch (Exception ex)
+            {
+                throw new InvalidSyntaxException(ex.getMessage(), filter);
+            }
+        }
+        List<ServiceReference> result = m_reg.getServiceReferences(clazz,
+                simple);
+        return result.isEmpty() ? null : result
+                .toArray(new ServiceReference[result.size()]);
+    }
+
+    public Filter createFilter(String filter) throws InvalidSyntaxException
+    {
+        return FrameworkUtil.createFilter(filter);
+    }
+
+    public void addServiceListener(ServiceListener listener)
+    {
+        try
+        {
+            addServiceListener(listener, null);
+        }
+        catch (InvalidSyntaxException e)
+        {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+     public void addServiceListener(final ServiceListener listener, String filter)
+            throws InvalidSyntaxException
+    {
+		 m_dispatcher.addListener(this, ServiceListener.class, listener,
+                filter == null ? null : FrameworkUtil.createFilter(filter));
+    }
+
+    public void addFrameworkListener(FrameworkListener listener)
+    {
+        m_dispatcher.addListener(this, FrameworkListener.class, listener,
+                null);
+    }
+
+    public void addBundleListener(BundleListener listener)
+    {
+        m_dispatcher
+                .addListener(this, BundleListener.class, listener, null);
+    }
+
+    public <S> ServiceRegistration<S> registerService(Class<S> clazz, S service, Dictionary<String, ?> properties)
+    {
+        return (ServiceRegistration<S>) registerService(clazz.getName(), service, properties);
+    }
+
+    public <S> ServiceReference<S> getServiceReference(Class<S> clazz)
+    {
+        return (ServiceReference<S>) getServiceReference(clazz.getName());
+    }
+
+    public <S> Collection<ServiceReference<S>> getServiceReferences(Class<S> clazz, String filter)
+                    throws InvalidSyntaxException
+    {
+        ServiceReference<S>[] refs = (ServiceReference<S>[]) getServiceReferences(clazz.getName(), filter);
+        if (refs == null)
+        {
+            return Collections.emptyList();
+        }
+        return Arrays.asList(refs);
+    }
+
+    public Bundle getBundle(String location)
+    {
+        for (Bundle bundle : m_bundles.values())
+        {
+            if (location.equals(bundle.getLocation()))
+            {
+                return bundle;
+            }
+        }
+        return null;
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoServiceRegistryFactoryImpl.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoServiceRegistryFactoryImpl.java
new file mode 100644
index 0000000..099ba3f
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/PojoServiceRegistryFactoryImpl.java
@@ -0,0 +1,238 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.framework.Version;
+import org.osgi.framework.launch.Framework;
+import org.osgi.framework.launch.FrameworkFactory;
+
+import de.kalpatec.pojosr.framework.launch.ClasspathScanner;
+import de.kalpatec.pojosr.framework.launch.PojoServiceRegistry;
+import de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory;
+
+public class PojoServiceRegistryFactoryImpl implements
+		PojoServiceRegistryFactory, FrameworkFactory {
+
+	public PojoServiceRegistry newPojoServiceRegistry(Map configuration)
+			throws Exception {
+		return new PojoSR(configuration);
+	}
+
+	public Framework newFramework(Map configuration) {
+		return new FrameworkImpl((String) configuration.get("pojosr.filter"));
+	}
+
+	private static final class FrameworkImpl implements Framework {
+		private final String m_filter;
+		private volatile Bundle m_bundle = null;
+		private volatile PojoServiceRegistry m_reg = null;
+
+		public FrameworkImpl(String filter) {
+			m_filter = filter;
+		}
+
+		public void init() throws BundleException {
+			try {
+				m_reg = new PojoServiceRegistryFactoryImpl()
+				.newPojoServiceRegistry(new HashMap());
+				m_bundle = m_reg.getBundleContext()
+						.getBundle();
+			} catch (Exception ex) {
+				throw new BundleException("Unable to scan classpath", ex);
+			}
+		}
+
+		public int getState() {
+			return (m_bundle == null) ? Bundle.INSTALLED : m_bundle.getState();
+		}
+
+		public void start(int options) throws BundleException {
+			start();
+		}
+
+		public void start() throws BundleException {
+			try {
+				m_reg.startBundles((m_filter != null) ? new ClasspathScanner()
+					.scanForBundles(m_filter)
+					: new ClasspathScanner().scanForBundles());
+			} catch (Exception e) {
+				throw new BundleException("Error starting framework", e);
+			}
+		}
+
+		public void stop(int options) throws BundleException {
+			m_bundle.stop(options);
+		}
+
+		public void stop() throws BundleException {
+			m_bundle.stop();
+		}
+
+		public void update(InputStream input) throws BundleException {
+			m_bundle.update(input);
+		}
+
+		public void update() throws BundleException {
+			m_bundle.update();
+		}
+
+		public void uninstall() throws BundleException {
+			m_bundle.uninstall();
+		}
+
+		public Dictionary getHeaders() {
+			return m_bundle.getHeaders();
+		}
+
+		public long getBundleId() {
+			return m_bundle.getBundleId();
+		}
+
+		public String getLocation() {
+			return m_bundle.getLocation();
+		}
+
+		public ServiceReference[] getRegisteredServices() {
+			return m_bundle.getRegisteredServices();
+		}
+
+		public ServiceReference[] getServicesInUse() {
+			return m_bundle.getServicesInUse();
+		}
+
+		public boolean hasPermission(Object permission) {
+			return m_bundle.hasPermission(permission);
+		}
+
+		public URL getResource(String name) {
+			return m_bundle.getResource(name);
+		}
+
+		public Dictionary getHeaders(String locale) {
+			return m_bundle.getHeaders(locale);
+		}
+
+		public String getSymbolicName() {
+			return m_bundle.getSymbolicName();
+		}
+
+		public Class loadClass(String name) throws ClassNotFoundException {
+			return m_bundle.loadClass(name);
+		}
+
+		public Enumeration getResources(String name) throws IOException {
+			return m_bundle.getResources(name);
+		}
+
+		public Enumeration getEntryPaths(String path) {
+			return m_bundle.getEntryPaths(path);
+		}
+
+		public URL getEntry(String path) {
+			return m_bundle.getEntry(path);
+		}
+
+		public long getLastModified() {
+			return m_bundle.getLastModified();
+		}
+
+		public Enumeration findEntries(String path, String filePattern,
+				boolean recurse) {
+			return m_bundle.findEntries(path, filePattern, recurse);
+		}
+
+		public BundleContext getBundleContext() {
+			return m_bundle.getBundleContext();
+		}
+
+		public Map getSignerCertificates(int signersType) {
+			return m_bundle.getSignerCertificates(signersType);
+		}
+
+		public Version getVersion() {
+			return m_bundle.getVersion();
+		}
+
+		public FrameworkEvent waitForStop(long timeout)
+				throws InterruptedException {
+			final Object lock = new Object();
+			
+			m_bundle.getBundleContext().addBundleListener(new SynchronousBundleListener() {
+				
+				public void bundleChanged(BundleEvent event) {
+					if ((event.getBundle() == m_bundle) && (event.getType() == BundleEvent.STOPPED)) {
+						synchronized (lock) {
+							lock.notifyAll();
+						}
+					}
+				}
+			});
+			synchronized (lock) {
+				while (m_bundle.getState() != Bundle.RESOLVED) {
+					if (m_bundle.getState() == Bundle.STOPPING ) {
+						lock.wait(100);
+					}
+					else {
+						lock.wait();
+					}
+				}
+			}
+			return new FrameworkEvent(FrameworkEvent.STOPPED, m_bundle, null);
+		}
+		
+		public File getDataFile(String filename) {
+		    return m_bundle.getDataFile(filename);
+		}
+
+        public int compareTo(Bundle o)
+        {
+            if (o == this) 
+            {
+                return 0;
+            }
+            return m_bundle.compareTo(o);
+        }
+
+        public <A> A adapt(Class<A> type)
+        {
+            return m_bundle.adapt(type);
+        }
+
+	}
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/Revision.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/Revision.java
new file mode 100644
index 0000000..b884ad1
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/Revision.java
@@ -0,0 +1,33 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.osgi.framework.wiring.BundleRevision;
+
+abstract class Revision
+{
+    public abstract long getLastModified();
+
+    public abstract URL getEntry(String entryName);
+
+    public abstract Enumeration<String> getEntries();
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/URLRevision.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/URLRevision.java
new file mode 100644
index 0000000..dcd0031
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/URLRevision.java
@@ -0,0 +1,70 @@
+/*
+ * 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 de.kalpatec.pojosr.framework;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Properties;
+
+class URLRevision extends Revision
+{
+    private final URL m_url;
+    private final long m_lastModified;
+
+    public URLRevision(URL url, long lastModified)
+    {
+        m_url = url;
+        if (lastModified > 0)
+        {
+            m_lastModified = lastModified;
+        }
+        else
+        {
+            m_lastModified = System.currentTimeMillis();
+        }
+    }
+
+    @Override
+    public long getLastModified()
+    {
+        return m_lastModified;
+    }
+
+    public Enumeration getEntries()
+    {
+        return new Properties().elements();
+    }
+
+    @Override
+    public URL getEntry(String entryName)
+    {
+        // TODO Auto-generated method stub
+        try
+        {
+            return new URL(m_url, entryName);
+        }
+        catch (MalformedURLException e)
+        {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/ServiceRegistrationImpl.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/ServiceRegistrationImpl.java
new file mode 100644
index 0000000..e222873
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/ServiceRegistrationImpl.java
@@ -0,0 +1,553 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.Attribute;
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.Capability;
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.Directive;
+import de.kalpatec.pojosr.framework.felix.framework.util.MapToDictionary;
+import de.kalpatec.pojosr.framework.felix.framework.util.StringMap;
+import de.kalpatec.pojosr.framework.felix.framework.util.Util;
+import org.osgi.framework.wiring.BundleCapability;
+import org.osgi.framework.wiring.BundleRevision;
+
+class ServiceRegistrationImpl implements ServiceRegistration
+{
+    // Service registry.
+    private final ServiceRegistry m_registry;
+    // Bundle providing the service.
+    private final Bundle m_bundle;
+    // Interfaces associated with the service object.
+    private final String[] m_classes;
+    // Service Id associated with the service object.
+    private final Long m_serviceId;
+    // Service object.
+    private volatile Object m_svcObj;
+    // Service factory interface.
+    private volatile ServiceFactory m_factory;
+    // Associated property dictionary.
+    private volatile Map m_propMap = new StringMap(false);
+    // Re-usable service reference.
+    private final ServiceReferenceImpl m_ref;
+    // Flag indicating that we are unregistering.
+    private volatile boolean m_isUnregistering = false;
+
+    public ServiceRegistrationImpl(ServiceRegistry registry, Bundle bundle,
+            String[] classes, Long serviceId, Object svcObj, Dictionary dict)
+    {
+        m_registry = registry;
+        m_bundle = bundle;
+        m_classes = classes;
+        m_serviceId = serviceId;
+        m_svcObj = svcObj;
+        m_factory = (m_svcObj instanceof ServiceFactory) ? (ServiceFactory) m_svcObj
+                : null;
+
+        initializeProperties(dict);
+
+        // This reference is the "standard" reference for this
+        // service and will always be returned by getReference().
+        m_ref = new ServiceReferenceImpl();
+    }
+
+    protected synchronized boolean isValid()
+    {
+        return (m_svcObj != null);
+    }
+
+    protected synchronized void invalidate()
+    {
+        m_svcObj = null;
+    }
+
+    public synchronized ServiceReference getReference()
+    {
+        // Make sure registration is valid.
+        if (!isValid())
+        {
+            throw new IllegalStateException(
+                    "The service registration is no longer valid.");
+        }
+        return m_ref;
+    }
+
+    public void setProperties(Dictionary dict)
+    {
+        Map oldProps;
+        synchronized (this)
+        {
+            // Make sure registration is valid.
+            if (!isValid())
+            {
+                throw new IllegalStateException(
+                        "The service registration is no longer valid.");
+            }
+            // Remember old properties.
+            oldProps = m_propMap;
+            // Set the properties.
+            initializeProperties(dict);
+        }
+        // Tell registry about it.
+        m_registry.servicePropertiesModified(this,
+                new MapToDictionary(oldProps));
+    }
+
+    public void unregister()
+    {
+        synchronized (this)
+        {
+            if (!isValid() || m_isUnregistering)
+            {
+                throw new IllegalStateException("Service already unregistered.");
+            }
+            m_isUnregistering = true;
+        }
+        m_registry.unregisterService(m_bundle, this);
+        synchronized (this)
+        {
+            m_svcObj = null;
+            m_factory = null;
+        }
+    }
+
+    //
+    // Utility methods.
+    //
+    Object getProperty(String key)
+    {
+        return m_propMap.get(key);
+    }
+
+    private String[] getPropertyKeys()
+    {
+        Set s = m_propMap.keySet();
+        return (String[]) s.toArray(new String[s.size()]);
+    }
+
+    private Bundle[] getUsingBundles()
+    {
+        return m_registry.getUsingBundles(m_ref);
+    }
+
+    /**
+     * This method provides direct access to the associated service object; it
+     * generally should not be used by anyone other than the service registry
+     * itself.
+     *
+     * @return The service object associated with the registration.
+     **/
+    Object getService()
+    {
+        return m_svcObj;
+    }
+
+    Object getService(Bundle acqBundle)
+    {
+        // If the service object is a service factory, then
+        // let it create the service object.
+        if (m_factory != null)
+        {
+            Object svcObj = null;
+            svcObj = getFactoryUnchecked(acqBundle);
+
+            return svcObj;
+        }
+        else
+        {
+            return m_svcObj;
+        }
+    }
+
+    void ungetService(Bundle relBundle, Object svcObj)
+    {
+        // If the service object is a service factory, then
+        // let it release the service object.
+        if (m_factory != null)
+        {
+            try
+            {
+                ungetFactoryUnchecked(relBundle, svcObj);
+            }
+            catch (Exception ex)
+            {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+    private void initializeProperties(Dictionary dict)
+    {
+        // Create a case-insensitive map for the properties.
+        Map props = new StringMap(false);
+
+        if (dict != null)
+        {
+            // Make sure there are no duplicate keys.
+            Enumeration keys = dict.keys();
+            while (keys.hasMoreElements())
+            {
+                Object key = keys.nextElement();
+                if (props.get(key) == null)
+                {
+                    props.put(key, dict.get(key));
+                }
+                else
+                {
+                    throw new IllegalArgumentException(
+                            "Duplicate service property: " + key);
+                }
+            }
+        }
+
+        // Add the framework assigned properties.
+        props.put(Constants.OBJECTCLASS, m_classes);
+        props.put(Constants.SERVICE_ID, m_serviceId);
+
+        // Update the service property map.
+        m_propMap = props;
+    }
+
+    private Object getFactoryUnchecked(Bundle bundle)
+    {
+        Object svcObj = null;
+        try
+        {
+            svcObj = m_factory.getService(bundle, this);
+        }
+        catch (Throwable th)
+        {
+            throw new ServiceException("Service factory exception: "
+                    + th.getMessage(), ServiceException.FACTORY_EXCEPTION, th);
+        }
+        if (svcObj != null)
+        {
+            for (int i = 0; i < m_classes.length; i++)
+            {
+                try {
+                if (!Class.forName(m_classes[i]).isAssignableFrom(svcObj.getClass()))
+                {
+                    throw new ServiceException(
+                            "Service cannot be cast: " + m_classes[i],
+                            ServiceException.FACTORY_ERROR);
+                }
+				} catch (ClassNotFoundException ex) {
+				   throw new ServiceException("Service is missing class: " + m_classes[i], ServiceException.FACTORY_ERROR);
+				}
+
+            }
+        }
+        else
+        {
+            throw new ServiceException("Service factory returned null.",
+                    ServiceException.FACTORY_ERROR);
+        }
+        return svcObj;
+    }
+
+    private void ungetFactoryUnchecked(Bundle bundle, Object svcObj)
+    {
+        m_factory.ungetService(bundle, this, svcObj);
+    }
+
+
+
+    //
+    // ServiceReference implementation
+    //
+
+    class ServiceReferenceImpl implements ServiceReference, BundleCapability
+    {
+        private final ServiceReferenceMap m_map;
+
+        private ServiceReferenceImpl()
+        {
+            m_map = new ServiceReferenceMap();
+        }
+
+        ServiceRegistrationImpl getRegistration()
+        {
+            return ServiceRegistrationImpl.this;
+        }
+
+        //
+        // Capability methods.
+        //
+
+        @Override
+        public BundleRevision getRevision()
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        public String getNamespace()
+        {
+            return "service-reference";
+        }
+
+        @Override
+        public Map<String, String> getDirectives()
+        {
+            return Collections.EMPTY_MAP;
+        }
+
+        @Override
+        public Map<String,Object> getAttributes()
+        {
+            return m_map;
+        }
+
+        public List<String> getUses()
+        {
+            return Collections.EMPTY_LIST;
+        }
+
+        public Object getProperty(String s)
+        {
+            return ServiceRegistrationImpl.this.getProperty(s);
+        }
+
+        public String[] getPropertyKeys()
+        {
+            return ServiceRegistrationImpl.this.getPropertyKeys();
+        }
+
+        public Bundle getBundle()
+        {
+            // The spec says that this should return null if
+            // the service is unregistered.
+            return (isValid()) ? m_bundle : null;
+        }
+
+        public Bundle[] getUsingBundles()
+        {
+            return ServiceRegistrationImpl.this.getUsingBundles();
+        }
+
+        public String toString()
+        {
+            String[] ocs = (String[]) getProperty("objectClass");
+            String oc = "[";
+            for (int i = 0; i < ocs.length; i++)
+            {
+                oc = oc + ocs[i];
+                if (i < ocs.length - 1)
+                    oc = oc + ", ";
+            }
+            oc = oc + "]";
+            return oc;
+        }
+
+        public boolean isAssignableTo(Bundle requester, String className)
+        {
+            // Always return true if the requester is the same as the provider.
+            if (requester == m_bundle)
+            {
+                return true;
+            }
+
+            // Boolean flag.
+            boolean allow = true;
+            // Get the package.
+            String pkgName = Util.getClassPackage(className);
+            /*
+             * Module requesterModule = ((BundleImpl)
+             * requester).getCurrentModule(); // Get package wiring from service
+             * requester. Wire requesterWire = Util.getWire(requesterModule,
+             * pkgName); // Get package wiring from service provider. Module
+             * providerModule = ((BundleImpl) m_bundle).getCurrentModule(); Wire
+             * providerWire = Util.getWire(providerModule, pkgName);
+             *
+             * // There are four situations that may occur here: // 1. Neither
+             * the requester, nor provider have wires for the package. // 2. The
+             * requester does not have a wire for the package. // 3. The
+             * provider does not have a wire for the package. // 4. Both the
+             * requester and provider have a wire for the package. // For case
+             * 1, if the requester does not have access to the class at // all,
+             * we assume it is using reflection and do not filter. If the //
+             * requester does have access to the class, then we make sure it is
+             * // the same class as the service. For case 2, we do not filter if
+             * the // requester is the exporter of the package to which the
+             * provider of // the service is wired. Otherwise, as in case 1, if
+             * the requester // does not have access to the class at all, we do
+             * not filter, but if // it does have access we check if it is the
+             * same class accessible to // the providing module. For case 3, the
+             * provider will not have a wire // if it is exporting the package,
+             * so we determine if the requester // is wired to it or somehow
+             * using the same class. For case 4, we // simply compare the
+             * exporting modules from the package wiring to // determine if we
+             * need to filter the service reference.
+             *
+             * // Case 1: Both requester and provider have no wire. if
+             * ((requesterWire == null) && (providerWire == null)) { // If
+             * requester has no access then true, otherwise service //
+             * registration must have same class as requester. try { Class
+             * requestClass = requesterModule.getClassByDelegation(className);
+             * allow = getRegistration().isClassAccessible(requestClass); }
+             * catch (Exception ex) { // Requester has no access to the class,
+             * so allow it, since // we assume the requester is using
+             * reflection. allow = true; } } // Case 2: Requester has no wire,
+             * but provider does. else if ((requesterWire == null) &&
+             * (providerWire != null)) { // Allow if the requester is the
+             * exporter of the provider's wire. if
+             * (providerWire.getExporter().equals(requesterModule)) { allow =
+             * true; } // Otherwise, check if the requester has access to the
+             * class and, // if so, if it is the same class as the provider.
+             * else { try { // Try to load class from requester. Class
+             * requestClass = requesterModule.getClassByDelegation(className);
+             * try { // If requester has access to the class, verify it is the
+             * // same class as the provider. allow =
+             * (providerWire.getClass(className) == requestClass); } catch
+             * (Exception ex) { allow = false; } } catch (Exception ex) { //
+             * Requester has no access to the class, so allow it, since // we
+             * assume the requester is using reflection. allow = true; } } } //
+             * Case 3: Requester has a wire, but provider does not. else if
+             * ((requesterWire != null) && (providerWire == null)) { // If the
+             * provider is the exporter of the requester's package, then check
+             * // if the requester is wired to the latest version of the
+             * provider, if so // then allow else don't (the provider has been
+             * updated but not refreshed). if (((BundleImpl)
+             * m_bundle).hasModule(requesterWire.getExporter())) { allow =
+             * providerModule.equals(requesterWire.getExporter()); } // If the
+             * provider is not the exporter of the requester's package, // then
+             * try to use the service registration to see if the requester's //
+             * class is accessible. else { try { // Load the class from the
+             * requesting bundle. Class requestClass =
+             * requesterModule.getClassByDelegation(className); // Get the
+             * service registration and ask it to check // if the service object
+             * is assignable to the requesting // bundle's class. allow =
+             * getRegistration().isClassAccessible(requestClass); } catch
+             * (Exception ex) { // Filter to be safe. allow = false; } } } //
+             * Case 4: Both requester and provider have a wire. else { //
+             * Include service reference if the wires have the // same source
+             * module. allow =
+             * providerWire.getExporter().equals(requesterWire.getExporter()); }
+             */
+
+            return allow;
+        }
+
+        public int compareTo(Object reference)
+        {
+            ServiceReference other = (ServiceReference) reference;
+
+            Long id = (Long) getProperty(Constants.SERVICE_ID);
+            Long otherId = (Long) other.getProperty(Constants.SERVICE_ID);
+
+            if (id.equals(otherId))
+            {
+                return 0; // same service
+            }
+
+            Object rankObj = getProperty(Constants.SERVICE_RANKING);
+            Object otherRankObj = other.getProperty(Constants.SERVICE_RANKING);
+
+            // If no rank, then spec says it defaults to zero.
+            rankObj = (rankObj == null) ? new Integer(0) : rankObj;
+            otherRankObj = (otherRankObj == null) ? new Integer(0)
+                    : otherRankObj;
+
+            // If rank is not Integer, then spec says it defaults to zero.
+            Integer rank = (rankObj instanceof Integer) ? (Integer) rankObj
+                    : new Integer(0);
+            Integer otherRank = (otherRankObj instanceof Integer) ? (Integer) otherRankObj
+                    : new Integer(0);
+
+            // Sort by rank in ascending order.
+            if (rank.compareTo(otherRank) < 0)
+            {
+                return -1; // lower rank
+            }
+            else if (rank.compareTo(otherRank) > 0)
+            {
+                return 1; // higher rank
+            }
+
+            // If ranks are equal, then sort by service id in descending order.
+            return (id.compareTo(otherId) < 0) ? 1 : -1;
+        }
+    }
+
+     private class ServiceReferenceMap implements Map
+    {
+        public int size()
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public boolean isEmpty()
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public boolean containsKey(Object o)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public boolean containsValue(Object o)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public Object get(Object o)
+        {
+            return ServiceRegistrationImpl.this.getProperty((String) o);
+        }
+
+        public Object put(Object k, Object v)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public Object remove(Object o)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public void putAll(Map map)
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public void clear()
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public Set<Object> keySet()
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public Collection<Object> values()
+        {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public Set<Entry<Object, Object>> entrySet()
+        {
+            return Collections.EMPTY_SET;
+        }
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/ServiceRegistry.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/ServiceRegistry.java
new file mode 100644
index 0000000..f61a3c7
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/ServiceRegistry.java
@@ -0,0 +1,813 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+import org.osgi.framework.hooks.service.*;
+import org.osgi.framework.launch.Framework;
+
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.Capability;
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.CapabilitySet;
+import de.kalpatec.pojosr.framework.felix.framework.capabilityset.SimpleFilter;
+import org.osgi.framework.wiring.BundleCapability;
+
+public class ServiceRegistry
+{
+    private long m_currentServiceId = 1L;
+    // Maps bundle to an array of service registrations.
+    private final Map m_regsMap = Collections.synchronizedMap(new HashMap());
+    // Capability set for all service registrations.
+    private final CapabilitySet m_regCapSet;
+    // Maps registration to thread to keep track when a
+    // registration is in use, which will cause other
+    // threads to wait.
+    private final Map m_lockedRegsMap = new HashMap();
+    // Maps bundle to an array of usage counts.
+    private final Map m_inUseMap = new HashMap();
+    private final ServiceRegistryCallbacks m_callbacks;
+    private final WeakHashMap<ServiceReference, ServiceReference> m_blackList =
+        new WeakHashMap<ServiceReference, ServiceReference>();
+    private final static Class<?>[] m_hookClasses =
+    {
+        org.osgi.framework.hooks.bundle.FindHook.class,
+        org.osgi.framework.hooks.bundle.EventHook.class,
+        org.osgi.framework.hooks.service.EventHook.class,
+        org.osgi.framework.hooks.service.EventListenerHook.class,
+        org.osgi.framework.hooks.service.FindHook.class,
+        org.osgi.framework.hooks.service.ListenerHook.class,
+        org.osgi.framework.hooks.weaving.WeavingHook.class,
+        org.osgi.framework.hooks.resolver.ResolverHookFactory.class,
+        org.osgi.service.url.URLStreamHandlerService.class,
+        java.net.ContentHandler.class
+    };
+    private final Map<Class<?>, Set<ServiceReference<?>>> m_allHooks =
+        new HashMap<Class<?>, Set<ServiceReference<?>>>();
+
+    public ServiceRegistry(ServiceRegistryCallbacks callbacks)
+    {
+        m_callbacks = callbacks;
+
+        List indices = new ArrayList();
+        indices.add(Constants.OBJECTCLASS);
+        m_regCapSet = new CapabilitySet(indices, false);
+    }
+
+    public ServiceReference[] getRegisteredServices(Bundle bundle)
+    {
+        ServiceRegistration[] regs = (ServiceRegistration[]) m_regsMap.get(bundle);
+        if (regs != null)
+        {
+            List refs = new ArrayList(regs.length);
+            for (int i = 0; i < regs.length; i++)
+            {
+                try
+                {
+                    refs.add(regs[i].getReference());
+                }
+                catch (IllegalStateException ex)
+                {
+                    // Don't include the reference as it is not valid anymore
+                }
+            }
+            return (ServiceReference[]) refs.toArray(new ServiceReference[refs.size()]);
+        }
+        return null;
+    }
+
+    // Caller is expected to fire REGISTERED event.
+    public ServiceRegistration registerService(
+        Bundle bundle, String[] classNames, Object svcObj, Dictionary dict)
+    {
+        ServiceRegistrationImpl reg = null;
+
+        synchronized (this)
+        {
+            // Create the service registration.
+            reg = new ServiceRegistrationImpl(
+                this, bundle, classNames, new Long(m_currentServiceId++), svcObj, dict);
+
+            // Keep track of registered hooks.
+            addHooks(classNames, svcObj, reg.getReference());
+
+            // Get the bundles current registered services.
+            ServiceRegistration[] regs = (ServiceRegistration[]) m_regsMap.get(bundle);
+            m_regsMap.put(bundle, addServiceRegistration(regs, reg));
+            m_regCapSet.addCapability((BundleCapability) reg.getReference());
+        }
+
+        // Notify callback objects about registered service.
+        if (m_callbacks != null)
+        {
+            m_callbacks.serviceChanged(new ServiceEvent(
+                ServiceEvent.REGISTERED, reg.getReference()), null);
+        }
+        return reg;
+    }
+
+    public void unregisterService(Bundle bundle, ServiceRegistration reg)
+    {
+        // If this is a hook, it should be removed.
+        removeHook(reg.getReference());
+
+        synchronized (this)
+        {
+            // Note that we don't lock the service registration here using
+            // the m_lockedRegsMap because we want to allow bundles to get
+            // the service during the unregistration process. However, since
+            // we do remove the registration from the service registry, no
+            // new bundles will be able to look up the service.
+
+            // Now remove the registered service.
+            ServiceRegistration[] regs = (ServiceRegistration[]) m_regsMap.get(bundle);
+            m_regsMap.put(bundle, removeServiceRegistration(regs, reg));
+            m_regCapSet.removeCapability((BundleCapability) reg.getReference());
+        }
+
+        // Notify callback objects about unregistering service.
+        if (m_callbacks != null)
+        {
+            m_callbacks.serviceChanged(
+                new ServiceEvent(ServiceEvent.UNREGISTERING, reg.getReference()), null);
+        }
+
+        // Now forcibly unget the service object for all stubborn clients.
+        synchronized (this)
+        {
+            Bundle[] clients = getUsingBundles(reg.getReference());
+            for (int i = 0; (clients != null) && (i < clients.length); i++)
+            {
+                while (ungetService(clients[i], reg.getReference()))
+                    ; // Keep removing until it is no longer possible
+            }
+            ((ServiceRegistrationImpl) reg).invalidate();
+        }
+    }
+
+    /**
+     * This method retrieves all services registrations for the specified bundle
+     * and invokes <tt>ServiceRegistration.unregister()</tt> on each one. This
+     * method is only called be the framework to clean up after a stopped
+     * bundle.
+     *
+     * @param bundle the bundle whose services should be unregistered.
+     *
+     */
+    public void unregisterServices(Bundle bundle)
+    {
+        // Simply remove all service registrations for the bundle.
+        ServiceRegistration[] regs = null;
+        synchronized (this)
+        {
+            regs = (ServiceRegistration[]) m_regsMap.get(bundle);
+        }
+
+        // Note, there is no race condition here with respect to the
+        // bundle registering more services, because its bundle context
+        // has already been invalidated by this point, so it would not
+        // be able to register more services.
+
+        // Unregister each service.
+        for (int i = 0; (regs != null) && (i < regs.length); i++)
+        {
+            if (((ServiceRegistrationImpl) regs[i]).isValid())
+            {
+                regs[i].unregister();
+            }
+        }
+
+        // Now remove the bundle itself.
+        synchronized (this)
+        {
+            m_regsMap.remove(bundle);
+        }
+    }
+
+    public synchronized List getServiceReferences(String className, SimpleFilter filter)
+    {
+        if ((className == null) && (filter == null))
+        {
+            // Return all services.
+            filter = new SimpleFilter(Constants.OBJECTCLASS, "*", SimpleFilter.PRESENT);
+        }
+        else if ((className != null) && (filter == null))
+        {
+            // Return services matching the class name.
+            filter = new SimpleFilter(Constants.OBJECTCLASS, className, SimpleFilter.EQ);
+        }
+        else if ((className != null) && (filter != null))
+        {
+            // Return services matching the class name and filter.
+            List filters = new ArrayList(2);
+            filters.add(new SimpleFilter(Constants.OBJECTCLASS, className, SimpleFilter.EQ));
+            filters.add(filter);
+            filter = new SimpleFilter(null, filters, SimpleFilter.AND);
+        }
+        // else just use the specified filter.
+
+        Set<BundleCapability> matches = m_regCapSet.match(filter, false);
+
+        return new ArrayList(matches);
+    }
+
+    public synchronized ServiceReference[] getServicesInUse(Bundle bundle)
+    {
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+        if (usages != null)
+        {
+            ServiceReference[] refs = new ServiceReference[usages.length];
+            for (int i = 0; i < refs.length; i++)
+            {
+                refs[i] = usages[i].m_ref;
+            }
+            return refs;
+        }
+        return null;
+    }
+
+    public <S> S getService(Bundle bundle, ServiceReference<S> ref)
+    {
+        UsageCount usage = null;
+        Object svcObj = null;
+
+        // Get the service registration.
+        ServiceRegistrationImpl reg =
+            ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration();
+
+        synchronized (this)
+        {
+            // First make sure that no existing operation is currently
+            // being performed by another thread on the service registration.
+            for (Object o = m_lockedRegsMap.get(reg); (o != null); o = m_lockedRegsMap.get(reg))
+            {
+                // We don't allow cycles when we call out to the service factory.
+                if (o.equals(Thread.currentThread()))
+                {
+                    throw new ServiceException(
+                        "ServiceFactory.getService() resulted in a cycle.",
+                        ServiceException.FACTORY_ERROR,
+                        null);
+                }
+
+                // Otherwise, wait for it to be freed.
+                try
+                {
+                    wait();
+                }
+                catch (InterruptedException ex)
+                {
+                }
+            }
+
+            // Lock the service registration.
+            m_lockedRegsMap.put(reg, Thread.currentThread());
+
+            // Make sure the service registration is still valid.
+            if (reg.isValid())
+            {
+                // Get the usage count, if any.
+                usage = getUsageCount(bundle, ref);
+
+                // If we don't have a usage count, then create one and
+                // since the spec says we increment usage count before
+                // actually getting the service object.
+                if (usage == null)
+                {
+                    usage = addUsageCount(bundle, ref);
+                }
+
+                // Increment the usage count and grab the already retrieved
+                // service object, if one exists.
+                usage.m_count++;
+                svcObj = usage.m_svcObj;
+            }
+        }
+
+        // If we have a usage count, but no service object, then we haven't
+        // cached the service object yet, so we need to create one now without
+        // holding the lock, since we will potentially call out to a service
+        // factory.
+        try
+        {
+            if ((usage != null) && (svcObj == null))
+            {
+                svcObj = reg.getService(bundle);
+            }
+        }
+        finally
+        {
+            // If we successfully retrieved a service object, then we should
+            // cache it in the usage count. If not, we should flush the usage
+            // count. Either way, we need to unlock the service registration
+            // so that any threads waiting for it can continue.
+            synchronized (this)
+            {
+                // Before caching the service object, double check to see if
+                // the registration is still valid, since it may have been
+                // unregistered while we didn't hold the lock.
+                if (!reg.isValid() || (svcObj == null))
+                {
+                    flushUsageCount(bundle, ref);
+                }
+                else
+                {
+                    usage.m_svcObj = svcObj;
+                }
+                m_lockedRegsMap.remove(reg);
+                notifyAll();
+            }
+        }
+
+        return (S) svcObj;
+    }
+
+    public boolean ungetService(Bundle bundle, ServiceReference ref)
+    {
+        UsageCount usage = null;
+        ServiceRegistrationImpl reg =
+            ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration();
+
+        synchronized (this)
+        {
+            // First make sure that no existing operation is currently
+            // being performed by another thread on the service registration.
+            for (Object o = m_lockedRegsMap.get(reg); (o != null); o = m_lockedRegsMap.get(reg))
+            {
+                // We don't allow cycles when we call out to the service factory.
+                if (o.equals(Thread.currentThread()))
+                {
+                    throw new IllegalStateException(
+                        "ServiceFactory.ungetService() resulted in a cycle.");
+                }
+
+                // Otherwise, wait for it to be freed.
+                try
+                {
+                    wait();
+                }
+                catch (InterruptedException ex)
+                {
+                }
+            }
+
+            // Get the usage count.
+            usage = getUsageCount(bundle, ref);
+            // If there is no cached services, then just return immediately.
+            if (usage == null)
+            {
+                return false;
+            }
+
+            // Lock the service registration.
+            m_lockedRegsMap.put(reg, Thread.currentThread());
+        }
+
+        // If usage count will go to zero, then unget the service
+        // from the registration; we do this outside the lock
+        // since this might call out to the service factory.
+        try
+        {
+            if (usage.m_count == 1)
+            {
+                // Remove reference from usages array.
+                ((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
+                    .getRegistration().ungetService(bundle, usage.m_svcObj);
+            }
+        }
+        finally
+        {
+            // Finally, decrement usage count and flush if it goes to zero or
+            // the registration became invalid while we were not holding the
+            // lock. Either way, unlock the service registration so that any
+            // threads waiting for it can continue.
+            synchronized (this)
+            {
+                // Decrement usage count, which spec says should happen after
+                // ungetting the service object.
+                usage.m_count--;
+
+                // If the registration is invalid or the usage count has reached
+                // zero, then flush it.
+                if (!reg.isValid() || (usage.m_count <= 0))
+                {
+                    usage.m_svcObj = null;
+                    flushUsageCount(bundle, ref);
+                }
+
+                // Release the registration lock so any waiting threads can
+                // continue.
+                m_lockedRegsMap.remove(reg);
+                notifyAll();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * This is a utility method to release all services being used by the
+     * specified bundle.
+     *
+     * @param bundle the bundle whose services are to be released.
+     *
+     */
+    public void ungetServices(Bundle bundle)
+    {
+        UsageCount[] usages;
+        synchronized (this)
+        {
+            usages = (UsageCount[]) m_inUseMap.get(bundle);
+        }
+
+        if (usages == null)
+        {
+            return;
+        }
+
+        // Note, there is no race condition here with respect to the
+        // bundle using more services, because its bundle context
+        // has already been invalidated by this point, so it would not
+        // be able to look up more services.
+
+        // Remove each service object from the
+        // service cache.
+        for (int i = 0; i < usages.length; i++)
+        {
+            // Keep ungetting until all usage count is zero.
+            while (ungetService(bundle, usages[i].m_ref))
+            {
+                // Empty loop body.
+            }
+        }
+    }
+
+    public synchronized Bundle[] getUsingBundles(ServiceReference ref)
+    {
+        Bundle[] bundles = null;
+        for (Iterator iter = m_inUseMap.entrySet().iterator(); iter.hasNext();)
+        {
+            Map.Entry entry = (Map.Entry) iter.next();
+            Bundle bundle = (Bundle) entry.getKey();
+            UsageCount[] usages = (UsageCount[]) entry.getValue();
+            for (int useIdx = 0; useIdx < usages.length; useIdx++)
+            {
+                if (usages[useIdx].m_ref.equals(ref))
+                {
+                    // Add the bundle to the array to be returned.
+                    if (bundles == null)
+                    {
+                        bundles = new Bundle[]
+                        {
+                            bundle
+                        };
+                    }
+                    else
+                    {
+                        Bundle[] nbs = new Bundle[bundles.length + 1];
+                        System.arraycopy(bundles, 0, nbs, 0, bundles.length);
+                        nbs[bundles.length] = bundle;
+                        bundles = nbs;
+                    }
+                }
+            }
+        }
+        return bundles;
+    }
+
+    void servicePropertiesModified(ServiceRegistration reg, Dictionary oldProps)
+    {
+        updateHook(reg.getReference());
+        if (m_callbacks != null)
+        {
+            m_callbacks.serviceChanged(
+                new ServiceEvent(ServiceEvent.MODIFIED, reg.getReference()), oldProps);
+        }
+    }
+
+    private static ServiceRegistration[] addServiceRegistration(
+        ServiceRegistration[] regs, ServiceRegistration reg)
+    {
+        if (regs == null)
+        {
+            regs = new ServiceRegistration[]
+            {
+                reg
+            };
+        }
+        else
+        {
+            ServiceRegistration[] newRegs = new ServiceRegistration[regs.length + 1];
+            System.arraycopy(regs, 0, newRegs, 0, regs.length);
+            newRegs[regs.length] = reg;
+            regs = newRegs;
+        }
+        return regs;
+    }
+
+    private static ServiceRegistration[] removeServiceRegistration(
+        ServiceRegistration[] regs, ServiceRegistration reg)
+    {
+        for (int i = 0; (regs != null) && (i < regs.length); i++)
+        {
+            if (regs[i].equals(reg))
+            {
+                // If this is the only usage, then point to empty list.
+                if ((regs.length - 1) == 0)
+                {
+                    regs = new ServiceRegistration[0];
+                }
+                // Otherwise, we need to do some array copying.
+                else
+                {
+                    ServiceRegistration[] newRegs = new ServiceRegistration[regs.length - 1];
+                    System.arraycopy(regs, 0, newRegs, 0, i);
+                    if (i < newRegs.length)
+                    {
+                        System.arraycopy(
+                            regs, i + 1, newRegs, i, newRegs.length - i);
+                    }
+                    regs = newRegs;
+                }
+            }
+        }
+        return regs;
+    }
+
+    /**
+     * Utility method to retrieve the specified bundle's usage count for the
+     * specified service reference.
+     *
+     * @param bundle The bundle whose usage counts are being searched.
+     * @param ref The service reference to find in the bundle's usage counts.
+     * @return The associated usage count or null if not found.
+     *
+     */
+    private UsageCount getUsageCount(Bundle bundle, ServiceReference ref)
+    {
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+        for (int i = 0; (usages != null) && (i < usages.length); i++)
+        {
+            if (usages[i].m_ref.equals(ref))
+            {
+                return usages[i];
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Utility method to update the specified bundle's usage count array to
+     * include the specified service. This method should only be called to add a
+     * usage count for a previously unreferenced service. If the service already
+     * has a usage count, then the existing usage count counter simply needs to
+     * be incremented.
+     *
+     * @param bundle The bundle acquiring the service.
+     * @param ref The service reference of the acquired service.
+     * @param svcObj The service object of the acquired service.
+     *
+     */
+    private UsageCount addUsageCount(Bundle bundle, ServiceReference ref)
+    {
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+
+        UsageCount usage = new UsageCount();
+        usage.m_ref = ref;
+
+        if (usages == null)
+        {
+            usages = new UsageCount[]
+            {
+                usage
+            };
+        }
+        else
+        {
+            UsageCount[] newUsages = new UsageCount[usages.length + 1];
+            System.arraycopy(usages, 0, newUsages, 0, usages.length);
+            newUsages[usages.length] = usage;
+            usages = newUsages;
+        }
+
+        m_inUseMap.put(bundle, usages);
+
+        return usage;
+    }
+
+    /**
+     * Utility method to flush the specified bundle's usage count for the
+     * specified service reference. This should be called to completely remove
+     * the associated usage count object for the specified service reference. If
+     * the goal is to simply decrement the usage, then get the usage count and
+     * decrement its counter. This method will also remove the specified bundle
+     * from the "in use" map if it has no more usage counts after removing the
+     * usage count for the specified service reference.
+     *
+     * @param bundle The bundle whose usage count should be removed.
+     * @param ref The service reference whose usage count should be removed.
+     *
+     */
+    private void flushUsageCount(Bundle bundle, ServiceReference ref)
+    {
+        UsageCount[] usages = (UsageCount[]) m_inUseMap.get(bundle);
+        for (int i = 0; (usages != null) && (i < usages.length); i++)
+        {
+            if (usages[i].m_ref.equals(ref))
+            {
+                // If this is the only usage, then point to empty list.
+                if ((usages.length - 1) == 0)
+                {
+                    usages = null;
+                }
+                // Otherwise, we need to do some array copying.
+                else
+                {
+                    UsageCount[] newUsages = new UsageCount[usages.length - 1];
+                    System.arraycopy(usages, 0, newUsages, 0, i);
+                    if (i < newUsages.length)
+                    {
+                        System.arraycopy(
+                            usages, i + 1, newUsages, i, newUsages.length - i);
+                    }
+                    usages = newUsages;
+                }
+            }
+        }
+
+        if (usages != null)
+        {
+            m_inUseMap.put(bundle, usages);
+        }
+        else
+        {
+            m_inUseMap.remove(bundle);
+        }
+    }
+
+    //
+    // Hook-related methods.
+    //
+    boolean isHookBlackListed(ServiceReference sr)
+    {
+        return m_blackList.containsKey(sr);
+    }
+
+    void blackListHook(ServiceReference sr)
+    {
+        m_blackList.put(sr, sr);
+    }
+
+    static boolean isHook(String[] classNames, Class<?> hookClass, Object svcObj)
+    {
+        // For a service factory, we can only match names.
+        if (svcObj instanceof ServiceFactory)
+        {
+            for (String className : classNames)
+            {
+                if (className.equals(hookClass.getName()))
+                {
+                    return true;
+                }
+            }
+        }
+
+        // For a service object, check if its class matches.
+        if (hookClass.isAssignableFrom(svcObj.getClass()))
+        {
+            // But still only if it is registered under that interface.
+            String hookName = hookClass.getName();
+            for (String className : classNames)
+            {
+                if (className.equals(hookName))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private void addHooks(String[] classNames, Object svcObj, ServiceReference<?> ref)
+    {
+        for (Class<?> hookClass : m_hookClasses)
+        {
+            if (isHook(classNames, hookClass, svcObj))
+            {
+                synchronized (m_allHooks)
+                {
+                    Set<ServiceReference<?>> hooks = m_allHooks.get(hookClass);
+                    if (hooks == null)
+                    {
+                        hooks = new TreeSet<ServiceReference<?>>(Collections.reverseOrder());
+                        m_allHooks.put(hookClass, hooks);
+                    }
+                    hooks.add(ref);
+                }
+            }
+        }
+    }
+
+    private void updateHook(ServiceReference ref)
+    {
+        // We maintain the hooks sorted, so if ranking has changed for example,
+        // we need to ensure the order remains correct by resorting the hooks.
+        Object svcObj = ((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
+            .getRegistration().getService();
+        String[] classNames = (String[]) ref.getProperty(Constants.OBJECTCLASS);
+
+        for (Class<?> hookClass : m_hookClasses)
+        {
+            if (isHook(classNames, hookClass, svcObj))
+            {
+                synchronized (m_allHooks)
+                {
+                    Set<ServiceReference<?>> hooks = m_allHooks.get(hookClass);
+                    if (hooks != null)
+                    {
+                        List<ServiceReference<?>> refs = new ArrayList<ServiceReference<?>>(hooks);
+                        hooks.clear();
+                        hooks.addAll(refs);
+                    }
+                }
+            }
+        }
+    }
+
+    private void removeHook(ServiceReference ref)
+    {
+        Object svcObj = ((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
+            .getRegistration().getService();
+        String[] classNames = (String[]) ref.getProperty(Constants.OBJECTCLASS);
+
+        for (Class<?> hookClass : m_hookClasses)
+        {
+            if (isHook(classNames, hookClass, svcObj))
+            {
+                synchronized (m_allHooks)
+                {
+                    Set<ServiceReference<?>> hooks = m_allHooks.get(hookClass);
+                    if (hooks != null)
+                    {
+                        hooks.remove(ref);
+                        if (hooks.isEmpty())
+                        {
+                            m_allHooks.remove(hookClass);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    public <S> Set<ServiceReference<S>> getHooks(Class<S> hookClass)
+    {
+        synchronized (m_allHooks)
+        {
+            Set<ServiceReference<?>> hooks = m_allHooks.get(hookClass);
+            if (hooks != null)
+            {
+                SortedSet sorted = new TreeSet<ServiceReference<?>>(Collections.reverseOrder());
+                sorted.addAll(hooks);
+                return asTypedSortedSet(sorted);
+            }
+            return Collections.EMPTY_SET;
+        }
+    }
+
+    private static <S> SortedSet<ServiceReference<S>> asTypedSortedSet(
+        SortedSet<ServiceReference<?>> ss)
+    {
+        return (SortedSet<ServiceReference<S>>) (SortedSet) ss;
+
+
+    }
+
+    private static class UsageCount
+    {
+        public int m_count = 0;
+        public ServiceReference m_ref = null;
+        public Object m_svcObj = null;
+    }
+
+    public interface ServiceRegistryCallbacks
+    {
+        void serviceChanged(ServiceEvent event, Dictionary oldProps);
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Attribute.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Attribute.java
new file mode 100644
index 0000000..26539ab
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Attribute.java
@@ -0,0 +1,53 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+public class Attribute
+{
+    private final String m_name;
+    private final Object m_value;
+    private final boolean m_isMandatory;
+
+    public Attribute(String name, Object value, boolean isMandatory)
+    {
+        m_name = name;
+        m_value = value;
+        m_isMandatory = isMandatory;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public Object getValue()
+    {
+        return m_value;
+    }
+
+    public boolean isMandatory()
+    {
+        return m_isMandatory;
+    }
+
+    public String toString()
+    {
+        return m_name + "=" + m_value;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Capability.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Capability.java
new file mode 100644
index 0000000..9609e94
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Capability.java
@@ -0,0 +1,44 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.util.List;
+
+public interface Capability
+{
+    static final String MODULE_NAMESPACE = "module";
+    static final String HOST_NAMESPACE = "host";
+    static final String PACKAGE_NAMESPACE = "package";
+    static final String SINGLETON_NAMESPACE = "singleton";
+
+    public static final String PACKAGE_ATTR = "package";
+    public static final String VERSION_ATTR = "version";
+
+    String getNamespace();
+
+    Directive getDirective(String name);
+
+    List<Directive> getDirectives();
+
+    Attribute getAttribute(String name);
+
+    List<Attribute> getAttributes();
+
+    List<String> getUses();
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java
new file mode 100644
index 0000000..1c4f7ee
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/CapabilitySet.java
@@ -0,0 +1,578 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+import de.kalpatec.pojosr.framework.felix.framework.util.StringComparator;
+import org.osgi.framework.wiring.BundleCapability;
+
+public class CapabilitySet
+{
+    private final Map<String, Map<Object, Set<BundleCapability>>> m_indices;
+    private final Set<BundleCapability> m_capSet = new HashSet<BundleCapability>();
+
+    public CapabilitySet(List<String> indexProps, boolean caseSensitive)
+    {
+        m_indices = (caseSensitive)
+            ? new TreeMap<String, Map<Object, Set<BundleCapability>>>()
+            : new TreeMap<String, Map<Object, Set<BundleCapability>>>(
+                        new StringComparator(false));
+        for (int i = 0; (indexProps != null) && (i < indexProps.size()); i++)
+        {
+            m_indices.put(
+                indexProps.get(i), new HashMap<Object, Set<BundleCapability>>());
+        }
+    }
+
+    public void addCapability(BundleCapability cap)
+    {
+        m_capSet.add(cap);
+
+        // Index capability.
+        for (Entry<String, Map<Object, Set<BundleCapability>>> entry : m_indices.entrySet())
+        {
+            Object value = cap.getAttributes().get(entry.getKey());
+            if (value != null)
+            {
+                if (value.getClass().isArray())
+                {
+                    value = convertArrayToList(value);
+                }
+
+                Map<Object, Set<BundleCapability>> index = entry.getValue();
+
+                if (value instanceof Collection)
+                {
+                    Collection c = (Collection) value;
+                    for (Object o : c)
+                    {
+                        indexCapability(index, cap, o);
+                    }
+                }
+                else
+                {
+                    indexCapability(index, cap, value);
+                }
+            }
+        }
+    }
+
+    private void indexCapability(
+        Map<Object, Set<BundleCapability>> index, BundleCapability cap, Object capValue)
+    {
+        Set<BundleCapability> caps = index.get(capValue);
+        if (caps == null)
+        {
+            caps = new HashSet<BundleCapability>();
+            index.put(capValue, caps);
+        }
+        caps.add(cap);
+    }
+
+    public void removeCapability(BundleCapability cap)
+    {
+        if (m_capSet.remove(cap))
+        {
+            for (Entry<String, Map<Object, Set<BundleCapability>>> entry : m_indices.entrySet())
+            {
+                Object value = cap.getAttributes().get(entry.getKey());
+                if (value != null)
+                {
+                    if (value.getClass().isArray())
+                    {
+                        value = convertArrayToList(value);
+                    }
+
+                    Map<Object, Set<BundleCapability>> index = entry.getValue();
+
+                    if (value instanceof Collection)
+                    {
+                        Collection c = (Collection) value;
+                        for (Object o : c)
+                        {
+                            deindexCapability(index, cap, o);
+                        }
+                    }
+                    else
+                    {
+                        deindexCapability(index, cap, value);
+                    }
+                }
+            }
+        }
+    }
+
+    private void deindexCapability(
+        Map<Object, Set<BundleCapability>> index, BundleCapability cap, Object value)
+    {
+        Set<BundleCapability> caps = index.get(value);
+        if (caps != null)
+        {
+            caps.remove(cap);
+            if (caps.isEmpty())
+            {
+                index.remove(value);
+            }
+        }
+    }
+
+   public Set<BundleCapability> match(SimpleFilter sf, boolean obeyMandatory)
+    {
+        Set<BundleCapability> matches = match(m_capSet, sf);
+        return matches;
+       /* return (obeyMandatory)
+            ? matchMandatory(matches, sf)
+            : matches;*/
+    }
+
+    private Set<BundleCapability> match(Set<BundleCapability> caps, SimpleFilter sf)
+    {
+        Set<BundleCapability> matches = new HashSet<BundleCapability>();
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+        {
+            matches.addAll(caps);
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; (caps.size() > 0) && (i < sfs.size()); i++)
+            {
+                matches = match(caps, sfs.get(i));
+                caps = matches;
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.OR)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matches.addAll(match(caps, sfs.get(i)));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.NOT)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matches.addAll(caps);
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matches.removeAll(match(caps, sfs.get(i)));
+            }
+        }
+        else
+        {
+            Map<Object, Set<BundleCapability>> index = m_indices.get(sf.getName());
+            if ((sf.getOperation() == SimpleFilter.EQ) && (index != null))
+            {
+                Set<BundleCapability> existingCaps = index.get(sf.getValue());
+                if (existingCaps != null)
+                {
+                    matches.addAll(existingCaps);
+                    matches.retainAll(caps);
+                }
+            }
+            else
+            {
+                for (Iterator<BundleCapability> it = caps.iterator(); it.hasNext(); )
+                {
+                    BundleCapability cap = it.next();
+                    Object lhs = cap.getAttributes().get(sf.getName());
+                    if (lhs != null)
+                    {
+                        if (compare(lhs, sf.getValue(), sf.getOperation()))
+                        {
+                            matches.add(cap);
+                        }
+                    }
+                }
+            }
+        }
+
+        return matches;
+    }
+
+  /*  public static boolean matches(BundleCapability cap, SimpleFilter sf)
+    {
+        return matchesInternal(cap, sf) && matchMandatory(cap, sf);
+    }
+*/
+    private static boolean matchesInternal(BundleCapability cap, SimpleFilter sf)
+    {
+        boolean matched = true;
+
+        if (sf.getOperation() == SimpleFilter.MATCH_ALL)
+        {
+            matched = true;
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For AND we calculate the intersection of each subfilter.
+            // We can short-circuit the AND operation if there are no
+            // remaining capabilities.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; matched && (i < sfs.size()); i++)
+            {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.OR)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            matched = false;
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; !matched && (i < sfs.size()); i++)
+            {
+                matched = matchesInternal(cap, sfs.get(i));
+            }
+        }
+        else if (sf.getOperation() == SimpleFilter.NOT)
+        {
+            // Evaluate each subfilter against the remaining capabilities.
+            // For OR we calculate the union of each subfilter.
+            List<SimpleFilter> sfs = (List<SimpleFilter>) sf.getValue();
+            for (int i = 0; i < sfs.size(); i++)
+            {
+                matched = !(matchesInternal(cap, sfs.get(i)));
+            }
+        }
+        else
+        {
+            matched = false;
+            Object lhs = cap.getAttributes().get(sf.getName());
+            if (lhs != null)
+            {
+                matched = compare(lhs, sf.getValue(), sf.getOperation());
+            }
+        }
+
+        return matched;
+    }
+
+   /* private static Set<BundleCapability> matchMandatory(
+        Set<BundleCapability> caps, SimpleFilter sf)
+    {
+        for (Iterator<BundleCapability> it = caps.iterator(); it.hasNext(); )
+        {
+            BundleCapability cap = it.next();
+            if (!matchMandatory(cap, sf))
+            {
+                it.remove();
+            }
+        }
+        return caps;
+    }
+
+    private static boolean matchMandatory(BundleCapability cap, SimpleFilter sf)
+    {
+        Map<String, Object> attrs = cap.getAttributes();
+        for (Entry<String, Object> entry : attrs.entrySet())
+        {
+            if (((BundleCapability) cap).isAttributeMandatory(entry.getKey())
+                && !matchMandatoryAttrbute(entry.getKey(), sf))
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean matchMandatoryAttrbute(String attrName, SimpleFilter sf)
+    {
+        if ((sf.getName() != null) && sf.getName().equals(attrName))
+        {
+            return true;
+        }
+        else if (sf.getOperation() == SimpleFilter.AND)
+        {
+            List list = (List) sf.getValue();
+            for (int i = 0; i < list.size(); i++)
+            {
+                SimpleFilter sf2 = (SimpleFilter) list.get(i);
+                if ((sf2.getName() != null)
+                    && sf2.getName().equals(attrName))
+                {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }*/
+
+    private static final Class<?>[] STRING_CLASS = new Class[] { String.class };
+
+    private static boolean compare(Object lhs, Object rhsUnknown, int op)
+    {
+        if (lhs == null)
+        {
+            return false;
+        }
+
+        // If this is a PRESENT operation, then just return true immediately
+        // since we wouldn't be here if the attribute wasn't present.
+        if (op == SimpleFilter.PRESENT)
+        {
+            return true;
+        }
+
+        // If the type is comparable, then we can just return the
+        // result immediately.
+        if (lhs instanceof Comparable)
+        {
+            // Spec says SUBSTRING is false for all types other than string.
+            if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+            {
+                return false;
+            }
+
+            Object rhs;
+            if (op == SimpleFilter.SUBSTRING)
+            {
+                rhs = rhsUnknown;
+            }
+            else
+            {
+                try
+                {
+                    rhs = coerceType(lhs, (String) rhsUnknown);
+                }
+                catch (Exception ex)
+                {
+                    return false;
+                }
+            }
+
+            switch (op)
+            {
+            case SimpleFilter.EQ:
+                    try
+                    {
+                return (((Comparable) lhs).compareTo(rhs) == 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+            case SimpleFilter.GTE:
+                    try
+                    {
+                return (((Comparable) lhs).compareTo(rhs) >= 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+            case SimpleFilter.LTE:
+                    try
+                    {
+                return (((Comparable) lhs).compareTo(rhs) <= 0);
+                    }
+                    catch (Exception ex)
+                    {
+                        return false;
+                    }
+            case SimpleFilter.APPROX:
+                return compareApproximate(((Comparable) lhs), rhs);
+            case SimpleFilter.SUBSTRING:
+                    return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
+            default:
+                    throw new RuntimeException(
+                        "Unknown comparison operator: " + op);
+            }
+        }
+        // Booleans do not implement comparable, so special case them.
+        else if (lhs instanceof Boolean)
+        {
+            Object rhs;
+            try
+            {
+                rhs = coerceType(lhs, (String) rhsUnknown);
+            }
+            catch (Exception ex)
+            {
+                return false;
+            }
+
+            switch (op)
+            {
+            case SimpleFilter.EQ:
+            case SimpleFilter.GTE:
+            case SimpleFilter.LTE:
+            case SimpleFilter.APPROX:
+                return (lhs.equals(rhs));
+            default:
+                    throw new RuntimeException(
+                        "Unknown comparison operator: " + op);
+            }
+        }
+
+        // If the LHS is not a comparable or boolean, check if it is an
+        // array. If so, convert it to a list so we can treat it as a
+        // collection.
+        if (lhs.getClass().isArray())
+        {
+            lhs = convertArrayToList(lhs);
+        }
+
+        // If LHS is a collection, then call compare() on each element
+        // of the collection until a match is found.
+        if (lhs instanceof Collection)
+        {
+            for (Iterator iter = ((Collection) lhs).iterator(); iter.hasNext();)
+            {
+                if (compare(iter.next(), rhsUnknown, op))
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        // Spec says SUBSTRING is false for all types other than string.
+        if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
+        {
+            return false;
+        }
+
+        // Since we cannot identify the LHS type, then we can only perform
+        // equality comparison.
+        try
+        {
+            return lhs.equals(coerceType(lhs, (String) rhsUnknown));
+        }
+        catch (Exception ex)
+        {
+            return false;
+        }
+    }
+
+    private static boolean compareApproximate(Object lhs, Object rhs)
+    {
+        if (rhs instanceof String)
+        {
+            return removeWhitespace((String) lhs)
+                .equalsIgnoreCase(removeWhitespace((String) rhs));
+        }
+        else if (rhs instanceof Character)
+        {
+            return Character.toLowerCase(((Character) lhs))
+                == Character.toLowerCase(((Character) rhs));
+        }
+        return lhs.equals(rhs);
+    }
+
+    private static String removeWhitespace(String s)
+    {
+        StringBuffer sb = new StringBuffer(s.length());
+        for (int i = 0; i < s.length(); i++)
+        {
+            if (!Character.isWhitespace(s.charAt(i)))
+            {
+                sb.append(s.charAt(i));
+            }
+        }
+        return sb.toString();
+    }
+
+    private static Object coerceType(Object lhs, String rhsString) throws Exception
+    {
+        // If the LHS expects a string, then we can just return
+        // the RHS since it is a string.
+        if (lhs.getClass() == rhsString.getClass())
+        {
+            return rhsString;
+        }
+
+        // Try to convert the RHS type to the LHS type by using
+        // the string constructor of the LHS class, if it has one.
+        Object rhs = null;
+        try
+        {
+            // The Character class is a special case, since its constructor
+            // does not take a string, so handle it separately.
+            if (lhs instanceof Character)
+            {
+                rhs = new Character(rhsString.charAt(0));
+            }
+            else
+            {
+                // Spec says we should trim number types.
+                if ((lhs instanceof Number) || (lhs instanceof Boolean))
+                {
+                    rhsString = rhsString.trim();
+                }
+                Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
+                ctor.setAccessible(true);
+                rhs = ctor.newInstance(new Object[] { rhsString });
+            }
+        }
+        catch (Exception ex)
+        {
+            throw new Exception(
+                "Could not instantiate class "
+                    + lhs.getClass().getName()
+                    + " from string constructor with argument '"
+                    + rhsString + "' because " + ex);
+        }
+
+        return rhs;
+    }
+
+    /**
+     * This is an ugly utility method to convert an array of primitives to an
+     * array of primitive wrapper objects. This method simplifies processing
+     * LDAP filters since the special case of primitive arrays can be ignored.
+     *
+     * @param array
+     *            An array of primitive types.
+     * @return An corresponding array using pritive wrapper objects.
+     **/
+    private static List convertArrayToList(Object array)
+    {
+        int len = Array.getLength(array);
+        List list = new ArrayList(len);
+        for (int i = 0; i < len; i++)
+        {
+            list.add(Array.get(array, i));
+        }
+        return list;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java
new file mode 100644
index 0000000..fa310e3
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Directive.java
@@ -0,0 +1,46 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+public class Directive
+{
+    private final String m_name;
+    private final Object m_value;
+
+    public Directive(String name, Object value)
+    {
+        m_name = name;
+        m_value = value;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public Object getValue()
+    {
+        return m_value;
+    }
+
+    public String toString()
+    {
+        return m_name + "=" + m_value;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java
new file mode 100644
index 0000000..bb280c0
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/Requirement.java
@@ -0,0 +1,34 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.util.List;
+
+public interface Requirement
+{
+    String getNamespace();
+
+    SimpleFilter getFilter();
+
+    boolean isOptional();
+
+    Directive getDirective(String name);
+
+    List<Directive> getDirectives();
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java
new file mode 100644
index 0000000..8ab4668
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/capabilityset/SimpleFilter.java
@@ -0,0 +1,556 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.capabilityset;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class SimpleFilter
+{
+    public static final int MATCH_ALL = 0;
+    public static final int AND = 1;
+    public static final int OR = 2;
+    public static final int NOT = 3;
+    public static final int EQ = 4;
+    public static final int LTE = 5;
+    public static final int GTE = 6;
+    public static final int SUBSTRING = 7;
+    public static final int PRESENT = 8;
+    public static final int APPROX = 9;
+
+    private final String m_name;
+    private final Object m_value;
+    private final int m_op;
+
+    public SimpleFilter(String attr, Object value, int op)
+    {
+        m_name = attr;
+        m_value = value;
+        m_op = op;
+    }
+
+    public String getName()
+    {
+        return m_name;
+    }
+
+    public Object getValue()
+    {
+        return m_value;
+    }
+
+    public int getOperation()
+    {
+        return m_op;
+    }
+
+    public String toString()
+    {
+        String s = null;
+        switch (m_op)
+        {
+        case AND:
+            s = "(&" + toString((List) m_value) + ")";
+            break;
+        case OR:
+            s = "(|" + toString((List) m_value) + ")";
+            break;
+        case NOT:
+            s = "(!" + toString((List) m_value) + ")";
+            break;
+        case EQ:
+            s = "(" + m_name + "=" + toEncodedString(m_value) + ")";
+            break;
+        case LTE:
+            s = "(" + m_name + "<=" + toEncodedString(m_value) + ")";
+            break;
+        case GTE:
+            s = "(" + m_name + ">=" + toEncodedString(m_value) + ")";
+            break;
+        case SUBSTRING:
+            s = "(" + m_name + "=" + unparseSubstring((List<String>) m_value)
+                    + ")";
+            break;
+        case PRESENT:
+            s = "(" + m_name + "=*)";
+            break;
+        case APPROX:
+            s = "(" + m_name + "~=" + toEncodedString(m_value) + ")";
+            break;
+        }
+        return s;
+    }
+
+    private static String toString(List list)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < list.size(); i++)
+        {
+            sb.append(list.get(i).toString());
+        }
+        return sb.toString();
+    }
+
+    private static String toDecodedString(String s, int startIdx, int endIdx)
+    {
+        StringBuffer sb = new StringBuffer(endIdx - startIdx);
+        boolean escaped = false;
+        for (int i = 0; i < (endIdx - startIdx); i++)
+        {
+            char c = s.charAt(startIdx + i);
+            if (!escaped && (c == '\\'))
+            {
+                escaped = true;
+            }
+            else
+            {
+                escaped = false;
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    private static String toEncodedString(Object o)
+    {
+        if (o instanceof String)
+        {
+            String s = (String) o;
+            StringBuffer sb = new StringBuffer();
+            for (int i = 0; i < s.length(); i++)
+            {
+                char c = s.charAt(i);
+                if ((c == '\\') || (c == '(') || (c == ')') || (c == '*'))
+                {
+                    sb.append('\\');
+                }
+                sb.append(c);
+            }
+
+            o = sb.toString();
+        }
+
+        return o.toString();
+    }
+
+    public static SimpleFilter parse(String filter)
+    {
+        int idx = skipWhitespace(filter, 0);
+
+        if ((filter == null) || (filter.length() == 0)
+                || (idx >= filter.length()))
+        {
+            throw new IllegalArgumentException("Null or empty filter.");
+        }
+        else if (filter.charAt(idx) != '(')
+        {
+            throw new IllegalArgumentException("Missing opening parenthesis: "
+                    + filter);
+        }
+
+        SimpleFilter sf = null;
+        List stack = new ArrayList();
+        boolean isEscaped = false;
+        while (idx < filter.length())
+        {
+            if (sf != null)
+            {
+                throw new IllegalArgumentException(
+                        "Only one top-level operation allowed: " + filter);
+            }
+
+            if (!isEscaped && (filter.charAt(idx) == '('))
+            {
+                // Skip paren and following whitespace.
+                idx = skipWhitespace(filter, idx + 1);
+
+                if (filter.charAt(idx) == '&')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(),
+                                SimpleFilter.AND));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else if (filter.charAt(idx) == '|')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(),
+                                SimpleFilter.OR));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else if (filter.charAt(idx) == '!')
+                {
+                    int peek = skipWhitespace(filter, idx + 1);
+                    if (filter.charAt(peek) == '(')
+                    {
+                        idx = peek - 1;
+                        stack.add(0, new SimpleFilter(null, new ArrayList(),
+                                SimpleFilter.NOT));
+                    }
+                    else
+                    {
+                        stack.add(0, new Integer(idx));
+                    }
+                }
+                else
+                {
+                    stack.add(0, new Integer(idx));
+                }
+            }
+            else if (!isEscaped && (filter.charAt(idx) == ')'))
+            {
+                Object top = stack.remove(0);
+                if (top instanceof SimpleFilter)
+                {
+                    if (!stack.isEmpty()
+                            && (stack.get(0) instanceof SimpleFilter))
+                    {
+                        ((List) ((SimpleFilter) stack.get(0)).m_value).add(top);
+                    }
+                    else
+                    {
+                        sf = (SimpleFilter) top;
+                    }
+                }
+                else if (!stack.isEmpty()
+                        && (stack.get(0) instanceof SimpleFilter))
+                {
+                    ((List) ((SimpleFilter) stack.get(0)).m_value)
+                            .add(SimpleFilter.subfilter(filter,
+                                    ((Integer) top).intValue(), idx));
+                }
+                else
+                {
+                    sf = SimpleFilter.subfilter(filter,
+                            ((Integer) top).intValue(), idx);
+                }
+            }
+            else if (!isEscaped && (filter.charAt(idx) == '\\'))
+            {
+                isEscaped = true;
+            }
+            else
+            {
+                isEscaped = false;
+            }
+
+            idx = skipWhitespace(filter, idx + 1);
+        }
+
+        if (sf == null)
+        {
+            throw new IllegalArgumentException("Missing closing parenthesis: "
+                    + filter);
+        }
+
+        return sf;
+    }
+
+    private static SimpleFilter subfilter(String filter, int startIdx,
+            int endIdx)
+    {
+        final String opChars = "=<>~";
+
+        // Determine the ending index of the attribute name.
+        int attrEndIdx = startIdx;
+        for (int i = 0; i < (endIdx - startIdx); i++)
+        {
+            char c = filter.charAt(startIdx + i);
+            if (opChars.indexOf(c) >= 0)
+            {
+                break;
+            }
+            else if (!Character.isWhitespace(c))
+            {
+                attrEndIdx = startIdx + i + 1;
+            }
+        }
+        if (attrEndIdx == startIdx)
+        {
+            throw new IllegalArgumentException("Missing attribute name: "
+                    + filter.substring(startIdx, endIdx));
+        }
+        String attr = filter.substring(startIdx, attrEndIdx);
+
+        // Skip the attribute name and any following whitespace.
+        startIdx = skipWhitespace(filter, attrEndIdx);
+
+        // Determine the operator type.
+        int op = -1;
+        switch (filter.charAt(startIdx))
+        {
+        case '=':
+            op = EQ;
+            startIdx++;
+            break;
+        case '<':
+            if (filter.charAt(startIdx + 1) != '=')
+            {
+                throw new IllegalArgumentException("Unknown operator: "
+                        + filter.substring(startIdx, endIdx));
+            }
+            op = LTE;
+            startIdx += 2;
+            break;
+        case '>':
+            if (filter.charAt(startIdx + 1) != '=')
+            {
+                throw new IllegalArgumentException("Unknown operator: "
+                        + filter.substring(startIdx, endIdx));
+            }
+            op = GTE;
+            startIdx += 2;
+            break;
+        case '~':
+            if (filter.charAt(startIdx + 1) != '=')
+            {
+                throw new IllegalArgumentException("Unknown operator: "
+                        + filter.substring(startIdx, endIdx));
+            }
+            op = APPROX;
+            startIdx += 2;
+            break;
+        default:
+            throw new IllegalArgumentException("Unknown operator: "
+                    + filter.substring(startIdx, endIdx));
+        }
+
+        // Parse value.
+        Object value = toDecodedString(filter, startIdx, endIdx);
+
+        // Check if the equality comparison is actually a substring
+        // or present operation.
+        if (op == EQ)
+        {
+            String valueStr = filter.substring(startIdx, endIdx);
+            List<String> values = parseSubstring(valueStr);
+            if ((values.size() == 2) && (values.get(0).length() == 0)
+                    && (values.get(1).length() == 0))
+            {
+                op = PRESENT;
+            }
+            else if (values.size() > 1)
+            {
+                op = SUBSTRING;
+                value = values;
+            }
+        }
+
+        return new SimpleFilter(attr, value, op);
+    }
+
+    public static List<String> parseSubstring(String value)
+    {
+        List<String> pieces = new ArrayList();
+        StringBuffer ss = new StringBuffer();
+        // int kind = SIMPLE; // assume until proven otherwise
+        boolean wasStar = false; // indicates last piece was a star
+        boolean leftstar = false; // track if the initial piece is a star
+        boolean rightstar = false; // track if the final piece is a star
+
+        int idx = 0;
+
+        // We assume (sub)strings can contain leading and trailing blanks
+        boolean escaped = false;
+        loop: for (;;)
+        {
+            if (idx >= value.length())
+            {
+                if (wasStar)
+                {
+                    // insert last piece as "" to handle trailing star
+                    rightstar = true;
+                }
+                else
+                {
+                    pieces.add(ss.toString());
+                    // accumulate the last piece
+                    // note that in the case of
+                    // (cn=); this might be
+                    // the string "" (!=null)
+                }
+                ss.setLength(0);
+                break loop;
+            }
+
+            // Read the next character and account for escapes.
+            char c = value.charAt(idx++);
+            if (!escaped && ((c == '(') || (c == ')')))
+            {
+                throw new IllegalArgumentException("Illegal value: " + value);
+            }
+            else if (!escaped && (c == '*'))
+            {
+                if (wasStar)
+                {
+                    // encountered two successive stars;
+                    // I assume this is illegal
+                    throw new IllegalArgumentException(
+                            "Invalid filter string: " + value);
+                }
+                if (ss.length() > 0)
+                {
+                    pieces.add(ss.toString()); // accumulate the pieces
+                    // between '*' occurrences
+                }
+                ss.setLength(0);
+                // if this is a leading star, then track it
+                if (pieces.size() == 0)
+                {
+                    leftstar = true;
+                }
+                wasStar = true;
+            }
+            else if (!escaped && (c == '\\'))
+            {
+                escaped = true;
+            }
+            else
+            {
+                escaped = false;
+                wasStar = false;
+                ss.append(c);
+            }
+        }
+        if (leftstar || rightstar || pieces.size() > 1)
+        {
+            // insert leading and/or trailing "" to anchor ends
+            if (rightstar)
+            {
+                pieces.add("");
+            }
+            if (leftstar)
+            {
+                pieces.add(0, "");
+            }
+        }
+        return pieces;
+    }
+
+    public static String unparseSubstring(List<String> pieces)
+    {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < pieces.size(); i++)
+        {
+            if (i > 0)
+            {
+                sb.append("*");
+            }
+            sb.append(toEncodedString(pieces.get(i)));
+        }
+        return sb.toString();
+    }
+
+    public static boolean compareSubstring(List<String> pieces, String s)
+    {
+        // Walk the pieces to match the string
+        // There are implicit stars between each piece,
+        // and the first and last pieces might be "" to anchor the match.
+        // assert (pieces.length > 1)
+        // minimal case is <string>*<string>
+
+        boolean result = true;
+        int len = pieces.size();
+
+        // Special case, if there is only one piece, then
+        // we must perform an equality test.
+        if (len == 1)
+        {
+            return s.equals(pieces.get(0));
+        }
+
+        // Otherwise, check whether the pieces match
+        // the specified string.
+
+        int index = 0;
+
+        loop: for (int i = 0; i < len; i++)
+        {
+            String piece = pieces.get(i);
+
+            // If this is the first piece, then make sure the
+            // string starts with it.
+            if (i == 0)
+            {
+                if (!s.startsWith(piece))
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // If this is the last piece, then make sure the
+            // string ends with it.
+            if (i == len - 1)
+            {
+                if (s.endsWith(piece))
+                {
+                    result = true;
+                }
+                else
+                {
+                    result = false;
+                }
+                break loop;
+            }
+
+            // If this is neither the first or last piece, then
+            // make sure the string contains it.
+            if ((i > 0) && (i < (len - 1)))
+            {
+                index = s.indexOf(piece, index);
+                if (index < 0)
+                {
+                    result = false;
+                    break loop;
+                }
+            }
+
+            // Move string index beyond the matching piece.
+            index += piece.length();
+        }
+
+        return result;
+    }
+
+    private static int skipWhitespace(String s, int startIdx)
+    {
+        int len = s.length();
+        while ((startIdx < len) && Character.isWhitespace(s.charAt(startIdx)))
+        {
+            startIdx++;
+        }
+        return startIdx;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java
new file mode 100644
index 0000000..50912f2
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/EventDispatcher.java
@@ -0,0 +1,1044 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.osgi.framework.AllServiceListener;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServicePermission;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.SynchronousBundleListener;
+import org.osgi.framework.hooks.service.ListenerHook;
+import org.osgi.framework.launch.Framework;
+
+import de.kalpatec.pojosr.framework.felix.framework.ServiceRegistry;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class EventDispatcher
+{
+    private final ServiceRegistry m_registry;
+    private Map<BundleContext, List<ListenerInfo>> m_fwkListeners = Collections.EMPTY_MAP;
+    private Map<BundleContext, List<ListenerInfo>> m_bndlListeners = Collections.EMPTY_MAP;
+    private Map<BundleContext, List<ListenerInfo>> m_syncBndlListeners = Collections.EMPTY_MAP;
+    private Map<BundleContext, List<ListenerInfo>> m_svcListeners = Collections.EMPTY_MAP;
+    // A single thread is used to deliver events for all dispatchers.
+    private static Thread m_thread = null;
+    private final static String m_threadLock = new String("thread lock");
+    private static int m_references = 0;
+    private static volatile boolean m_stopping = false;
+    // List of requests.
+    private static final List<Request> m_requestList = new ArrayList<Request>();
+    // Pooled requests to avoid memory allocation.
+    private static final List<Request> m_requestPool = new ArrayList<Request>();
+
+    private static final boolean m_sync = "true".equalsIgnoreCase(System
+            .getProperty("de.kalpatec.pojosr.framework.events.sync"));
+    
+    public EventDispatcher(ServiceRegistry registry)
+    {
+        m_registry = registry;
+    }
+
+    public void startDispatching()
+    {
+        synchronized (m_threadLock)
+        {
+            // Start event dispatching thread if necessary.
+            if (m_thread == null || !m_thread.isAlive())
+            {
+                m_stopping = false;
+
+                if (!m_sync)
+                {
+                    m_thread = new Thread(new Runnable()
+                    {
+                        public void run()
+                        {
+                            try
+                            {
+                                EventDispatcher.run();
+                            }
+                            finally
+                            {
+                                // Ensure we update state even if stopped by
+                                // external cause
+                                // e.g. an Applet VM forceably killing threads
+                                synchronized (m_threadLock)
+                                {
+                                    m_thread = null;
+                                    m_stopping = false;
+                                    m_references = 0;
+                                    m_threadLock.notifyAll();
+                                }
+                            }
+                        }
+                    }, "FelixDispatchQueue");
+                    m_thread.start();
+                }
+            }
+
+            // reference counting and flags
+            m_references++;
+        }
+    }
+
+    public void stopDispatching()
+    {
+        synchronized (m_threadLock)
+        {
+            // Return if already dead or stopping.
+            if (m_thread == null || m_stopping)
+            {
+                return;
+            }
+
+            // decrement use counter, don't continue if there are users
+            m_references--;
+            if (m_references > 0)
+            {
+                return;
+            }
+
+            m_stopping = true;
+        }
+
+        // Signal dispatch thread.
+        synchronized (m_requestList)
+        {
+            m_requestList.notify();
+        }
+
+        // Use separate lock for shutdown to prevent any chance of nested lock
+        // deadlock
+        synchronized (m_threadLock)
+        {
+            while (m_thread != null)
+            {
+                try
+                {
+                    m_threadLock.wait();
+                }
+                catch (InterruptedException ex)
+                {
+                }
+            }
+        }
+    }
+
+    public Filter addListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
+    {
+        // Verify the listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+        else if (!clazz.isInstance(l))
+        {
+            throw new IllegalArgumentException(
+                "Listener not of type " + clazz.getName());
+        }
+
+        // See if we can simply update the listener, if so then
+        // return immediately.
+        Filter oldFilter = updateListener(bc, clazz, l, filter);
+        if (oldFilter != null)
+        {
+            return oldFilter;
+        }
+
+        // Lock the object to add the listener.
+        synchronized (this)
+        {
+            // Verify that the bundle context is still valid.
+            try
+            {
+                bc.getBundle();
+            }
+            catch (IllegalStateException ex)
+            {
+                // Bundle context is no longer valid, so just return.
+            }
+
+            Map<BundleContext, List<ListenerInfo>> listeners = null;
+            Object acc = null;
+
+            if (clazz == FrameworkListener.class)
+            {
+                listeners = m_fwkListeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    listeners = m_syncBndlListeners;
+                }
+                else
+                {
+                    listeners = m_bndlListeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                // Remember security context for filtering service events.
+               /* Object sm = System.getSecurityManager();
+                 if (sm != null)
+                 {
+                 acc = ((SecurityManager) sm).getSecurityContext();
+                 }*/
+                // We need to create a Set for keeping track of matching service
+                // registrations so we can fire ServiceEvent.MODIFIED_ENDMATCH
+                // events. We need a Set even if filter is null, since the
+                // listener can be updated and have a filter added later.
+                listeners = m_svcListeners;
+            }
+            else
+            {
+                throw new IllegalArgumentException("Unknown listener: " + l.getClass());
+            }
+
+            // Add listener.
+            ListenerInfo info =
+                new ListenerInfo(bc.getBundle(), bc, clazz, l, filter, acc, false);
+            listeners = addListenerInfo(listeners, info);
+
+            if (clazz == FrameworkListener.class)
+            {
+                m_fwkListeners = listeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    m_syncBndlListeners = listeners;
+                }
+                else
+                {
+                    m_bndlListeners = listeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                m_svcListeners = listeners;
+            }
+        }
+        return null;
+    }
+
+    public ListenerHook.ListenerInfo removeListener(
+        BundleContext bc, Class clazz, EventListener l)
+    {
+        ListenerHook.ListenerInfo returnInfo = null;
+
+        // Verify listener.
+        if (l == null)
+        {
+            throw new IllegalArgumentException("Listener is null");
+        }
+        else if (!clazz.isInstance(l))
+        {
+            throw new IllegalArgumentException(
+                "Listener not of type " + clazz.getName());
+        }
+
+        // Lock the object to remove the listener.
+        synchronized (this)
+        {
+            Map<BundleContext, List<ListenerInfo>> listeners = null;
+
+            if (clazz == FrameworkListener.class)
+            {
+                listeners = m_fwkListeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    listeners = m_syncBndlListeners;
+                }
+                else
+                {
+                    listeners = m_bndlListeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                listeners = m_svcListeners;
+            }
+            else
+            {
+                throw new IllegalArgumentException("Unknown listener: " + l.getClass());
+            }
+
+            // Try to find the instance in our list.
+            int idx = -1;
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+            {
+                List<ListenerInfo> infos = entry.getValue();
+                for (int i = 0; i < infos.size(); i++)
+                {
+                    ListenerInfo info = infos.get(i);
+                    if (info.getBundleContext().equals(bc)
+                        && (info.getListenerClass() == clazz)
+                        && (info.getListener() == l))
+                    {
+                        // For service listeners, we must return some info about
+                        // the listener for the ListenerHook callback.
+                        if (ServiceListener.class == clazz)
+                        {
+                            returnInfo = new ListenerInfo(infos.get(i), true);
+                        }
+                        idx = i;
+                        break;
+                    }
+                }
+            }
+
+            // If we have the instance, then remove it.
+            if (idx >= 0)
+            {
+                listeners = removeListenerInfo(listeners, bc, idx);
+            }
+
+            if (clazz == FrameworkListener.class)
+            {
+                m_fwkListeners = listeners;
+            }
+            else if (clazz == BundleListener.class)
+            {
+                if (SynchronousBundleListener.class.isInstance(l))
+                {
+                    m_syncBndlListeners = listeners;
+                }
+                else
+                {
+                    m_bndlListeners = listeners;
+                }
+            }
+            else if (clazz == ServiceListener.class)
+            {
+                m_svcListeners = listeners;
+            }
+        }
+
+        // Return information about the listener; this is null
+        // for everything but service listeners.
+        return returnInfo;
+    }
+
+    public void removeListeners(BundleContext bc)
+    {
+        if (bc == null)
+        {
+            return;
+        }
+
+        synchronized (this)
+        {
+            // Remove all framework listeners associated with the specified bundle.
+            m_fwkListeners = removeListenerInfos(m_fwkListeners, bc);
+
+            // Remove all bundle listeners associated with the specified bundle.
+            m_bndlListeners = removeListenerInfos(m_bndlListeners, bc);
+
+            // Remove all synchronous bundle listeners associated with
+            // the specified bundle.
+            m_syncBndlListeners = removeListenerInfos(m_syncBndlListeners, bc);
+
+            // Remove all service listeners associated with the specified bundle.
+            m_svcListeners = removeListenerInfos(m_svcListeners, bc);
+        }
+    }
+
+    public Filter updateListener(BundleContext bc, Class clazz, EventListener l, Filter filter)
+    {
+        if (clazz == ServiceListener.class)
+        {
+            synchronized (this)
+            {
+                // Verify that the bundle context is still valid.
+                try
+                {
+                    bc.getBundle();
+                }
+                catch (IllegalStateException ex)
+                {
+                    // Bundle context is no longer valid, so just return.
+                }
+
+                // See if the service listener is already registered; if so then
+                // update its filter per the spec.
+                List<ListenerInfo> infos = m_svcListeners.get(bc);
+                for (int i = 0; (infos != null) && (i < infos.size()); i++)
+                {
+                    ListenerInfo info = infos.get(i);
+                    if (info.getBundleContext().equals(bc)
+                        && (info.getListenerClass() == clazz)
+                        && (info.getListener() == l))
+                    {
+                        // The spec says to update the filter in this case.
+                        Filter oldFilter = info.getParsedFilter();
+                        ListenerInfo newInfo = new ListenerInfo(
+                            info.getBundle(),
+                            info.getBundleContext(),
+                            info.getListenerClass(),
+                            info.getListener(),
+                            filter,
+                            info.getSecurityContext(),
+                            info.isRemoved());
+                        m_svcListeners = updateListenerInfo(m_svcListeners, i, newInfo);
+                        return oldFilter;
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns all existing service listener information into a collection of
+     * ListenerHook.ListenerInfo objects. This is used the first time a listener
+     * hook is registered to synchronize it with the existing set of listeners.
+     *
+     * @return Returns all existing service listener information into a
+     * collection of ListenerHook.ListenerInfo objects
+     *
+     */
+    public Collection<ListenerHook.ListenerInfo> getAllServiceListeners()
+    {
+        List<ListenerHook.ListenerInfo> listeners = new ArrayList<ListenerHook.ListenerInfo>();
+        synchronized (this)
+        {
+            for (Entry<BundleContext, List<ListenerInfo>> entry : m_svcListeners.entrySet())
+            {
+                listeners.addAll(entry.getValue());
+            }
+        }
+        return listeners;
+    }
+
+    public void fireFrameworkEvent(FrameworkEvent event)
+    {
+        // Take a snapshot of the listener array.
+        Map<BundleContext, List<ListenerInfo>> listeners = null;
+        synchronized (this)
+        {
+            listeners = m_fwkListeners;
+        }
+
+        // Fire all framework listeners on a separate thread.
+        fireEventAsynchronously(this, Request.FRAMEWORK_EVENT, listeners, event);
+    }
+
+    public void fireBundleEvent(BundleEvent event)
+    {
+        // Take a snapshot of the listener array.
+        Map<BundleContext, List<ListenerInfo>> listeners = null;
+        Map<BundleContext, List<ListenerInfo>> syncListeners = null;
+        synchronized (this)
+        {
+            listeners = m_bndlListeners;
+            syncListeners = m_syncBndlListeners;
+        }
+
+        // Create a whitelist of bundle context for bundle listeners,
+        // if we have hooks.
+        Set<BundleContext> whitelist = createWhitelistFromHooks(event, event.getBundle(),
+            listeners, syncListeners, org.osgi.framework.hooks.bundle.EventHook.class);
+
+        // If we have a whitelist, then create copies of only the whitelisted
+        // listeners.
+        if (whitelist != null)
+        {
+            Map<BundleContext, List<ListenerInfo>> copy =
+                new HashMap<BundleContext, List<ListenerInfo>>();
+            for (BundleContext bc : whitelist)
+            {
+                List<ListenerInfo> infos = listeners.get(bc);
+                if (infos != null)
+                {
+                    copy.put(bc, infos);
+                }
+            }
+            listeners = copy;
+            copy = new HashMap<BundleContext, List<ListenerInfo>>();
+            for (BundleContext bc : whitelist)
+            {
+                List<ListenerInfo> infos = syncListeners.get(bc);
+                if (infos != null)
+                {
+                    copy.put(bc, infos);
+                }
+            }
+            syncListeners = copy;
+        }
+
+        // Fire synchronous bundle listeners immediately on the calling thread.
+        fireEventImmediately(
+            this, Request.BUNDLE_EVENT, syncListeners, event, null);
+
+        // The spec says that asynchronous bundle listeners do not get events
+        // of types STARTING, STOPPING, or LAZY_ACTIVATION.
+        if ((event.getType() != BundleEvent.STARTING)
+            && (event.getType() != BundleEvent.STOPPING)
+            && (event.getType() != BundleEvent.LAZY_ACTIVATION))
+        {
+            // Fire asynchronous bundle listeners on a separate thread.
+            fireEventAsynchronously(
+                this, Request.BUNDLE_EVENT, listeners, event);
+        }
+    }
+
+    public void fireServiceEvent(
+        final ServiceEvent event, final Dictionary oldProps, final Framework felix)
+    {
+        // Take a snapshot of the listener array.
+        Map<BundleContext, List<ListenerInfo>> listeners = null;
+        synchronized (this)
+        {
+            listeners = m_svcListeners;
+        }
+
+        // Use service registry hooks to filter target listeners.
+        listeners = filterListenersUsingHooks(event, felix, listeners);
+
+        // Fire all service events immediately on the calling thread.
+        fireEventImmediately(
+            this, Request.SERVICE_EVENT, listeners, event, oldProps);
+    }
+
+// TODO: OSGi R4.3 - This is ugly and inefficient.
+    private Map<BundleContext, List<ListenerInfo>> filterListenersUsingHooks(
+        ServiceEvent event, Framework felix, Map<BundleContext, List<ListenerInfo>> listeners)
+    {
+        Set<ServiceReference<org.osgi.framework.hooks.service.EventHook>> ehs =
+            m_registry.getHooks(org.osgi.framework.hooks.service.EventHook.class);
+        if ((ehs != null) && !ehs.isEmpty())
+        {
+            // Create a whitelist of bundle context for bundle listeners,
+            // if we have hooks.
+            Set<BundleContext> whitelist = createWhitelistFromHooks(event, felix,
+                listeners, null, org.osgi.framework.hooks.service.EventHook.class);
+
+            // If we have a whitelist, then create copies of only the whitelisted
+            // listeners.
+            if (whitelist != null)
+            {
+                Map<BundleContext, List<ListenerInfo>> copy =
+                    new HashMap<BundleContext, List<ListenerInfo>>();
+                for (BundleContext bc : whitelist)
+                {
+                    copy.put(bc, listeners.get(bc));
+                }
+                listeners = copy;
+            }
+        }
+
+        Set<ServiceReference<org.osgi.framework.hooks.service.EventListenerHook>> elhs =
+            m_registry.getHooks(org.osgi.framework.hooks.service.EventListenerHook.class);
+        if ((elhs != null) && !elhs.isEmpty())
+        {
+            // Create shrinkable map with shrinkable collections.
+            Map<BundleContext, Collection<ListenerHook.ListenerInfo>> shrinkableMap =
+                new HashMap<BundleContext, Collection<ListenerHook.ListenerInfo>>();
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+            {
+                Collection shrinkableCollection =
+                    new ShrinkableCollection(new ArrayList(entry.getValue()));
+                shrinkableMap.put(
+                    entry.getKey(),
+                    (Collection<ListenerHook.ListenerInfo>) shrinkableCollection);
+            }
+            shrinkableMap =
+                new ShrinkableMap<BundleContext, Collection<ListenerHook.ListenerInfo>>(shrinkableMap);
+            for (ServiceReference<org.osgi.framework.hooks.service.EventListenerHook> sr : elhs)
+            {
+                if (felix != null)
+                {
+                    org.osgi.framework.hooks.service.EventListenerHook elh = null;
+                    try
+                    {
+                        elh = m_registry.getService(felix, sr);
+                    }
+                    catch (Exception ex)
+                    {
+                        // If we can't get the hook, then ignore it.
+                    }
+                    if (elh != null)
+                    {
+                        try
+                        {
+                            elh.event(event, shrinkableMap);
+                        }
+                        catch (Throwable th)
+                        {
+                            System.out.println("Problem invoking event hook");
+                            th.printStackTrace();
+                        }
+                        finally
+                        {
+                            m_registry.ungetService(felix, sr);
+                        }
+                    }
+                }
+            }
+// TODO: OSGi R4.3 - Should check and only do this if there was a change.
+//       Also, it is inefficient to have to create new lists for the values.
+            Map<BundleContext, List<ListenerInfo>> newMap =
+                new HashMap<BundleContext, List<ListenerInfo>>();
+            for (Entry entry : shrinkableMap.entrySet())
+            {
+                if (!((Collection) entry.getValue()).isEmpty())
+                {
+                    newMap.put((BundleContext) entry.getKey(),
+                        new ArrayList<ListenerInfo>((Collection) entry.getValue()));
+                }
+            }
+            listeners = newMap;
+        }
+
+        return listeners;
+    }
+
+    private <T> Set<BundleContext> createWhitelistFromHooks(
+        EventObject event, Bundle bundle,
+        Map<BundleContext, List<ListenerInfo>> listeners1,
+        Map<BundleContext, List<ListenerInfo>> listeners2,
+        Class<T> hookClass)
+    {
+        // Create a whitelist of bundle context, if we have hooks.
+        Set<BundleContext> whitelist = null;
+        Set<ServiceReference<T>> hooks = m_registry.getHooks(hookClass);
+        if ((hooks != null) && !hooks.isEmpty())
+        {
+            whitelist = new HashSet<BundleContext>();
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners1.entrySet())
+            {
+                whitelist.add(entry.getKey());
+            }
+            if (listeners2 != null)
+            {
+                for (Entry<BundleContext, List<ListenerInfo>> entry : listeners2.entrySet())
+                {
+                    whitelist.add(entry.getKey());
+                }
+            }
+
+            int originalSize = whitelist.size();
+            ShrinkableCollection<BundleContext> shrinkable =
+                new ShrinkableCollection<BundleContext>(whitelist);
+            for (ServiceReference<T> sr : hooks)
+            {
+                if (bundle != null)
+                {
+                    T eh = null;
+                    try
+                    {
+                        eh = m_registry.getService(bundle, sr);
+                    }
+                    catch (Exception ex)
+                    {
+                        // If we can't get the hook, then ignore it.
+                    }
+                    if (eh != null)
+                    {
+                        try
+                        {
+                            if (eh instanceof org.osgi.framework.hooks.service.EventHook)
+                            {
+                                ((org.osgi.framework.hooks.service.EventHook) eh).event((ServiceEvent) event, shrinkable);
+                            }
+                            else if (eh instanceof org.osgi.framework.hooks.bundle.EventHook)
+                            {
+                                ((org.osgi.framework.hooks.bundle.EventHook) eh).event((BundleEvent) event, shrinkable);
+                            }
+                        }
+                        catch (Throwable th)
+                        {
+                            System.out.println("Problem invoking event hook");
+                            th.printStackTrace();
+                        }
+                        finally
+                        {
+                            m_registry.ungetService(bundle, sr);
+                        }
+                    }
+                }
+            }
+            // If the whitelist hasn't changed, then null it to avoid having
+            // to do whitelist lookups during event delivery.
+            if (originalSize == whitelist.size())
+            {
+                whitelist = null;
+            }
+        }
+        return whitelist;
+    }
+
+    private static void fireEventAsynchronously(
+        EventDispatcher dispatcher, int type,
+        Map<BundleContext, List<ListenerInfo>> listeners,
+        EventObject event)
+    {
+        if (!m_sync)
+        {
+            // TODO: should possibly check this within thread lock, seems to be
+            // ok
+            // though without
+            // If dispatch thread is stopped, then ignore dispatch request.
+            if (m_stopping || m_thread == null)
+            {
+                return;
+            }
+
+            // First get a request from the pool or create one if necessary.
+            Request req = null;
+            synchronized (m_requestPool)
+            {
+                if (m_requestPool.size() > 0)
+                {
+                    req = m_requestPool.remove(0);
+                }
+                else
+                {
+                    req = new Request();
+                }
+            }
+
+            // Initialize dispatch request.
+            req.m_dispatcher = dispatcher;
+            req.m_type = type;
+            req.m_listeners = listeners;
+            req.m_event = event;
+
+            // Lock the request list.
+            synchronized (m_requestList)
+            {
+                // Add our request to the list.
+                m_requestList.add(req);
+                // Notify the dispatch thread that there is work to do.
+                m_requestList.notify();
+            }
+        }
+        else
+        {
+            fireEventImmediately(dispatcher, type, listeners, event, null);
+        }
+    }
+
+    private static void fireEventImmediately(
+        EventDispatcher dispatcher, int type,
+        Map<BundleContext, List<ListenerInfo>> listeners,
+        EventObject event, Dictionary oldProps)
+    {
+        if (!listeners.isEmpty())
+        {
+            // Notify appropriate listeners.
+            for (Entry<BundleContext, List<ListenerInfo>> entry : listeners.entrySet())
+            {
+                for (ListenerInfo info : entry.getValue())
+                {
+                    Bundle bundle = info.getBundle();
+                    EventListener l = info.getListener();
+                    Filter filter = info.getParsedFilter();
+                    Object acc = info.getSecurityContext();
+
+                    try
+                    {
+                        if (type == Request.FRAMEWORK_EVENT)
+                        {
+                            invokeFrameworkListenerCallback(bundle, l, event);
+                        }
+                        else if (type == Request.BUNDLE_EVENT)
+                        {
+                            invokeBundleListenerCallback(bundle, l, event);
+                        }
+                        else if (type == Request.SERVICE_EVENT)
+                        {
+                            invokeServiceListenerCallback(
+                                bundle, l, filter, acc, event, oldProps);
+                        }
+                    }
+                    catch (Throwable th)
+                    {
+                        if ((type != Request.FRAMEWORK_EVENT)
+                            || (((FrameworkEvent) event).getType() != FrameworkEvent.ERROR))
+                        {
+                            System.out.println("EventDispatcher: Error during dispatch.");
+                            th.printStackTrace();
+                            dispatcher.fireFrameworkEvent(
+                                new FrameworkEvent(FrameworkEvent.ERROR, bundle, th));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private static void invokeFrameworkListenerCallback(
+        Bundle bundle, final EventListener l, final EventObject event)
+    {
+        // The spec says only active bundles receive asynchronous events,
+        // but we will include starting bundles too otherwise
+        // it is impossible to see everything.
+        if ((bundle.getState() == Bundle.STARTING)
+            || (bundle.getState() == Bundle.ACTIVE))
+        {
+            ((FrameworkListener) l).frameworkEvent((FrameworkEvent) event);
+
+        }
+    }
+
+    private static void invokeBundleListenerCallback(
+        Bundle bundle, final EventListener l, final EventObject event)
+    {
+        // A bundle listener is either synchronous or asynchronous.
+        // If the bundle listener is synchronous, then deliver the
+        // event to bundles with a state of STARTING, STOPPING, or
+        // ACTIVE. If the listener is asynchronous, then deliver the
+        // event only to bundles that are STARTING or ACTIVE.
+        if (((SynchronousBundleListener.class.isAssignableFrom(l.getClass()))
+            && ((bundle.getState() == Bundle.STARTING)
+            || (bundle.getState() == Bundle.STOPPING)
+            || (bundle.getState() == Bundle.ACTIVE)))
+            || ((bundle.getState() == Bundle.STARTING)
+            || (bundle.getState() == Bundle.ACTIVE)))
+        {
+            ((BundleListener) l).bundleChanged((BundleEvent) event);
+        }
+    }
+
+    private static void invokeServiceListenerCallback(Bundle bundle,
+        final EventListener l, Filter filter, Object acc,
+        final EventObject event, final Dictionary oldProps)
+    {
+        // Service events should be delivered to STARTING,
+        // STOPPING, and ACTIVE bundles.
+        if ((bundle.getState() != Bundle.STARTING)
+            && (bundle.getState() != Bundle.STOPPING)
+            && (bundle.getState() != Bundle.ACTIVE))
+        {
+            return;
+        }
+
+        // Check that the bundle has permission to get at least
+        // one of the service interfaces; the objectClass property
+        // of the service stores its service interfaces.
+        ServiceReference ref = ((ServiceEvent) event).getServiceReference();
+
+        boolean hasPermission = true;
+        if (hasPermission)
+        {
+            // Dispatch according to the filter.
+            boolean matched = (filter == null)
+                || filter.match(((ServiceEvent) event).getServiceReference());
+
+            if (matched)
+            {
+                ((ServiceListener) l)
+                    .serviceChanged((ServiceEvent) event);
+            }
+            // We need to send an MODIFIED_ENDMATCH event if the listener
+            // matched previously.
+            else if (((ServiceEvent) event).getType() == ServiceEvent.MODIFIED)
+            {
+                if (filter.match(oldProps))
+                {
+                    final ServiceEvent se = new ServiceEvent(
+                        ServiceEvent.MODIFIED_ENDMATCH,
+                        ((ServiceEvent) event).getServiceReference());
+                    ((ServiceListener) l).serviceChanged(se);
+
+                }
+            }
+        }
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> addListenerInfo(
+        Map<BundleContext, List<ListenerInfo>> listeners, ListenerInfo info)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and make a copy so we can modify it.
+        List<ListenerInfo> infos = copy.remove(info.getBundleContext());
+        if (infos == null)
+        {
+            infos = new ArrayList<ListenerInfo>();
+        }
+        else
+        {
+            infos = new ArrayList<ListenerInfo>(infos);
+        }
+        // Add the new listener info.
+        infos.add(info);
+        // Put the listeners back into the copy of the map and return it.
+        copy.put(info.getBundleContext(), infos);
+        return copy;
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> updateListenerInfo(
+        Map<BundleContext, List<ListenerInfo>> listeners, int idx,
+        ListenerInfo info)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and make a copy so we can modify it.
+        List<ListenerInfo> infos = copy.remove(info.getBundleContext());
+        if (infos != null)
+        {
+            infos = new ArrayList<ListenerInfo>(infos);
+            // Update the new listener info.
+            infos.set(idx, info);
+            // Put the listeners back into the copy of the map and return it.
+            copy.put(info.getBundleContext(), infos);
+            return copy;
+        }
+        return listeners;
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> removeListenerInfo(
+        Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc, int idx)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and make a copy so we can modify it.
+        List<ListenerInfo> infos = copy.remove(bc);
+        if (infos != null)
+        {
+            infos = new ArrayList<ListenerInfo>(infos);
+            // Remove the listener info.
+            infos.remove(idx);
+            if (!infos.isEmpty())
+            {
+                // Put the listeners back into the copy of the map and return it.
+                copy.put(bc, infos);
+            }
+            return copy;
+        }
+        return listeners;
+    }
+
+    private static Map<BundleContext, List<ListenerInfo>> removeListenerInfos(
+        Map<BundleContext, List<ListenerInfo>> listeners, BundleContext bc)
+    {
+        // Make a copy of the map, since we will be mutating it.
+        Map<BundleContext, List<ListenerInfo>> copy =
+            new HashMap<BundleContext, List<ListenerInfo>>(listeners);
+        // Remove the affected entry and return the copy.
+        copy.remove(bc);
+        return copy;
+    }
+
+    /**
+     * This is the dispatching thread's main loop.
+     *
+     */
+    private static void run()
+    {
+        Request req = null;
+        while (true)
+        {
+            // Lock the request list so we can try to get a
+            // dispatch request from it.
+            synchronized (m_requestList)
+            {
+                // Wait while there are no requests to dispatch. If the
+                // dispatcher thread is supposed to stop, then let the
+                // dispatcher thread exit the loop and stop.
+                while (m_requestList.isEmpty() && !m_stopping)
+                {
+                    // Wait until some signals us for work.
+                    try
+                    {
+                        m_requestList.wait();
+                    }
+                    catch (InterruptedException ex)
+                    {
+                        // Not much we can do here except for keep waiting.
+                    }
+                }
+
+                // If there are no events to dispatch and shutdown
+                // has been called then exit, otherwise dispatch event.
+                if (m_requestList.isEmpty() && m_stopping)
+                {
+                    return;
+                }
+
+                // Get the dispatch request.
+                req = m_requestList.remove(0);
+            }
+
+            // Deliver event outside of synchronized block
+            // so that we don't block other requests from being
+            // queued during event processing.
+            // NOTE: We don't catch any exceptions here, because
+            // the invoked method shields us from exceptions by
+            // catching Throwables when it invokes callbacks.
+            fireEventImmediately(
+                req.m_dispatcher, req.m_type, req.m_listeners,
+                req.m_event, null);
+
+            // Put dispatch request in cache.
+            synchronized (m_requestPool)
+            {
+                req.m_dispatcher = null;
+                req.m_type = -1;
+                req.m_listeners = null;
+                req.m_event = null;
+                m_requestPool.add(req);
+            }
+        }
+    }
+
+    private static class Request
+    {
+        public static final int FRAMEWORK_EVENT = 0;
+        public static final int BUNDLE_EVENT = 1;
+        public static final int SERVICE_EVENT = 2;
+        public EventDispatcher m_dispatcher = null;
+        public int m_type = -1;
+        public Map<BundleContext, List<ListenerInfo>> m_listeners = null;
+        public EventObject m_event = null;
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java
new file mode 100644
index 0000000..2ef572b
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerHookInfoImpl.java
@@ -0,0 +1,85 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+public class ListenerHookInfoImpl implements ListenerHook.ListenerInfo
+{
+    private final BundleContext m_context;
+    private final ServiceListener m_listener;
+    private final String m_filter;
+    private boolean m_removed;
+
+    public ListenerHookInfoImpl(BundleContext context,
+            ServiceListener listener, String filter, boolean removed)
+    {
+        m_context = context;
+        m_listener = listener;
+        m_filter = filter;
+        m_removed = removed;
+    }
+
+    public BundleContext getBundleContext()
+    {
+        return m_context;
+    }
+
+    public String getFilter()
+    {
+        return m_filter;
+    }
+
+    public boolean isRemoved()
+    {
+        return m_removed;
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+
+        if (!(obj instanceof ListenerHookInfoImpl))
+        {
+            return false;
+        }
+
+        ListenerHookInfoImpl other = (ListenerHookInfoImpl) obj;
+        return other.m_listener == m_listener
+                && (m_filter == null ? other.m_filter == null : m_filter
+                        .equals(other.m_filter));
+    }
+
+    public int hashCode()
+    {
+        int rc = 17;
+
+        rc = 37 * rc + m_listener.hashCode();
+        if (m_filter != null)
+        {
+            rc = 37 * rc + m_filter.hashCode();
+        }
+        return rc;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerInfo.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerInfo.java
new file mode 100644
index 0000000..dcdc5f4
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ListenerInfo.java
@@ -0,0 +1,141 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.EventListener;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+
+public class ListenerInfo implements ListenerHook.ListenerInfo
+{
+    private final Bundle m_bundle;
+    private final BundleContext m_context;
+    private final Class m_listenerClass;
+    private final EventListener m_listener;
+    private final Filter m_filter;
+    private final Object m_acc;
+    private final boolean m_removed;
+
+    public ListenerInfo(
+        Bundle bundle, BundleContext context, Class listenerClass, EventListener listener,
+        Filter filter, Object acc, boolean removed)
+    {
+        // Technically, we could get the bundle from the bundle context, but
+        // there are some corner cases where the bundle context might become
+        // invalid and we still need the bundle.
+        m_bundle = bundle;
+        m_context = context;
+        m_listenerClass = listenerClass;
+        m_listener = listener;
+        m_filter = filter;
+        m_acc = acc;
+        m_removed = removed;
+    }
+
+    public ListenerInfo(ListenerInfo info, boolean removed)
+    {
+        m_bundle = info.m_bundle;
+        m_context = info.m_context;
+        m_listenerClass = info.m_listenerClass;
+        m_listener = info.m_listener;
+        m_filter = info.m_filter;
+        m_acc = info.m_acc;
+        m_removed = removed;
+    }
+
+    public Bundle getBundle()
+    {
+        return m_bundle;
+    }
+
+    public BundleContext getBundleContext()
+    {
+        return m_context;
+    }
+
+    public Class getListenerClass()
+    {
+        return m_listenerClass;
+    }
+
+    public EventListener getListener()
+    {
+        return m_listener;
+    }
+
+    public Filter getParsedFilter()
+    {
+        return m_filter;
+    }
+
+    public String getFilter()
+    {
+        if (m_filter != null)
+        {
+            return m_filter.toString();
+        }
+        return null;
+    }
+
+    public Object getSecurityContext()
+    {
+        return m_acc;
+    }
+
+    public boolean isRemoved()
+    {
+        return m_removed;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == this)
+        {
+            return true;
+        }
+
+        if (!(obj instanceof ListenerInfo))
+        {
+            return false;
+        }
+
+        ListenerInfo other = (ListenerInfo) obj;
+        return (other.m_bundle == m_bundle)
+            && (other.m_context == m_context)
+            && (other.m_listenerClass == m_listenerClass)
+            && (other.m_listener == m_listener)
+            && (m_filter == null ? other.m_filter == null : m_filter.equals(other.m_filter));
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int hash = 7;
+        hash = 59 * hash + (this.m_bundle != null ? this.m_bundle.hashCode() : 0);
+        hash = 59 * hash + (this.m_context != null ? this.m_context.hashCode() : 0);
+        hash = 59 * hash + (this.m_listenerClass != null ? this.m_listenerClass.hashCode() : 0);
+        hash = 59 * hash + (this.m_listener != null ? this.m_listener.hashCode() : 0);
+        hash = 59 * hash + (this.m_filter != null ? this.m_filter.hashCode() : 0);
+        return hash;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/MapToDictionary.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/MapToDictionary.java
new file mode 100644
index 0000000..084a1f4
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/MapToDictionary.java
@@ -0,0 +1,82 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.*;
+
+/**
+ * This is a simple class that implements a <tt>Dictionary</tt> from a
+ * <tt>Map</tt>. The resulting dictionary is immutable.
+ **/
+public class MapToDictionary extends Dictionary
+{
+    /**
+     * Map source.
+     **/
+    private Map m_map = null;
+
+    public MapToDictionary(Map map)
+    {
+        if (map == null)
+        {
+            throw new IllegalArgumentException("Source map cannot be null.");
+        }
+        m_map = map;
+    }
+
+    public Enumeration elements()
+    {
+        return Collections.enumeration(m_map.values());
+    }
+
+    public Object get(Object key)
+    {
+        return m_map.get(key);
+    }
+
+    public boolean isEmpty()
+    {
+        return m_map.isEmpty();
+    }
+
+    public Enumeration keys()
+    {
+        return Collections.enumeration(m_map.keySet());
+    }
+
+    public Object put(Object key, Object value)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public Object remove(Object key)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public int size()
+    {
+        return m_map.size();
+    }
+
+    public String toString()
+    {
+        return m_map.toString();
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ShrinkableCollection.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ShrinkableCollection.java
new file mode 100644
index 0000000..1420d31
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ShrinkableCollection.java
@@ -0,0 +1,112 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * A collection wrapper that only permits clients to shrink the collection.
+**/
+public class ShrinkableCollection<T> implements Collection<T>
+{
+    private final Collection<T> m_delegate;
+
+    public ShrinkableCollection(Collection<T> delegate)
+    {
+        m_delegate = delegate;
+    }
+
+    public boolean add(T o)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public boolean addAll(Collection<? extends T> c)
+    {
+        throw new UnsupportedOperationException();
+    }
+
+    public void clear()
+    {
+        m_delegate.clear();
+    }
+
+    public boolean contains(Object o)
+    {
+        return m_delegate.contains(o);
+    }
+
+    public boolean containsAll(Collection<?> c)
+    {
+        return m_delegate.containsAll(c);
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        return m_delegate.equals(o);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return m_delegate.hashCode();
+    }
+
+    public boolean isEmpty()
+    {
+        return m_delegate.isEmpty();
+    }
+
+    public Iterator iterator()
+    {
+        return m_delegate.iterator();
+    }
+
+    public boolean remove(Object o)
+    {
+        return m_delegate.remove(o);
+    }
+
+    public boolean removeAll(Collection<?> c)
+    {
+        return m_delegate.removeAll(c);
+    }
+
+    public boolean retainAll(Collection<?> c)
+    {
+        return m_delegate.retainAll(c);
+    }
+
+    public int size()
+    {
+        return m_delegate.size();
+    }
+
+    public Object[] toArray()
+    {
+        return m_delegate.toArray();
+    }
+
+    public <A> A[] toArray(A[] a)
+    {
+        return m_delegate.toArray(a);
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ShrinkableMap.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ShrinkableMap.java
new file mode 100644
index 0000000..b058609
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/ShrinkableMap.java
@@ -0,0 +1,94 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+public class ShrinkableMap<K, V> implements Map<K, V>
+{
+    private final Map<K, V> m_delegate;
+
+    public ShrinkableMap(Map<K, V> delegate)
+    {
+        m_delegate = delegate;
+    }
+
+    public int size()
+    {
+        return m_delegate.size();
+    }
+
+    public boolean isEmpty()
+    {
+        return m_delegate.isEmpty();
+    }
+
+    public boolean containsKey(Object o)
+    {
+        return m_delegate.containsKey(o);
+    }
+
+    public boolean containsValue(Object o)
+    {
+        return m_delegate.containsValue(o);
+    }
+
+    public V get(Object o)
+    {
+        return m_delegate.get(o);
+    }
+
+    public V put(K k, V v)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public V remove(Object o)
+    {
+        return m_delegate.remove(o);
+    }
+
+    public void putAll(Map<? extends K, ? extends V> map)
+    {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public void clear()
+    {
+        m_delegate.clear();
+    }
+
+    public Set<K> keySet()
+    {
+        return m_delegate.keySet();
+    }
+
+    public Collection<V> values()
+    {
+        return m_delegate.values();
+    }
+
+    public Set<Entry<K, V>> entrySet()
+    {
+        return m_delegate.entrySet();
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/StringComparator.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/StringComparator.java
new file mode 100644
index 0000000..6d359f0
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/StringComparator.java
@@ -0,0 +1,48 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.Comparator;
+
+public class StringComparator implements Comparator
+{
+    private final boolean m_isCaseSensitive;
+
+    public StringComparator(boolean b)
+    {
+        m_isCaseSensitive = b;
+    }
+
+    public int compare(Object o1, Object o2)
+    {
+        if (m_isCaseSensitive)
+        {
+            return o1.toString().compareTo(o2.toString());
+        }
+        else
+        {
+            return o1.toString().compareToIgnoreCase(o2.toString());
+        }
+    }
+
+    public boolean isCaseSensitive()
+    {
+        return m_isCaseSensitive;
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/StringMap.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/StringMap.java
new file mode 100644
index 0000000..8280961
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/StringMap.java
@@ -0,0 +1,133 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.util.*;
+
+/**
+ * Simple utility class that creates a map for string-based keys. This map can
+ * be set to use case-sensitive or case-insensitive comparison when searching
+ * for the key. Any keys put into this map will be converted to a
+ * <tt>String</tt> using the <tt>toString()</tt> method, since it is only
+ * intended to compare strings.
+ **/
+public class StringMap implements Map
+{
+    private TreeMap m_map;
+
+    public StringMap()
+    {
+        this(true);
+    }
+
+    public StringMap(boolean caseSensitive)
+    {
+        m_map = new TreeMap(new StringComparator(caseSensitive));
+    }
+
+    public StringMap(Map map, boolean caseSensitive)
+    {
+        this(caseSensitive);
+        putAll(map);
+    }
+
+    public boolean isCaseSensitive()
+    {
+        return ((StringComparator) m_map.comparator()).isCaseSensitive();
+    }
+
+    public void setCaseSensitive(boolean b)
+    {
+        if (isCaseSensitive() != b)
+        {
+            TreeMap map = new TreeMap(new StringComparator(b));
+            map.putAll(m_map);
+            m_map = map;
+        }
+    }
+
+    public int size()
+    {
+        return m_map.size();
+    }
+
+    public boolean isEmpty()
+    {
+        return m_map.isEmpty();
+    }
+
+    public boolean containsKey(Object arg0)
+    {
+        return m_map.containsKey(arg0);
+    }
+
+    public boolean containsValue(Object arg0)
+    {
+        return m_map.containsValue(arg0);
+    }
+
+    public Object get(Object arg0)
+    {
+        return m_map.get(arg0);
+    }
+
+    public Object put(Object key, Object value)
+    {
+        return m_map.put(key.toString(), value);
+    }
+
+    public void putAll(Map map)
+    {
+        for (Iterator it = map.entrySet().iterator(); it.hasNext();)
+        {
+            Map.Entry entry = (Map.Entry) it.next();
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public Object remove(Object arg0)
+    {
+        return m_map.remove(arg0);
+    }
+
+    public void clear()
+    {
+        m_map.clear();
+    }
+
+    public Set keySet()
+    {
+        return m_map.keySet();
+    }
+
+    public Collection values()
+    {
+        return m_map.values();
+    }
+
+    public Set entrySet()
+    {
+        return m_map.entrySet();
+    }
+
+    public String toString()
+    {
+        return m_map.toString();
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/Util.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/Util.java
new file mode 100644
index 0000000..bdbdbfb
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/Util.java
@@ -0,0 +1,527 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class Util
+{
+    /**
+     * The default name used for the default configuration properties file.
+     **/
+    private static final String DEFAULT_PROPERTIES_FILE = "default.properties";
+
+    public static String getDefaultProperty(String name)
+    {
+        String value = null;
+
+        URL propURL = Util.class.getClassLoader().getResource(
+                DEFAULT_PROPERTIES_FILE);
+        if (propURL != null)
+        {
+            InputStream is = null;
+            try
+            {
+                // Load properties from URL.
+                is = propURL.openConnection().getInputStream();
+                Properties props = new Properties();
+                props.load(is);
+                is.close();
+                // Perform variable substitution for property.
+                value = props.getProperty(name);
+                value = (value != null) ? Util.substVars(value, name, null,
+                        props) : null;
+            }
+            catch (Exception ex)
+            {
+                // Try to close input stream if we have one.
+                try
+                {
+                    if (is != null)
+                        is.close();
+                }
+                catch (IOException ex2)
+                {
+                    // Nothing we can do.
+                }
+
+                ex.printStackTrace();
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Converts a module identifier to a bundle identifier. Module IDs are
+     * typically <tt>&lt;bundle-id&gt;.&lt;revision&gt;</tt>; this method
+     * returns only the portion corresponding to the bundle ID.
+     **/
+    public static long getBundleIdFromModuleId(String id)
+    {
+        try
+        {
+            String bundleId = (id.indexOf('.') >= 0) ? id.substring(0,
+                    id.indexOf('.')) : id;
+            return Long.parseLong(bundleId);
+        }
+        catch (NumberFormatException ex)
+        {
+            return -1;
+        }
+    }
+
+    /**
+     * Converts a module identifier to a bundle identifier. Module IDs are
+     * typically <tt>&lt;bundle-id&gt;.&lt;revision&gt;</tt>; this method
+     * returns only the portion corresponding to the revision.
+     **/
+    public static int getModuleRevisionFromModuleId(String id)
+    {
+        try
+        {
+            int index = id.indexOf('.');
+            if (index >= 0)
+            {
+                return Integer.parseInt(id.substring(index + 1));
+            }
+        }
+        catch (NumberFormatException ex)
+        {
+        }
+        return -1;
+    }
+
+    public static String getClassName(String className)
+    {
+        if (className == null)
+        {
+            className = "";
+        }
+        return (className.lastIndexOf('.') < 0) ? "" : className
+                .substring(className.lastIndexOf('.') + 1);
+    }
+
+    public static String getClassPackage(String className)
+    {
+        if (className == null)
+        {
+            className = "";
+        }
+        return (className.lastIndexOf('.') < 0) ? "" : className.substring(0,
+                className.lastIndexOf('.'));
+    }
+
+    public static String getResourcePackage(String resource)
+    {
+        if (resource == null)
+        {
+            resource = "";
+        }
+        // NOTE: The package of a resource is tricky to determine since
+        // resources do not follow the same naming conventions as classes.
+        // This code is pessimistic and assumes that the package of a
+        // resource is everything up to the last '/' character. By making
+        // this choice, it will not be possible to load resources from
+        // imports using relative resource names. For example, if a
+        // bundle exports "foo" and an importer of "foo" tries to load
+        // "/foo/bar/myresource.txt", this will not be found in the exporter
+        // because the following algorithm assumes the package name is
+        // "foo.bar", not just "foo". This only affects imported resources,
+        // local resources will work as expected.
+        String pkgName = (resource.startsWith("/")) ? resource.substring(1)
+                : resource;
+        pkgName = (pkgName.lastIndexOf('/') < 0) ? "" : pkgName.substring(0,
+                pkgName.lastIndexOf('/'));
+        pkgName = pkgName.replace('/', '.');
+        return pkgName;
+    }
+
+    /**
+     * <p>
+     * This is a simple utility class that attempts to load the named class
+     * using the class loader of the supplied class or the class loader of one
+     * of its super classes or their implemented interfaces. This is necessary
+     * during service registration to test whether a given service object
+     * implements its declared service interfaces.
+     * </p>
+     * <p>
+     * To perform this test, the framework must try to load the classes
+     * associated with the declared service interfaces, so it must choose a
+     * class loader. The class loader of the registering bundle cannot be used,
+     * since this disallows third parties to register service on behalf of
+     * another bundle. Consequently, the class loader of the service object must
+     * be used. However, this is also not sufficient since the class loader of
+     * the service object may not have direct access to the class in question.
+     * </p>
+     * <p>
+     * The service object's class loader may not have direct access to its
+     * service interface if it extends a super class from another bundle which
+     * implements the service interface from an imported bundle or if it
+     * implements an extension of the service interface from another bundle
+     * which imports the base interface from another bundle. In these cases, the
+     * service object's class loader only has access to the super class's class
+     * or the extended service interface, respectively, but not to the actual
+     * service interface.
+     * </p>
+     * <p>
+     * Thus, it is necessary to not only try to load the service interface class
+     * from the service object's class loader, but from the class loaders of any
+     * interfaces it implements and the class loaders of all super classes.
+     * </p>
+     * 
+     * @param svcObj
+     *            the class that is the root of the search.
+     * @param name
+     *            the name of the class to load.
+     * @return the loaded class or <tt>null</tt> if it could not be loaded.
+     **/
+    public static Class loadClassUsingClass(Class clazz, String name)
+    {
+        Class loadedClass = null;
+
+        while (clazz != null)
+        {
+            // Get the class loader of the current class object.
+            ClassLoader loader = clazz.getClassLoader();
+            // A null class loader represents the system class loader.
+            loader = (loader == null) ? ClassLoader.getSystemClassLoader()
+                    : loader;
+            try
+            {
+                return loader.loadClass(name);
+            }
+            catch (ClassNotFoundException ex)
+            {
+                // Ignore and try interface class loaders.
+            }
+
+            // Try to see if we can load the class from
+            // one of the class's implemented interface
+            // class loaders.
+            Class[] ifcs = clazz.getInterfaces();
+            for (int i = 0; i < ifcs.length; i++)
+            {
+                loadedClass = loadClassUsingClass(ifcs[i], name);
+                if (loadedClass != null)
+                {
+                    return loadedClass;
+                }
+            }
+
+            // Try to see if we can load the class from
+            // the super class class loader.
+            clazz = clazz.getSuperclass();
+        }
+
+        return null;
+    }
+
+    /**
+     * This method determines if the requesting bundle is able to cast the
+     * specified service reference based on class visibility rules of the
+     * underlying modules.
+     * 
+     * @param requester
+     *            The bundle requesting the service.
+     * @param ref
+     *            The service in question.
+     * @return <tt>true</tt> if the requesting bundle is able to case the
+     *         service object to a known type.
+     **/
+    public static boolean isServiceAssignable(Bundle requester,
+            ServiceReference ref)
+    {
+        // Boolean flag.
+        boolean allow = true;
+        // Get the service's objectClass property.
+        String[] objectClass = (String[]) ref
+                .getProperty(Constants.OBJECTCLASS);
+
+        // The the service reference is not assignable when the requesting
+        // bundle is wired to a different version of the service object.
+        // NOTE: We are pessimistic here, if any class in the service's
+        // objectClass is not usable by the requesting bundle, then we
+        // disallow the service reference.
+        for (int classIdx = 0; (allow) && (classIdx < objectClass.length); classIdx++)
+        {
+            if (!ref.isAssignableTo(requester, objectClass[classIdx]))
+            {
+                allow = false;
+            }
+        }
+        return allow;
+    }
+
+    private static final byte encTab[] = { 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
+            0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51,
+            0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62,
+            0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d,
+            0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+            0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
+            0x39, 0x2b, 0x2f };
+
+    private static final byte decTab[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+            62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
+            -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
+            14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+            42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 };
+
+    public static String base64Encode(String s) throws IOException
+    {
+        return encode(s.getBytes(), 0);
+    }
+
+    /**
+     * Encode a raw byte array to a Base64 String.
+     * 
+     * @param in
+     *            Byte array to encode.
+     * @param len
+     *            Length of Base64 lines. 0 means no line breaks.
+     **/
+    public static String encode(byte[] in, int len) throws IOException
+    {
+        ByteArrayOutputStream baos = null;
+        ByteArrayInputStream bais = null;
+        try
+        {
+            baos = new ByteArrayOutputStream();
+            bais = new ByteArrayInputStream(in);
+            encode(bais, baos, len);
+            // ASCII byte array to String
+            return (new String(baos.toByteArray()));
+        }
+        finally
+        {
+            if (baos != null)
+            {
+                baos.close();
+            }
+            if (bais != null)
+            {
+                bais.close();
+            }
+        }
+    }
+
+    public static void encode(InputStream in, OutputStream out, int len)
+            throws IOException
+    {
+
+        // Check that length is a multiple of 4 bytes
+        if (len % 4 != 0)
+        {
+            throw new IllegalArgumentException("Length must be a multiple of 4");
+        }
+
+        // Read input stream until end of file
+        int bits = 0;
+        int nbits = 0;
+        int nbytes = 0;
+        int b;
+
+        while ((b = in.read()) != -1)
+        {
+            bits = (bits << 8) | b;
+            nbits += 8;
+            while (nbits >= 6)
+            {
+                nbits -= 6;
+                out.write(encTab[0x3f & (bits >> nbits)]);
+                nbytes++;
+                // New line
+                if (len != 0 && nbytes >= len)
+                {
+                    out.write(0x0d);
+                    out.write(0x0a);
+                    nbytes -= len;
+                }
+            }
+        }
+
+        switch (nbits)
+        {
+        case 2:
+            out.write(encTab[0x3f & (bits << 4)]);
+            out.write(0x3d); // 0x3d = '='
+            out.write(0x3d);
+            break;
+        case 4:
+            out.write(encTab[0x3f & (bits << 2)]);
+            out.write(0x3d);
+            break;
+        }
+
+        if (len != 0)
+        {
+            if (nbytes != 0)
+            {
+                out.write(0x0d);
+                out.write(0x0a);
+            }
+            out.write(0x0d);
+            out.write(0x0a);
+        }
+    }
+
+    private static final String DELIM_START = "${";
+    private static final String DELIM_STOP = "}";
+
+    /**
+     * <p>
+     * This method performs property variable substitution on the specified
+     * value. If the specified value contains the syntax
+     * <tt>${&lt;prop-name&gt;}</tt>, where <tt>&lt;prop-name&gt;</tt> refers to
+     * either a configuration property or a system property, then the
+     * corresponding property value is substituted for the variable placeholder.
+     * Multiple variable placeholders may exist in the specified value as well
+     * as nested variable placeholders, which are substituted from inner most to
+     * outer most. Configuration properties override system properties.
+     * </p>
+     * 
+     * @param val
+     *            The string on which to perform property substitution.
+     * @param currentKey
+     *            The key of the property being evaluated used to detect cycles.
+     * @param cycleMap
+     *            Map of variable references used to detect nested cycles.
+     * @param configProps
+     *            Set of configuration properties.
+     * @return The value of the specified string after system property
+     *         substitution.
+     * @throws IllegalArgumentException
+     *             If there was a syntax error in the property placeholder
+     *             syntax or a recursive variable reference.
+     **/
+    public static String substVars(String val, String currentKey, Map cycleMap,
+            Properties configProps) throws IllegalArgumentException
+    {
+        // If there is currently no cycle map, then create
+        // one for detecting cycles for this invocation.
+        if (cycleMap == null)
+        {
+            cycleMap = new HashMap();
+        }
+
+        // Put the current key in the cycle map.
+        cycleMap.put(currentKey, currentKey);
+
+        // Assume we have a value that is something like:
+        // "leading ${foo.${bar}} middle ${baz} trailing"
+
+        // Find the first ending '}' variable delimiter, which
+        // will correspond to the first deepest nested variable
+        // placeholder.
+        int stopDelim = -1;
+        int startDelim = -1;
+
+        do
+        {
+            stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
+            // If there is no stopping delimiter, then just return
+            // the value since there is no variable declared.
+            if (stopDelim < 0)
+            {
+                return val;
+            }
+            // Try to find the matching start delimiter by
+            // looping until we find a start delimiter that is
+            // greater than the stop delimiter we have found.
+            startDelim = val.indexOf(DELIM_START);
+            // If there is no starting delimiter, then just return
+            // the value since there is no variable declared.
+            if (startDelim < 0)
+            {
+                return val;
+            }
+            while (stopDelim >= 0)
+            {
+                int idx = val.indexOf(DELIM_START,
+                        startDelim + DELIM_START.length());
+                if ((idx < 0) || (idx > stopDelim))
+                {
+                    break;
+                }
+                else if (idx < stopDelim)
+                {
+                    startDelim = idx;
+                }
+            }
+        }
+        while ((startDelim > stopDelim) && (stopDelim >= 0));
+
+        // At this point, we have found a variable placeholder so
+        // we must perform a variable substitution on it.
+        // Using the start and stop delimiter indices, extract
+        // the first, deepest nested variable placeholder.
+        String variable = val.substring(startDelim + DELIM_START.length(),
+                stopDelim);
+
+        // Verify that this is not a recursive variable reference.
+        if (cycleMap.get(variable) != null)
+        {
+            throw new IllegalArgumentException("recursive variable reference: "
+                    + variable);
+        }
+
+        // Get the value of the deepest nested variable placeholder.
+        // Try to configuration properties first.
+        String substValue = (configProps != null) ? configProps.getProperty(
+                variable, null) : null;
+        if (substValue == null)
+        {
+            // Ignore unknown property values.
+            substValue = System.getProperty(variable, "");
+        }
+
+        // Remove the found variable from the cycle map, since
+        // it may appear more than once in the value and we don't
+        // want such situations to appear as a recursive reference.
+        cycleMap.remove(variable);
+
+        // Append the leading characters, the substituted value of
+        // the variable, and the trailing characters to get the new
+        // value.
+        val = val.substring(0, startDelim) + substValue
+                + val.substring(stopDelim + DELIM_STOP.length(), val.length());
+
+        // Now perform substitution again, since there could still
+        // be substitutions to make.
+        val = substVars(val, currentKey, cycleMap, configProps);
+
+        // Return the value.
+        return val;
+    }
+
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/VersionRange.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/VersionRange.java
new file mode 100644
index 0000000..345e65c
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/felix/framework/util/VersionRange.java
@@ -0,0 +1,163 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.felix.framework.util;
+
+import org.osgi.framework.Version;
+
+public class VersionRange
+{
+    private final Version m_floor;
+    private final boolean m_isFloorInclusive;
+    private final Version m_ceiling;
+    private final boolean m_isCeilingInclusive;
+    public static final VersionRange infiniteRange = new VersionRange(
+            Version.emptyVersion, true, null, true);
+
+    public VersionRange(Version low, boolean isLowInclusive, Version high,
+            boolean isHighInclusive)
+    {
+        m_floor = low;
+        m_isFloorInclusive = isLowInclusive;
+        m_ceiling = high;
+        m_isCeilingInclusive = isHighInclusive;
+    }
+
+    public Version getFloor()
+    {
+        return m_floor;
+    }
+
+    public boolean isFloorInclusive()
+    {
+        return m_isFloorInclusive;
+    }
+
+    public Version getCeiling()
+    {
+        return m_ceiling;
+    }
+
+    public boolean isCeilingInclusive()
+    {
+        return m_isCeilingInclusive;
+    }
+
+    public boolean isInRange(Version version)
+    {
+        // We might not have an upper end to the range.
+        if (m_ceiling == null)
+        {
+            return (version.compareTo(m_floor) >= 0);
+        }
+        else if (isFloorInclusive() && isCeilingInclusive())
+        {
+            return (version.compareTo(m_floor) >= 0)
+                    && (version.compareTo(m_ceiling) <= 0);
+        }
+        else if (isCeilingInclusive())
+        {
+            return (version.compareTo(m_floor) > 0)
+                    && (version.compareTo(m_ceiling) <= 0);
+        }
+        else if (isFloorInclusive())
+        {
+            return (version.compareTo(m_floor) >= 0)
+                    && (version.compareTo(m_ceiling) < 0);
+        }
+        return (version.compareTo(m_floor) > 0)
+                && (version.compareTo(m_ceiling) < 0);
+    }
+
+    public static VersionRange parse(String range)
+    {
+        // Check if the version is an interval.
+        if (range.indexOf(',') >= 0)
+        {
+            String s = range.substring(1, range.length() - 1);
+            String vlo = s.substring(0, s.indexOf(',')).trim();
+            String vhi = s.substring(s.indexOf(',') + 1, s.length()).trim();
+            return new VersionRange(new Version(vlo), (range.charAt(0) == '['),
+                    new Version(vhi), (range.charAt(range.length() - 1) == ']'));
+        }
+        else
+        {
+            return new VersionRange(new Version(range), true, null, false);
+        }
+    }
+
+    public boolean equals(Object obj)
+    {
+        if (obj == null)
+        {
+            return false;
+        }
+        if (getClass() != obj.getClass())
+        {
+            return false;
+        }
+        final VersionRange other = (VersionRange) obj;
+        if (m_floor != other.m_floor
+                && (m_floor == null || !m_floor.equals(other.m_floor)))
+        {
+            return false;
+        }
+        if (m_isFloorInclusive != other.m_isFloorInclusive)
+        {
+            return false;
+        }
+        if (m_ceiling != other.m_ceiling
+                && (m_ceiling == null || !m_ceiling.equals(other.m_ceiling)))
+        {
+            return false;
+        }
+        if (m_isCeilingInclusive != other.m_isCeilingInclusive)
+        {
+            return false;
+        }
+        return true;
+    }
+
+    public int hashCode()
+    {
+        int hash = 5;
+        hash = 97 * hash + (m_floor != null ? m_floor.hashCode() : 0);
+        hash = 97 * hash + (m_isFloorInclusive ? 1 : 0);
+        hash = 97 * hash + (m_ceiling != null ? m_ceiling.hashCode() : 0);
+        hash = 97 * hash + (m_isCeilingInclusive ? 1 : 0);
+        return hash;
+    }
+
+    public String toString()
+    {
+        if (m_ceiling != null)
+        {
+            StringBuffer sb = new StringBuffer();
+            sb.append(m_isFloorInclusive ? '[' : '(');
+            sb.append(m_floor.toString());
+            sb.append(',');
+            sb.append(m_ceiling.toString());
+            sb.append(m_isCeilingInclusive ? ']' : ')');
+            return sb.toString();
+        }
+        else
+        {
+            return m_floor.toString();
+        }
+    }
+}
\ No newline at end of file
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/BundleDescriptor.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/BundleDescriptor.java
new file mode 100644
index 0000000..74de0ac
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/BundleDescriptor.java
@@ -0,0 +1,57 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.launch;
+
+import java.net.URL;
+import java.util.Map;
+
+public class BundleDescriptor
+{
+    private final ClassLoader m_loader;
+    private final URL m_url;
+    private final Map<String, String> m_headers;
+
+    public BundleDescriptor(ClassLoader loader, URL url,
+            Map<String, String> headers)
+    {
+        m_loader = loader;
+        m_url = url;
+        m_headers = headers;
+    }
+
+    public ClassLoader getClassLoader()
+    {
+        return m_loader;
+    }
+
+    public URL getUrl()
+    {
+        return m_url;
+    }
+
+    public String toString()
+    {
+        return m_url.toExternalForm();
+    }
+
+    public Map<String, String> getHeaders()
+    {
+        return m_headers;
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/ClasspathScanner.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/ClasspathScanner.java
new file mode 100644
index 0000000..03ecc7c
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/ClasspathScanner.java
@@ -0,0 +1,182 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.launch;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+
+import de.kalpatec.pojosr.framework.felix.framework.util.MapToDictionary;
+
+public class ClasspathScanner
+{
+    public List<BundleDescriptor> scanForBundles() throws Exception
+    {
+        return scanForBundles(null, null);
+    }
+
+    public List<BundleDescriptor> scanForBundles(ClassLoader loader) throws Exception
+    {
+        return scanForBundles(null, loader);
+    }
+
+    public List<BundleDescriptor> scanForBundles(String filterString)
+            throws Exception
+    {
+        return scanForBundles(filterString, null);
+    }
+
+    public List<BundleDescriptor> scanForBundles(String filterString, ClassLoader loader)
+        throws Exception
+    {
+        Filter filter = (filterString != null) ? FrameworkUtil
+                .createFilter(filterString) : null;
+
+        loader = (loader != null) ? loader : getClass().getClassLoader();
+
+        List<BundleDescriptor> bundles = new ArrayList<BundleDescriptor>();
+		byte[] bytes = new byte[1024 * 1024 * 2];
+        for (Enumeration<URL> e = loader.getResources(
+                "META-INF/MANIFEST.MF"); e.hasMoreElements();)
+        {
+            URL manifestURL = e.nextElement();
+            InputStream input = null;
+            try
+            {
+                input = manifestURL.openStream();
+				int size = 0;
+				for (int i = input.read(bytes); i != -1; i = input.read(bytes, size, bytes.length - size)) {
+					size += i;
+					if (size == bytes.length) {
+					     byte[] tmp = new byte[size * 2];
+						 System.arraycopy(bytes, 0, tmp, 0, bytes.length);
+						 bytes = tmp;
+					}
+				}
+
+				// Now parse the main attributes. The idea is to do that
+				// without creating new byte arrays. Therefore, we read through
+				// the manifest bytes inside the bytes array and write them back into
+				// the same array unless we don't need them (e.g., \r\n and \n are skipped).
+				// That allows us to create the strings from the bytes array without the skipped
+				// chars. We stopp as soon as we see a blankline as that denotes that the main
+				//attributes part is finished.
+				String key = null;
+				int last = 0;
+				int current = 0;
+
+                Map<String, String> headers = new HashMap<String, String>();
+				for (int i = 0; i < size; i++)
+				{
+					// skip \r and \n if it is follows by another \n
+					// (we catch the blank line case in the next iteration)
+					if (bytes[i] == '\r')
+					{
+						if ((i + 1 < size) && (bytes[i + 1] == '\n'))
+						{
+							continue;
+						}
+					}
+					if (bytes[i] == '\n')
+					{
+						if ((i + 1 < size) && (bytes[i + 1] == ' '))
+						{
+							i++;
+							continue;
+						}
+					}
+					// If we don't have a key yet and see the first : we parse it as the key
+					// and skip the :<blank> that follows it.
+					if ((key == null) && (bytes[i] == ':'))
+					{
+						key = new String(bytes, last, (current - last), "UTF-8");
+						if ((i + 1 < size) && (bytes[i + 1] == ' '))
+						{
+							last = current + 1;
+							continue;
+						}
+						else
+						{
+							throw new Exception(
+								"Manifest error: Missing space separator - " + key);
+						}
+					}
+					// if we are at the end of a line
+					if (bytes[i] == '\n')
+					{
+						// and it is a blank line stop parsing (main attributes are done)
+						if ((last == current) && (key == null))
+						{
+							break;
+						}
+						// Otherwise, parse the value and add it to the map (we throw an
+						// exception if we don't have a key or the key already exist.
+						String value = new String(bytes, last, (current - last), "UTF-8");
+						if (key == null)
+						{
+							throw new Exception("Manifst error: Missing attribute name - " + value);
+						}
+						else if (headers.put(key, value) != null)
+						{
+							throw new Exception("Manifst error: Duplicate attribute name - " + key);
+						}
+						last = current;
+						key = null;
+					}
+					else
+					{
+						// write back the byte if it needs to be included in the key or the value.
+						bytes[current++] = bytes[i];
+					}
+				}
+                if ((filter == null)
+                        || filter.match(new MapToDictionary(headers)))
+                {
+                    bundles.add(new BundleDescriptor(loader, getParentURL(manifestURL),
+                            headers));
+                }
+            }
+            finally
+            {
+                if (input != null)
+                {
+                    input.close();
+                }
+            }
+        }
+        return bundles;
+    }
+
+    private URL getParentURL(URL url) throws Exception
+    {
+        String externalForm = url.toExternalForm();
+        return new URL(externalForm.substring(0, externalForm.length()
+                - "META-INF/MANIFEST.MF".length()));
+    }
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/PojoServiceRegistry.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/PojoServiceRegistry.java
new file mode 100644
index 0000000..58447d0
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/PojoServiceRegistry.java
@@ -0,0 +1,56 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.launch;
+
+import java.util.Dictionary;
+import java.util.List;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+public interface PojoServiceRegistry
+{
+    public BundleContext getBundleContext();
+	
+	public void startBundles(List<BundleDescriptor> bundles) throws Exception;
+
+    public void addServiceListener(ServiceListener listener, String filter)
+            throws InvalidSyntaxException;
+
+    public void addServiceListener(ServiceListener listener);
+
+    public void removeServiceListener(ServiceListener listener);
+
+    public ServiceRegistration registerService(String[] clazzes,
+            Object service, @SuppressWarnings("rawtypes") Dictionary properties);
+
+    public ServiceRegistration registerService(String clazz, Object service,
+            @SuppressWarnings("rawtypes") Dictionary properties);
+
+    public ServiceReference[] getServiceReferences(String clazz, String filter)
+            throws InvalidSyntaxException;
+
+    public ServiceReference getServiceReference(String clazz);
+
+    public Object getService(ServiceReference reference);
+
+    public boolean ungetService(ServiceReference reference);
+}
diff --git a/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/PojoServiceRegistryFactory.java b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/PojoServiceRegistryFactory.java
new file mode 100644
index 0000000..9c611ae
--- /dev/null
+++ b/pojosr/src/main/java/de/kalpatec/pojosr/framework/launch/PojoServiceRegistryFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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 de.kalpatec.pojosr.framework.launch;
+
+import java.util.Map;
+
+public interface PojoServiceRegistryFactory 
+{
+    public static final String BUNDLE_DESCRIPTORS = PojoServiceRegistry.class
+            .getName().toLowerCase() + ".bundles";
+
+    public PojoServiceRegistry newPojoServiceRegistry(Map configuration)
+            throws Exception;
+}
diff --git a/pojosr/src/main/resources/META-INF/services/de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory b/pojosr/src/main/resources/META-INF/services/de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory
new file mode 100644
index 0000000..180be46
--- /dev/null
+++ b/pojosr/src/main/resources/META-INF/services/de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory
@@ -0,0 +1 @@
+de.kalpatec.pojosr.framework.PojoServiceRegistryFactoryImpl
\ No newline at end of file
diff --git a/pojosr/src/main/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory b/pojosr/src/main/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory
new file mode 100644
index 0000000..180be46
--- /dev/null
+++ b/pojosr/src/main/resources/META-INF/services/org.osgi.framework.launch.FrameworkFactory
@@ -0,0 +1 @@
+de.kalpatec.pojosr.framework.PojoServiceRegistryFactoryImpl
\ No newline at end of file