[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><bundle-id>.<revision></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><bundle-id>.<revision></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>${<prop-name>}</tt>, where <tt><prop-name></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