Added the initial version of DeploymentAdmin as donated in FELIX-452 (IP clearance posted to dev-felix on january 15th).
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@616813 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/deploymentadmin/LICENSE b/deploymentadmin/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/deploymentadmin/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/deploymentadmin/NOTICE b/deploymentadmin/NOTICE
new file mode 100644
index 0000000..fb681e0
--- /dev/null
+++ b/deploymentadmin/NOTICE
@@ -0,0 +1,10 @@
+Apache Felix Framework
+Copyright 2006 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+This product includes software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright 2006 The OSGi Alliance.
+Licensed under the Apache License 2.0.
diff --git a/deploymentadmin/pom.xml b/deploymentadmin/pom.xml
new file mode 100644
index 0000000..b9b5123
--- /dev/null
+++ b/deploymentadmin/pom.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <relativePath>../pom/pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>bundle</packaging>
+ <name>Apache Felix Deployment Admin</name>
+ <version>1.0.0-SNAPSHOT</version>
+ <artifactId>org.apache.felix.deploymentadmin</artifactId>
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <version>0.9.0-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>1.1.0-SNAPSHOT</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>org.apache.felix.deploymentadmin</Bundle-SymbolicName>
+ <Bundle-Activator>org.apache.felix.deploymentadmin.Activator</Bundle-Activator>
+ <Bundle-Name>Apache Felix Deployment Admin</Bundle-Name>
+ <Bundle-Description>A bundle that implements the Deployment Admin.</Bundle-Description>
+ <Bundle-Vendor>Apache Software Foundation</Bundle-Vendor>
+ <Private-Package>org.apache.felix.deploymentadmin.spi</Private-Package>
+ <Export-Package>org.apache.felix.deploymentadmin,org.osgi.service.deploymentadmin</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
new file mode 100644
index 0000000..be871e4
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractDeploymentPackage.java
@@ -0,0 +1,313 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Manifest;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+import org.osgi.service.deploymentadmin.BundleInfo;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+
+/**
+ * Base class for various types of deployment packages. Indifferent in regard to how the
+ * deployment package data is obtained, this should be handled by extending classes.
+ */
+public abstract class AbstractDeploymentPackage implements DeploymentPackage {
+
+ private final BundleContext m_bundleContext;
+ private final DeploymentPackageManifest m_manifest;
+ private final Map m_nameToBundleInfo = new HashMap();
+ private final Map m_pathToEntry = new HashMap();
+ private final BundleInfoImpl[] m_bundleInfos;
+ private final ResourceInfoImpl[] m_resourceInfos;
+ private final String[] m_resourcePaths;
+ private final boolean m_isFixPackage;
+ protected static final AbstractDeploymentPackage emptyPackage = new AbstractDeploymentPackage() {
+ public String getHeader(String header) {
+ if (Constants.DEPLOYMENTPACKAGE_SYMBOLICMAME.equals(header)) { return ""; }
+ else if (Constants.DEPLOYMENTPACKAGE_VERSION.equals(header)) { return Version.emptyVersion.toString(); }
+ else { return null; }
+ }
+ public Bundle getBundle(String symbolicName) { return null; }
+ public BundleInfo[] getBundleInfos() { return new BundleInfoImpl[] {}; }
+ public BundleInfoImpl[] getBundleInfoImpls() { return new BundleInfoImpl[] {}; }
+ public String getName() { return ""; }
+ public String getResourceHeader(String resource, String header) { return null; }
+ public ServiceReference getResourceProcessor(String resource) { return null; }
+ public String[] getResources() { return new String[] {}; }
+ public Version getVersion() { return Version.emptyVersion; }
+ public boolean isStale() { return true; }
+ public void uninstall() throws DeploymentException { throw new IllegalStateException("Can not uninstall stale DeploymentPackage"); }
+ public boolean uninstallForced() throws DeploymentException { throw new IllegalStateException("Can not uninstall stale DeploymentPackage"); }
+ public InputStream getBundleStream(String symbolicName) throws IOException { return null; }
+ public BundleInfoImpl[] getOrderedBundleInfos() { return new BundleInfoImpl[] {}; }
+ public ResourceInfoImpl[] getOrderedResourceInfos() { return new ResourceInfoImpl[] {}; }
+ public InputStream getCurrentEntryStream() { throw new UnsupportedOperationException(); }
+ public AbstractInfo getNextEntry() throws IOException { throw new UnsupportedOperationException(); }
+ };
+
+ /* Constructor only for use by the emptyPackage static variable */
+ private AbstractDeploymentPackage() {
+ m_bundleContext = null;
+ m_manifest = null;
+ m_bundleInfos = null;
+ m_resourceInfos = null;
+ m_resourcePaths = null;
+ m_isFixPackage = false;
+ }
+
+ /**
+ * Creates an instance of this class.
+ *
+ * @param manifest The manifest of the deployment package.
+ * @param bundleContext The bundle context.
+ * @throws DeploymentException Thrown if the specified manifest does not describe a valid deployment package.
+ */
+ public AbstractDeploymentPackage(Manifest manifest, BundleContext bundleContext) throws DeploymentException {
+ m_manifest = new DeploymentPackageManifest(manifest);
+ m_isFixPackage = m_manifest.getFixPackage() != null;
+ m_bundleContext = bundleContext;
+ m_bundleInfos = (BundleInfoImpl[]) m_manifest.getBundleInfos().toArray(new BundleInfoImpl[0]);
+ for(int i = 0; i < m_bundleInfos.length; i++) {
+ m_nameToBundleInfo.put(m_bundleInfos[i].getSymbolicName(), m_bundleInfos[i]);
+ m_pathToEntry.put(m_bundleInfos[i].getPath(), m_bundleInfos[i]);
+ }
+ m_resourceInfos = (ResourceInfoImpl[]) m_manifest.getResourceInfos().toArray(new ResourceInfoImpl[0]);
+ for (int i = 0; i < m_resourceInfos.length; i++) {
+ m_pathToEntry.put(m_resourceInfos[i].getPath(), m_resourceInfos[i]);
+ }
+ m_resourcePaths = (String[]) m_pathToEntry.keySet().toArray(new String[m_pathToEntry.size()]);
+ }
+
+ public Bundle getBundle(String symbolicName) {
+ if (isStale()) {
+ throw new IllegalStateException("Can not get bundle from stale deployment package.");
+ }
+ if (m_nameToBundleInfo.containsKey(symbolicName)) {
+ Bundle[] bundles = m_bundleContext.getBundles();
+ for (int i = 0; i < bundles.length; i++) {
+ if (bundles[i].getSymbolicName().equals(symbolicName)) {
+ return bundles[i];
+ }
+ }
+ }
+ return null;
+ }
+
+ public BundleInfo[] getBundleInfos() {
+ return (BundleInfo[]) m_bundleInfos.clone();
+ }
+
+ /**
+ * Returns the bundles of this deployment package as an array of <code>BundleInfoImpl</code> objects.
+ *
+ * @return Array containing <code>BundleInfoImpl</code> objects for each bundle this deployment package.
+ */
+ public BundleInfoImpl[] getBundleInfoImpls() {
+ return (BundleInfoImpl[]) m_bundleInfos.clone();
+ }
+
+ /**
+ * Returns the processed resources of this deployment package as an array of <code>ResourceInfoImpl</code> objects.
+ *
+ * @return Array containing <code>ResourceInfoImpl</code> objects for each processed resource of this deployment package.
+ */
+ public ResourceInfoImpl[] getResourceInfos() {
+ return (ResourceInfoImpl[]) m_resourceInfos.clone();
+ }
+
+ /**
+ * Determines whether this deployment package is a fix package.
+ *
+ * @return True if this deployment package is a fix package, false otherwise.
+ */
+ public boolean isFixPackage() {
+ return m_isFixPackage;
+ }
+
+ public String getHeader(String header) {
+ return m_manifest.getHeader(header);
+ }
+
+ public String getName() {
+ return m_manifest.getSymbolicName();
+ }
+
+ public String getResourceHeader(String resource, String header) {
+ AbstractInfo info = (AbstractInfo) m_pathToEntry.get(resource);
+ if (info != null) {
+ return info.getHeader(header);
+ }
+ return null;
+ }
+
+ public ServiceReference getResourceProcessor(String resource) {
+ if (isStale()) {
+ throw new IllegalStateException("Can not get bundle from stale deployment package.");
+ }
+ AbstractInfo info = (AbstractInfo) m_pathToEntry.get(resource);
+ if (info instanceof ResourceInfoImpl) {
+ String processor = ((ResourceInfoImpl) info).getResourceProcessor();
+ if (processor != null) {
+ try {
+ ServiceReference[] services = m_bundleContext.getServiceReferences(ResourceProcessor.class.getName(), "(" + org.osgi.framework.Constants.SERVICE_PID + "=" + processor + ")");
+ if (services.length > 0) {
+ return services[0];
+ }
+ }
+ catch (InvalidSyntaxException e) {
+ // TODO: log this
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ public String[] getResources() {
+ return (String[]) m_resourcePaths.clone();
+ }
+
+ public Version getVersion() {
+ return m_manifest.getVersion();
+ }
+
+ /**
+ * If this deployment package is a fix package this method determines the version range this deployment package can be applied to.
+ *
+ * @return <code>VersionRange</code> the fix package can be applied to or <code>null</code> if it is not a fix package.
+ */
+ public VersionRange getVersionRange() {
+ return m_manifest.getFixPackage();
+ }
+
+ public boolean isStale() {
+ return false;
+ }
+
+ public void uninstall() throws DeploymentException {
+ throw new IllegalStateException("Not implemented");
+ }
+
+ public boolean uninstallForced() throws DeploymentException {
+ throw new IllegalStateException("Not implemented");
+ }
+
+ /**
+ * Determines the bundles of this deployment package in the order in which they were originally received.
+ *
+ * @return Array containing <code>BundleInfoImpl</code> objects of the bundles in this deployment package, ordered in the way they appeared when the deployment package was first received.
+ */
+ public abstract BundleInfoImpl[] getOrderedBundleInfos();
+
+ /**
+ * Determines the resources of this deployment package in the order in which they were originally received.
+ *
+ * @return Array containing <code>ResourceInfoImpl</code> objects of all processed resources in this deployment package, ordered in the way they appeared when the deployment package was first received
+ */
+ public abstract ResourceInfoImpl[] getOrderedResourceInfos();
+
+ /**
+ * Determines the info about a processed resource based on it's path/resource-id.
+ *
+ * @param path String containing a (processed) resource path
+ * @return <code>ResourceInfoImpl</code> for the resource identified by the specified path or null if the path is unknown or does not describe a processed resource
+ */
+ public ResourceInfoImpl getResourceInfoByPath(String path) {
+ AbstractInfo info = (AbstractInfo) m_pathToEntry.get(path);
+ if (info instanceof ResourceInfoImpl) {
+ return (ResourceInfoImpl) info;
+ }
+ return null;
+ }
+
+ /**
+ * Determines the info about either a bundle or processed resource based on it's path/resource-id.
+ *
+ * @param path String containing a resource path (either bundle or processed resource)
+ * @return <code>AbstractInfoImpl</code> for the resource identified by the specified path or null if the path is unknown
+ */
+ protected AbstractInfo getAbstractInfoByPath(String path) {
+ return (AbstractInfo) m_pathToEntry.get(path);
+ }
+
+ /**
+ * Determines the info about a bundle based on it's path/resource-id.
+ *
+ * @param path String containing a bundle path
+ * @return <code>BundleInfoImpl</code> for the bundle resource identified by the specified path or null if the path is unknown or does not describe a bundle resource
+ */
+ public BundleInfoImpl getBundleInfoByPath(String path) {
+ AbstractInfo info = (AbstractInfo) m_pathToEntry.get(path);
+ if (info instanceof BundleInfoImpl) {
+ return (BundleInfoImpl) info;
+ }
+ return null;
+ }
+
+ /**
+ * Determines the info about a bundle resource based on the bundle symbolic name.
+ *
+ * @param symbolicName String containing a bundle symbolic name
+ * @return <code>BundleInfoImpl</code> for the bundle identified by the specified symbolic name or null if the symbolic name is unknown
+ */
+ public BundleInfoImpl getBundleInfoByName(String symbolicName) {
+ return (BundleInfoImpl) m_nameToBundleInfo.get(symbolicName);
+ }
+
+ /**
+ * Determines the data stream of a bundle resource based on the bundle symbolic name
+ *
+ * @param symbolicName Bundle symbolic name
+ * @return Stream to the bundle identified by the specified symbolic name or null if no such bundle exists in this deployment package.
+ * @throws IOException If the bundle can not be properly offered as an inputstream
+ */
+ public abstract InputStream getBundleStream(String symbolicName) throws IOException;
+
+ /**
+ * Determines the next resource entry in this deployment package based on the order in which the resources appeared when the package was originally received.
+ *
+ * @return <code>AbstractInfo</code> describing the next resource entry (as determined by the order in which the deployment package was received originally) or null if there is no next entry
+ * @throws IOException if the next entry can not be properly determined
+ */
+ public abstract AbstractInfo getNextEntry() throws IOException;
+
+ /**
+ * Determines the data stream to the current entry of this deployment package, use this together with the <code>getNextEntry</code> method.
+ *
+ * @return Stream to the current resource in the deployment package (as determined by the order in which the deployment package was received originally) or null if there is no entry
+ */
+ public abstract InputStream getCurrentEntryStream();
+
+ public Bundle getBundle(Object symbolicName) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+}
+
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractInfo.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractInfo.java
new file mode 100644
index 0000000..e950bb7
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/AbstractInfo.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.util.BitSet;
+import java.util.jar.Attributes;
+
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * Objects of this class represent the meta data for a resource from a deployment package, this
+ * can be either bundle resources or processed resources.
+ */
+public class AbstractInfo {
+
+ private static final BitSet VALID_RESOURCE_PATH_CHARS;
+ static
+ {
+ VALID_RESOURCE_PATH_CHARS = new BitSet();
+ for ( int i = 'a'; i <= 'z'; i++ )
+ {
+ VALID_RESOURCE_PATH_CHARS.set( i );
+ }
+ for ( int i = 'A'; i <= 'Z'; i++ )
+ {
+ VALID_RESOURCE_PATH_CHARS.set( i );
+ }
+ for ( int i = '0'; i <= '9'; i++ )
+ {
+ VALID_RESOURCE_PATH_CHARS.set( i );
+ }
+ VALID_RESOURCE_PATH_CHARS.set( '.' );
+ VALID_RESOURCE_PATH_CHARS.set( '-' );
+ VALID_RESOURCE_PATH_CHARS.set( '_' );
+ VALID_RESOURCE_PATH_CHARS.set( '/' );
+ }
+
+ private final String m_path;
+ private final Attributes m_attributes;
+ private final boolean m_missing;
+
+ /**
+ * Create an instance
+ *
+ * @param path Resource-id aka path of the resource
+ * @param attributes Attributes containing the meta data of the resource
+ * @throws DeploymentException If the specified attributes do not match the correct syntax for a deployment package resource.
+ */
+ public AbstractInfo(String path, Attributes attributes) throws DeploymentException {
+ verifyEntryName(path);
+ m_path = path;
+ m_attributes = attributes;
+ m_missing = parseBooleanHeader(attributes, Constants.DEPLOYMENTPACKAGE_MISSING);
+ }
+
+ /**
+ * @return The path of the resource
+ */
+ public String getPath() {
+ return m_path;
+ }
+
+ /**
+ * Return the value of a header for this resource
+ * @param header Name of the header
+ * @return Value of the header specified by the given header name
+ */
+ public String getHeader(String header) {
+ return m_attributes.getValue(header);
+ }
+
+ private void verifyEntryName(String name) throws DeploymentException {
+ byte[] bytes = name.getBytes();
+ boolean delimiterSeen = false;
+ for(int j = 0; j < bytes.length; j++) {
+ if(!VALID_RESOURCE_PATH_CHARS.get(bytes[j])) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER, "Resource ID '" + name +"' contains invalid character(s)");
+ }
+ if (bytes[j] == '/') {
+ if (delimiterSeen) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER, "Resource ID '" + name +"' contains multiple consequetive path seperators");
+ } else {
+ delimiterSeen = true;
+ }
+ } else {
+ delimiterSeen = false;
+ }
+ }
+ }
+
+ /**
+ * Determine if a resource is missing or not
+ * @return True if the actual data for this resource is not present, false otherwise
+ */
+ public boolean isMissing() {
+ return m_missing;
+ }
+
+ /**
+ * Parses a header that is allowed to have only boolean values.
+ *
+ * @param attributes Set of attributes containing the header
+ * @param header The header to verify
+ * @return true if the value of the header was "true", false if the value was "false"
+ * @throws DeploymentException if the value was not "true" or "false"
+ */
+ protected boolean parseBooleanHeader(Attributes attributes, String header) throws DeploymentException {
+ String value = attributes.getValue(header);
+ if (value != null) {
+ if ("true".equals(value)) {
+ return true;
+ } else if ("false".equals(value)){
+ return false;
+ } else {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER, "Invalid '" + header + "' header for manifest " +
+ "entry '" + getPath() + "' header, should be either 'true' or 'false' or not present");
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Activator.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Activator.java
new file mode 100644
index 0000000..81b91d6
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Activator.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import org.apache.felix.dependencymanager.DependencyActivatorBase;
+import org.apache.felix.dependencymanager.DependencyManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.deploymentadmin.DeploymentAdmin;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.log.LogService;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+/**
+ * Bundle activator for the deployment admin bundle
+ *
+ * @author Christian van Spaandonk
+ * @version $Revision: 8712 $
+ */
+public class Activator extends DependencyActivatorBase {
+
+ public void init(BundleContext context, DependencyManager manager) throws Exception {
+ manager.add(createService()
+ .setInterface(DeploymentAdmin.class.getName(), null)
+ .setImplementation(DeploymentAdminImpl.class)
+ .add(createServiceDependency()
+ .setService(PackageAdmin.class)
+ .setRequired(true))
+ .add(createServiceDependency()
+ .setService(EventAdmin.class)
+ .setRequired(false))
+ .add(createServiceDependency()
+ .setService(LogService.class)
+ .setRequired(false)));
+ }
+
+ public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+ // do nothing
+ }
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/BundleInfoImpl.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/BundleInfoImpl.java
new file mode 100644
index 0000000..6dfc5fd
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/BundleInfoImpl.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.util.jar.Attributes;
+
+import org.osgi.framework.Version;
+import org.osgi.service.deploymentadmin.BundleInfo;
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * Implementation of the <code>BundleInfo</code> interface as defined by the OSGi mobile specification.
+ */
+public class BundleInfoImpl extends AbstractInfo implements BundleInfo {
+
+ private final Version m_version;
+ private final String m_symbolicName;
+ private final boolean m_customizer;
+
+ /**
+ * Creates an instance of this class.
+ *
+ * @param path The path / resource-id of the bundle resource.
+ * @param attributes Set of attributes describing the bundle resource.
+ * @throws DeploymentException If the specified attributes do not describe a valid bundle.
+ */
+ public BundleInfoImpl(String path, Attributes attributes) throws DeploymentException {
+ super(path, attributes);
+
+ String bundleSymbolicName = attributes.getValue(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME);
+ if (bundleSymbolicName == null) {
+ throw new DeploymentException(DeploymentException.CODE_MISSING_HEADER, "Missing '" + org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME + "' header for manifest entry '" + getPath() + "'");
+ } else if (bundleSymbolicName.trim().equals("")) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER, "Invalid '" + org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME + "' header for manifest entry '" + getPath() + "'");
+ } else {
+ m_symbolicName = bundleSymbolicName;
+ }
+
+ String version = attributes.getValue(org.osgi.framework.Constants.BUNDLE_VERSION);
+ try {
+ m_version = Version.parseVersion(version);
+ } catch (IllegalArgumentException e) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER, "Invalid '" + org.osgi.framework.Constants.BUNDLE_VERSION + "' header for manifest entry '" + getPath() + "'");
+ }
+
+ m_customizer = parseBooleanHeader(attributes, Constants.DEPLOYMENTPACKAGE_CUSTOMISER);
+ }
+
+ public String getSymbolicName() {
+ return m_symbolicName;
+ }
+
+ public Version getVersion() {
+ return m_version;
+ }
+
+ /**
+ * Determine whether this bundle resource is a customizer bundle.
+ *
+ * @return True if the bundle is a customizer bundle, false otherwise.
+ */
+ public boolean isCustomizer() {
+ return m_customizer;
+ }
+
+ /**
+ * Verify if the specified attributes describe a bundle resource.
+ *
+ * @param attributes Attributes describing the resource
+ * @return true if the attributes describe a bundle resource, false otherwise
+ */
+ public static boolean isBundleResource(Attributes attributes) {
+ return (attributes.getValue(org.osgi.framework.Constants.BUNDLE_SYMBOLICNAME) != null);
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Constants.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Constants.java
new file mode 100644
index 0000000..e0f0b1d
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Constants.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+public interface Constants extends org.osgi.framework.Constants {
+
+ // manifest main attribute header constants
+ public static final String DEPLOYMENTPACKAGE_SYMBOLICMAME = "DeploymentPackage-SymbolicName";
+ public static final String DEPLOYMENTPACKAGE_VERSION = "DeploymentPackage-Version";
+ public static final String DEPLOYMENTPACKAGE_FIXPACK = "DeploymentPackage-FixPack";
+
+ // manifest 'name' section header constants
+ public static final String RESOURCE_PROCESSOR = "Resource-Processor";
+ public static final String DEPLOYMENTPACKAGE_MISSING = "DeploymentPackage-Missing";
+ public static final String DEPLOYMENTPACKAGE_CUSTOMISER = "DeploymentPackage-Customiser";
+
+ // event topics and properties
+ public static final String EVENTTOPIC_INSTALL = "org/osgi/service/deployment/INSTALL";
+ public static final String EVENTTOPIC_UNINSTALL = "org/osgi/service/deployment/UNINSTALL";
+ public static final String EVENTTOPIC_COMPLETE = "org/osgi/service/deployment/COMPLETE";
+ public static final String EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME = "deploymentpackage.name";
+ public static final String EVENTPROPERTY_SUCCESFULL = "succesfull";
+
+ // miscellaneous constants
+ public static final String BUNDLE_LOCATION_PREFIX = "osgi-dp:";
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
new file mode 100644
index 0000000..9f78dc3
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentAdminImpl.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.jar.JarInputStream;
+
+import org.apache.felix.deploymentadmin.spi.CommitResourceCommand;
+import org.apache.felix.deploymentadmin.spi.DeploymentSessionImpl;
+import org.apache.felix.deploymentadmin.spi.DropBundleCommand;
+import org.apache.felix.deploymentadmin.spi.DropResourceCommand;
+import org.apache.felix.deploymentadmin.spi.GetStorageAreaCommand;
+import org.apache.felix.deploymentadmin.spi.ProcessResourceCommand;
+import org.apache.felix.deploymentadmin.spi.SnapshotCommand;
+import org.apache.felix.deploymentadmin.spi.StartBundleCommand;
+import org.apache.felix.deploymentadmin.spi.StartCustomizerCommand;
+import org.apache.felix.deploymentadmin.spi.StopBundleCommand;
+import org.apache.felix.deploymentadmin.spi.UpdateCommand;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.deploymentadmin.DeploymentAdmin;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.osgi.service.log.LogService;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class DeploymentAdminImpl implements DeploymentAdmin {
+
+ public static final String PACKAGE_DIR = "packages";
+ public static final String TEMP_DIR = "temp";
+ public static final String PACKAGECONTENTS_DIR = "contents";
+ public static final String PACKAGEINDEX_FILE = "index.txt";
+ public static final String TEMP_PREFIX = "pkg";
+ public static final String TEMP_POSTFIX = "";
+
+ private static final long TIMEOUT = 10000;
+
+ private BundleContext m_context; /* will be injected by dependencymanager */
+ private PackageAdmin m_packageAdmin; /* will be injected by dependencymanager */
+ private EventAdmin m_eventAdmin; /* will be injected by dependencymanager */
+ private LogService m_log; /* will be injected by dependencymanager */
+ private DeploymentSessionImpl m_session = null;
+ private final Map m_packages = new HashMap();
+ private final List m_commandChain = new ArrayList();
+ private final Semaphore m_semaphore = new Semaphore();
+
+ /**
+ * Create new instance of this <code>DeploymentAdmin</code>.
+ */
+ public DeploymentAdminImpl() {
+ GetStorageAreaCommand getStorageAreaCommand = new GetStorageAreaCommand();
+ m_commandChain.add(getStorageAreaCommand);
+ m_commandChain.add(new StopBundleCommand());
+ m_commandChain.add(new SnapshotCommand(getStorageAreaCommand));
+ m_commandChain.add(new UpdateCommand());
+ m_commandChain.add(new StartCustomizerCommand());
+ CommitResourceCommand commitCommand = new CommitResourceCommand();
+ m_commandChain.add(new ProcessResourceCommand(commitCommand));
+ m_commandChain.add(new DropResourceCommand(commitCommand));
+ m_commandChain.add(new DropBundleCommand());
+ m_commandChain.add(commitCommand);
+ m_commandChain.add(new StartBundleCommand());
+ }
+
+ // called automatically once dependencies are satisfied
+ public void start() throws DeploymentException {
+ File packageDir = m_context.getDataFile(PACKAGE_DIR);
+ if (packageDir == null) {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not create directories needed for deployment package persistence");
+ } else {
+ packageDir.mkdirs();
+ File[] packages = packageDir.listFiles();
+ for(int i = 0; i < packages.length; i++) {
+ if (packages[i].isDirectory()) {
+ try {
+ File index = new File(packages[i], PACKAGEINDEX_FILE);
+ File contents = new File(packages[i], PACKAGE_DIR);
+ FileDeploymentPackage dp = new FileDeploymentPackage(index, contents, m_context);
+ m_packages.put(dp.getName(), dp);
+ }
+ catch (IOException e) {
+ m_log.log(LogService.LOG_WARNING, "Could not read deployment package from disk, skipping: '" + packages[i].getAbsolutePath() + "'");
+ continue;
+ }
+ }
+ }
+ }
+ }
+
+
+ public void stop() {
+ cancel();
+ }
+
+ public boolean cancel() {
+ if (m_session != null) {
+ m_session.cancel();
+ return true;
+ }
+ return false;
+ }
+
+ public DeploymentPackage getDeploymentPackage(String symbName) {
+ if (symbName == null) {
+ throw new IllegalArgumentException("Symbolic name may not be null");
+ }
+ return (DeploymentPackage) m_packages.get(symbName);
+ }
+
+ public DeploymentPackage getDeploymentPackage(Bundle bundle) {
+ if (bundle == null) {
+ throw new IllegalArgumentException("Bundle can not be null");
+ }
+ for (Iterator i = m_packages.values().iterator(); i.hasNext();) {
+ DeploymentPackage dp = (DeploymentPackage) i.next();
+ if (dp.getBundle(bundle.getSymbolicName()) != null) {
+ return dp;
+ }
+ }
+ return null;
+ }
+
+ public DeploymentPackage installDeploymentPackage(InputStream input) throws DeploymentException {
+ if (input == null) {
+ throw new IllegalArgumentException("Inputstream may not be null");
+ }
+ try {
+ if (!m_semaphore.tryAcquire(TIMEOUT)) {
+ throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Timeout exceeded while waiting to install deployment package (" + TIMEOUT + "msec)");
+ }
+ }
+ catch (InterruptedException ie) {
+ throw new DeploymentException(DeploymentException.CODE_TIMEOUT, "Thread interrupted");
+ }
+
+ JarInputStream jarInput = null;
+ File tempPackage = null;
+ File tempIndex = null;
+ File tempContents = null;
+
+ try {
+ try {
+ File tempDir = m_context.getDataFile(TEMP_DIR);
+ tempDir.mkdirs();
+ tempPackage = File.createTempFile(TEMP_PREFIX, TEMP_POSTFIX, tempDir);
+ tempPackage.delete();
+ tempPackage.mkdirs();
+ tempIndex = new File(tempPackage, PACKAGEINDEX_FILE);
+ tempContents = new File(tempPackage, PACKAGECONTENTS_DIR);
+ tempContents.mkdirs();
+ input = new ExplodingOutputtingInputStream(input, tempIndex, tempContents);
+ }
+ catch (IOException e) {
+ m_log.log(LogService.LOG_ERROR, "Error writing package to disk", e);
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Error writing package to disk", e);
+ }
+ try {
+ jarInput = new JarInputStream(input);
+ }
+ catch (IOException e) {
+ m_log.log(LogService.LOG_ERROR, "Stream does not contain a valid Jar", e);
+ throw new DeploymentException(DeploymentException.CODE_NOT_A_JAR, "Stream does not contain a valid Jar", e);
+ }
+ }
+ finally {
+ m_semaphore.release();
+ }
+
+ StreamDeploymentPackage source = new StreamDeploymentPackage(jarInput, m_context);
+ sendStartedEvent(source.getName());
+
+ boolean succeeded = false;
+ try {
+ AbstractDeploymentPackage target = (AbstractDeploymentPackage) getDeploymentPackage(source.getName());
+ boolean newPackage = (target == null);
+ if (newPackage) {
+ target = AbstractDeploymentPackage.emptyPackage;
+ }
+ if (source.isFixPackage() && ((newPackage) || (!source.getVersionRange().isInRange(target.getVersion())))) {
+ succeeded = false;
+ m_log.log(LogService.LOG_ERROR, "Target package version '" + target.getVersion() + "' is not in source range '" + source.getVersionRange() + "'");
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Target package version '" + target.getVersion() + "' is not in source range '" + source.getVersionRange() + "'");
+ }
+ try {
+ m_session = new DeploymentSessionImpl(source, target, m_commandChain, this);
+ m_session.call();
+ }
+ catch (DeploymentException de) {
+ succeeded = false;
+ throw de;
+ }
+ try {
+ jarInput.close();
+ }
+ catch (IOException e) {
+ // nothing we can do
+ m_log.log(LogService.LOG_WARNING, "Could not close stream properly", e);
+ }
+
+ File targetContents = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName() + File.separator + PACKAGECONTENTS_DIR);
+ File targetIndex = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName() + File.separator + PACKAGEINDEX_FILE);
+ if (source.isFixPackage()) {
+ try {
+ ExplodingOutputtingInputStream.merge(targetIndex, targetContents, tempIndex, tempContents);
+ }
+ catch (IOException e) {
+ succeeded = false;
+ m_log.log(LogService.LOG_ERROR, "Could not merge source fix package with target deployment package", e);
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not merge source fix package with target deployment package", e);
+ }
+ } else {
+ File targetPackage = m_context.getDataFile(PACKAGE_DIR + File.separator + source.getName());
+ targetPackage.mkdirs();
+ ExplodingOutputtingInputStream.replace(targetPackage, tempPackage);
+ }
+ FileDeploymentPackage fileDeploymentPackage = null;
+ try {
+ fileDeploymentPackage = new FileDeploymentPackage(targetIndex, targetContents, m_context);
+ m_packages.put(source.getName(), fileDeploymentPackage);
+ }
+ catch (IOException e) {
+ succeeded = false;
+ m_log.log(LogService.LOG_ERROR, "Could not create installed deployment package from disk", e);
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not create installed deployment package from disk", e);
+ }
+ succeeded = true;
+ return fileDeploymentPackage;
+ }
+ finally {
+ delete(tempPackage);
+ sendCompleteEvent(source.getName(), succeeded);
+ m_semaphore.release();
+ }
+ }
+
+ private void delete(File tempPackage) {
+ if (tempPackage.isDirectory()) {
+ File[] childs = tempPackage.listFiles();
+ for (int i = 0; i < childs.length; i++) {
+ delete(childs[i]);
+ }
+ }
+ tempPackage.delete();
+ }
+
+ public DeploymentPackage[] listDeploymentPackages() {
+ Collection packages = m_packages.values();
+ return (DeploymentPackage[]) packages.toArray(new DeploymentPackage[packages.size()]);
+ }
+
+ /**
+ * Returns reference to this bundle's <code>BundleContext</code>
+ *
+ * @return This bundle's <code>BundleContext</code>
+ */
+ public BundleContext getBundleContext() {
+ return m_context;
+ }
+
+ /**
+ * Returns reference to the current logging service defined in the framework.
+ *
+ * @return Currently active <code>LogService</code>.
+ */
+ public LogService getLog() {
+ return m_log;
+ }
+
+ /**
+ * Returns reference to the current package admin defined in the framework.
+ *
+ * @return Currently active <code>PackageAdmin</code>.
+ */
+ public PackageAdmin getPackageAdmin() {
+ return m_packageAdmin;
+ }
+
+ private void sendStartedEvent(String name) {
+ Dictionary props = new Properties();
+ props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME, name);
+ Event completeEvent = new Event(Constants.EVENTTOPIC_INSTALL, props);
+ m_eventAdmin.postEvent(completeEvent);
+ }
+
+ private void sendCompleteEvent(String name, boolean success) {
+ Dictionary props = new Properties();
+ props.put(Constants.EVENTPROPERTY_DEPLOYMENTPACKAGE_NAME, name);
+ props.put(Constants.EVENTPROPERTY_SUCCESFULL, String.valueOf(success));
+ Event completeEvent = new Event(Constants.EVENTTOPIC_COMPLETE, props);
+ m_eventAdmin.postEvent(completeEvent);
+ }
+
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentPackageManifest.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentPackageManifest.java
new file mode 100644
index 0000000..9a3531a
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/DeploymentPackageManifest.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import org.osgi.framework.Version;
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * This class represents a manifest file used to describe the contents of a deployment package. It can verify the correctness of a
+ * deployment package manifest and can interpret the various manifest entries and headers the OSGi specification defines.
+ */
+public class DeploymentPackageManifest {
+
+ private final Manifest m_manifest;
+ private final Version m_version;
+
+ private final List m_bundleInfos = new ArrayList();
+ private final List m_resourceInfos = new ArrayList();
+ private final String m_symbolicName;
+ private final VersionRange m_fixPackage;
+
+ /**
+ * Creates an instance of this class.
+ *
+ * @param manifest The manifest file to be used as deployment manifest
+ * @throws DeploymentException If the specified manifest is not a valid deployment package manifest file.
+ */
+ public DeploymentPackageManifest(Manifest manifest) throws DeploymentException {
+ if ((manifest == null) || (manifest.getMainAttributes() == null)) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER);
+ }
+ m_manifest = manifest;
+
+ Attributes mainAttributes = m_manifest.getMainAttributes();
+
+ // TODO: verify symbolic name for valid format/chars
+ m_symbolicName = getNonNullHeader(mainAttributes.getValue(Constants.DEPLOYMENTPACKAGE_SYMBOLICMAME));
+
+ String version = getNonNullHeader(mainAttributes.getValue(Constants.DEPLOYMENTPACKAGE_VERSION));
+ try {
+ m_version = new Version(version);
+ } catch (IllegalArgumentException e) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER);
+ }
+
+ String fixPackage = mainAttributes.getValue(Constants.DEPLOYMENTPACKAGE_FIXPACK);
+ if (fixPackage != null) {
+ try {
+ m_fixPackage = VersionRange.parse(fixPackage);
+ }
+ catch (IllegalArgumentException iae) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER, "Invalid version range for header: " + Constants.DEPLOYMENTPACKAGE_FIXPACK);
+ }
+ } else {
+ m_fixPackage = null;
+ }
+
+ Map entries = m_manifest.getEntries();
+ for(Iterator i = entries.keySet().iterator(); i.hasNext(); ) {
+ String key = (String) i.next();
+ processEntry(key, (Attributes) entries.get(key), (m_fixPackage != null));
+ }
+ }
+
+ /**
+ * Determines the value of a header in the main section of the manifest.
+ *
+ * @param header Name of the header to retrieve.
+ * @return Value of the header or null if the header was not defined.
+ */
+ public String getHeader(String header) {
+ return m_manifest.getMainAttributes().getValue(header);
+ }
+
+ /**
+ * Determines the version range a fix package can be applied to
+ *
+ * @return A VersionRange describing the versions the fixpackage applies to, null if the package is not a fix package.
+ */
+ public VersionRange getFixPackage() {
+ return m_fixPackage;
+ }
+
+ /**
+ * Determines the symbolic name of the deployment package.
+ *
+ * @return String containing the symbolic name of the deployment package.
+ */
+ public String getSymbolicName() {
+ return m_symbolicName;
+ }
+
+ /**
+ * Determines the version of the deployment package.
+ * @return Version of the deployment package.
+ */
+ public Version getVersion() {
+ return m_version;
+ }
+
+ /**
+ * Determines which bundle resources are part of the deployment package, this includes customizer bundles.
+ *
+ * @return A List of <code>BundleInfoImpl</code> objects describing the bundle resources of the deployment package.
+ */
+ public List getBundleInfos() {
+ return m_bundleInfos;
+ }
+
+ /**
+ * Determines which processed resources are part of the deployment package.
+ *
+ * @return A list of <code>ResourceInfoImpl</code> objects describing the processed resources of the deployment package.
+ */
+ public List getResourceInfos() {
+ return m_resourceInfos;
+ }
+
+ private void processEntry(String key, Attributes attributes, boolean isFixPack) throws DeploymentException {
+ if (BundleInfoImpl.isBundleResource(attributes)) {
+ BundleInfoImpl bundleInfo = new BundleInfoImpl(key, attributes);
+ if (bundleInfo.isMissing() && !isFixPack) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER, "Header '" + Constants.DEPLOYMENTPACKAGE_MISSING + "' for manifest " +
+ "entry '" + key + "' may only be 'true' if " + Constants.DEPLOYMENTPACKAGE_FIXPACK + " manifest header is 'true'");
+ }
+ m_bundleInfos.add(bundleInfo);
+ } else {
+ m_resourceInfos.add(new ResourceInfoImpl(key, attributes));
+ }
+ }
+
+ private String getNonNullHeader(String header) throws DeploymentException {
+ if (header == null) {
+ throw new DeploymentException(DeploymentException.CODE_MISSING_HEADER);
+ } else if(header.trim().equals("")) {
+ throw new DeploymentException(DeploymentException.CODE_BAD_HEADER);
+ }
+ return header;
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStream.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStream.java
new file mode 100644
index 0000000..2008bc4
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ExplodingOutputtingInputStream.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+import java.util.jar.Attributes.Name;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * This class will write all entries encountered in an inputstream to disk. An index of files written to disk is kept in an index file in the
+ * order they were encountered. Each file is compressed using GZIP. All the work is done on a separate thread.
+ */
+class ExplodingOutputtingInputStream extends OutputtingInputStream implements Runnable {
+
+ private final Thread m_task;
+ private final File m_contentDir;
+ private final File m_indexFile;
+ private final PipedInputStream m_input;
+
+ /**
+ * Creates an instance of this class.
+ *
+ * @param inputStream The input stream that will be written to disk as individual entries as it's read.
+ * @param indexFile File to be used to write the index of all encountered files.
+ * @param contentDir File to be used as the directory to hold all files encountered in the stream.
+ * @throws IOException If a problem occurs reading the stream resources.
+ */
+ public ExplodingOutputtingInputStream(InputStream inputStream, File indexFile, File contentDir) throws IOException {
+ this(inputStream, new PipedOutputStream(), indexFile, contentDir);
+ }
+
+ public void close() throws IOException {
+ super.close();
+ waitFor();
+ }
+
+ private ExplodingOutputtingInputStream(InputStream inputStream, PipedOutputStream output, File index, File root) throws IOException {
+ super(inputStream, output);
+ m_contentDir = root;
+ m_indexFile = index;
+ m_input = new PipedInputStream(output);
+ m_task = new Thread(this, "LiQ - ExplodingIncomingThread");
+ m_task.start();
+ }
+
+ public void waitFor() {
+ try {
+ m_task.join();
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ public void run() {
+ ZipInputStream input = null;
+ PrintWriter writer = null;
+ try {
+ input = new ZipInputStream(m_input);
+ writer = new PrintWriter(new FileWriter(m_indexFile));
+ for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
+ File current = new File(m_contentDir, entry.getName());
+ if (entry.isDirectory()) {
+ current.mkdirs();
+ }
+ else {
+ writer.println(entry.getName());
+ File parent = current.getParentFile();
+ if (parent != null) {
+ parent.mkdirs();
+ }
+ OutputStream output = null;
+ try {
+ output = new GZIPOutputStream(new FileOutputStream(current));
+ byte[] buffer = new byte[4096];
+ for (int i = input.read(buffer); i > -1; i = input.read(buffer)) {
+ output.write(buffer, 0, i);
+ }
+ }
+ finally {
+ output.close();
+ }
+ }
+ input.closeEntry();
+ writer.flush();
+ }
+ } catch (IOException ex) {
+ // TODO: log this
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (IOException e) {
+ // Not much we can do
+ }
+ }
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ }
+
+
+ public static void replace(File target, File source) {
+ delete(target, true);
+ source.renameTo(target);
+ }
+
+ private static void delete(File root, boolean deleteRoot) {
+ if (root.isDirectory()) {
+ File[] childs = root.listFiles();
+ for (int i = 0; i < childs.length; i++) {
+ delete(childs[i], true);
+ }
+ }
+ if (deleteRoot) {
+ root.delete();
+ }
+ }
+
+ public static void merge(File targetIndex, File target, File sourceIndex, File source) throws IOException {
+ List targetFiles = readIndex(targetIndex);
+ List sourceFiles = readIndex(sourceIndex);
+ List result = new ArrayList(targetFiles);
+
+ File manifestFile = new File(source, (String) sourceFiles.remove(0));
+ Manifest resultManifest = new Manifest(new GZIPInputStream(new FileInputStream(manifestFile)));
+
+ resultManifest.getMainAttributes().remove(new Name(Constants.DEPLOYMENTPACKAGE_FIXPACK));
+
+ for (Iterator i = result.iterator(); i.hasNext();) {
+ String targetFile = (String) i.next();
+ if(!resultManifest.getEntries().containsKey(targetFile) && !targetFile.equals("META-INF/MANIFEST.MF")) {
+ i.remove();
+ }
+ }
+
+ for (Iterator iter = sourceFiles.iterator(); iter.hasNext();) {
+ String path = (String) iter.next();
+ if (targetFiles.contains(path)) {
+ (new File(target, path)).delete();
+ }
+ else {
+ result.add(path);
+ }
+ (new File(source, path)).renameTo(new File(target, path));
+ }
+
+ targetFiles.removeAll(sourceFiles);
+
+ for (Iterator iter = resultManifest.getEntries().keySet().iterator(); iter.hasNext();) {
+ String path = (String) iter.next();
+ Attributes sourceAttribute = (Attributes) resultManifest.getEntries().get(path);
+ if ("true".equals(sourceAttribute.remove(new Name(Constants.DEPLOYMENTPACKAGE_MISSING)))) {
+ targetFiles.remove(path);
+ }
+ }
+
+ for (Iterator iter = targetFiles.iterator(); iter.hasNext();) {
+ String path = (String) iter.next();
+ (new File(target, path)).delete();
+ }
+
+ GZIPOutputStream outputStream = new GZIPOutputStream(new FileOutputStream(new File(target, "META-INF/MANIFEST.MF")));
+ resultManifest.write(outputStream);
+ outputStream.close();
+ writeIndex(targetIndex, result);
+ }
+
+ public static List readIndex(File index) throws IOException {
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(index));
+ List result = new ArrayList();
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ result.add(line);
+ }
+ return result;
+ }
+ finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ }
+ catch (IOException e) {
+ // Not much we can do
+ }
+ }
+ }
+ }
+
+ private static void writeIndex(File index, List input) throws IOException {
+ PrintWriter writer = null;
+ try {
+ writer = new PrintWriter(new FileWriter(index));
+ for (Iterator iterator = input.iterator(); iterator.hasNext();) {
+ writer.println(iterator.next());
+ }
+ }
+ finally {
+ if (writer != null) {
+ writer.close();
+ }
+ }
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/FileDeploymentPackage.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/FileDeploymentPackage.java
new file mode 100644
index 0000000..bd0e4c6
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/FileDeploymentPackage.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.Manifest;
+import java.util.zip.GZIPInputStream;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * Implementation of a <code>DeploymentPackage</code> that is persisted on disk.
+ */
+class FileDeploymentPackage extends AbstractDeploymentPackage {
+
+ private final List m_index;
+ private final File m_contentsDir;
+
+ /**
+ * Creates a new instance of a deployment package stored on disk.
+ *
+ * @param index Reference to the index file that contains the order in which all the resources of this deployment package were received
+ * @param packageDir Reference to the directory in which the index and package cotents are stored.
+ * @param bundleContext The bundle context
+ * @throws DeploymentException Thrown if the disk contents do not resemble a valid deployment package.
+ * @throws IOException Thrown if there was a problem reading the resources from disk.
+ */
+ public FileDeploymentPackage(File index, File packageDir, BundleContext bundleContext) throws DeploymentException, IOException {
+ this(ExplodingOutputtingInputStream.readIndex(index), packageDir, bundleContext);
+ }
+
+ private FileDeploymentPackage(List index, File packageDir, BundleContext bundleContext) throws DeploymentException, IOException {
+ super(new Manifest(new GZIPInputStream(new FileInputStream(new File(packageDir, (String) index.remove(0))))), bundleContext);
+ m_index = index;
+ m_contentsDir = packageDir;
+ }
+
+ public BundleInfoImpl[] getOrderedBundleInfos() {
+ List result = new ArrayList();
+ for(Iterator i = m_index.iterator(); i.hasNext();) {
+ AbstractInfo bundleInfo = getBundleInfoByPath((String) i.next());
+ if (bundleInfo != null) {
+ result.add(bundleInfo);
+ }
+ }
+ return (BundleInfoImpl[]) result.toArray(new BundleInfoImpl[result.size()]);
+ }
+
+ public InputStream getBundleStream(String symbolicName) throws IOException {
+ BundleInfoImpl bundleInfo = getBundleInfoByName(symbolicName);
+ if (bundleInfo != null) {
+ return new GZIPInputStream(new FileInputStream(new File(m_contentsDir, bundleInfo.getPath())));
+ }
+ return null;
+ }
+
+ public ResourceInfoImpl[] getOrderedResourceInfos() {
+ List result = new ArrayList();
+ for(Iterator i = m_index.iterator(); i.hasNext();) {
+ AbstractInfo resourceInfo = getResourceInfoByPath((String) i.next());
+ if (resourceInfo != null) {
+ result.add(resourceInfo);
+ }
+ }
+ return (ResourceInfoImpl[]) result.toArray(new ResourceInfoImpl[result.size()]);
+ }
+
+ public InputStream getCurrentEntryStream() {
+ throw new UnsupportedOperationException("Not implemented for file-based deployment package");
+ }
+
+ public AbstractInfo getNextEntry() throws IOException {
+ throw new UnsupportedOperationException("Not implemented for file-based deployment package");
+ }
+
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/OutputtingInputStream.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/OutputtingInputStream.java
new file mode 100644
index 0000000..e44b92c
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/OutputtingInputStream.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 org.apache.felix.deploymentadmin;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * This extension of the <code>InputStream</code> writes every byte that is read to an
+ * <code>OutputStream</code> of choice. The outputstream is closed automatically when
+ * the end of the inputstream is reached.
+ */
+public class OutputtingInputStream extends InputStream {
+
+ private final InputStream m_inputStream;
+ private final OutputStream m_outputStream;
+
+ /**
+ * Creates an instance of the <code>OutputtingInputStream</code>.
+ *
+ * @param inputStream The inputstream from which bytes will be read.
+ * @param outputStream The outputstream to which every byte that is read should be outputted.
+ */
+ public OutputtingInputStream(InputStream inputStream, OutputStream outputStream) {
+ super();
+ m_inputStream = inputStream;
+ m_outputStream = outputStream;
+ }
+
+ public int read() throws IOException {
+ int i = m_inputStream.read();
+ if (i != -1) {
+ m_outputStream.write(i);
+ }
+ return i;
+ }
+
+ public int read(byte[] buffer) throws IOException {
+ int i = m_inputStream.read(buffer);
+ if (i != -1) {
+ m_outputStream.write(buffer, 0, i);
+ }
+ return i;
+ }
+
+ public int read(byte[] buffer, int off, int len) throws IOException {
+ int i = m_inputStream.read(buffer, off, len);
+ if (i != -1) {
+ m_outputStream.write(buffer, off, i);
+ }
+ return i;
+ }
+
+ public void close() throws IOException {
+ try {
+ m_inputStream.close();
+ }
+ finally {
+ try {
+ m_outputStream.close();
+ }
+ catch (Exception e) {
+ // not much we can do
+ }
+ }
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ResourceInfoImpl.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ResourceInfoImpl.java
new file mode 100644
index 0000000..be796a8
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/ResourceInfoImpl.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.util.jar.Attributes;
+
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * This class represents the meta data of a processed resource as used by the Deployment Admin.
+ */
+public class ResourceInfoImpl extends AbstractInfo {
+
+ private String m_resourceProcessor;
+
+ /**
+ * Create an instance of this class.
+ *
+ * @param path String containing the path / resource-id of the processed resource.
+ * @param attributes Attributes containing the meta data of the resource.
+ * @throws DeploymentException If the specified attributes do not describe a processed resource.
+ */
+ public ResourceInfoImpl(String path, Attributes attributes) throws DeploymentException {
+ super(path, attributes);
+ m_resourceProcessor = attributes.getValue(Constants.RESOURCE_PROCESSOR);
+ }
+
+ /**
+ * Determines the resource processor for this processed resource.
+ *
+ * @return String containing the PID of the resource processor that should handle this processed resource.
+ */
+ public String getResourceProcessor() {
+ return m_resourceProcessor;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Semaphore.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Semaphore.java
new file mode 100644
index 0000000..b27bb47
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/Semaphore.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+/**
+ * A semaphore, that maintains one single permit. An <code>acquire()</code> blocks until a permit is
+ * available, whilst <code>release()</code> will unblock it.
+ */
+public class Semaphore {
+ private boolean m_available;
+
+ /**
+ * Creates a new semaphore that is available.
+ */
+ public Semaphore() {
+ m_available = true;
+ }
+
+ /**
+ * Creates a new semaphore and allows you to specify if it's available or not.
+ *
+ * @param isAvailable should the semaphore be available or not
+ */
+ public Semaphore(boolean isAvailable) {
+ m_available = isAvailable;
+ }
+
+ /**
+ * Acquires the semaphore, or blocks until it's available or the thread is interrupted.
+ *
+ * @throws InterruptedException when the thread is interrupted
+ */
+ public void acquire() throws InterruptedException {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ synchronized (this) {
+ try {
+ if (!m_available) {
+ wait();
+ }
+ m_available = false;
+ }
+ catch (InterruptedException ie) {
+ notify();
+ throw ie;
+ }
+ }
+ }
+
+ /**
+ * Tries to acquire the semaphore and waits for the duration of the specified timeout
+ * until it becomes available.
+ *
+ * @param timeout the number of milliseconds to wait
+ * @return <code>true</code> if the semaphore was acquired, <code>false</code> if it was
+ * not after waiting for the specified amount of time
+ * @throws InterruptedException when the thread is interrupted
+ */
+ public boolean tryAcquire(long timeout) throws InterruptedException {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ synchronized (this) {
+ if (m_available) {
+ m_available = false;
+ return true;
+ }
+ else if (timeout <= 0) {
+ return false;
+ }
+ else {
+ long startTime = System.currentTimeMillis();
+ try {
+ while (true) {
+ wait(timeout);
+ if (m_available) {
+ m_available = false;
+ return true;
+ }
+ else {
+ timeout -= (System.currentTimeMillis() - startTime);
+ if (timeout <= 0) {
+ return false;
+ }
+ }
+ }
+ }
+ catch (InterruptedException ie) {
+ notify();
+ throw ie;
+ }
+ }
+ }
+ }
+
+ /**
+ * Releases the semaphore. If threads were waiting, one of them is
+ * notified.
+ */
+ public synchronized void release() {
+ m_available = true;
+ notify();
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/StreamDeploymentPackage.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/StreamDeploymentPackage.java
new file mode 100644
index 0000000..d5322c6
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/StreamDeploymentPackage.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.JarInputStream;
+import java.util.zip.ZipEntry;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * This class represents a deployment package that is read from a jar stream.
+ */
+class StreamDeploymentPackage extends AbstractDeploymentPackage {
+
+ private final JarInputStream m_input;
+ private final List m_names = new ArrayList();
+
+ /**
+ * Creates an instance of this class.
+ *
+ * @param input The stream from which the deployment package can be read.
+ * @param bundleContext The bundle context.
+ * @throws DeploymentException If it was not possible to read a valid deployment package from the specified stream.
+ */
+ public StreamDeploymentPackage(JarInputStream input, BundleContext bundleContext) throws DeploymentException {
+ super(input.getManifest(), bundleContext);
+ m_input = input;
+ }
+
+ public InputStream getBundleStream(String symbolicName) {
+ throw new UnsupportedOperationException("Not applicable for stream-based deployment package");
+ }
+
+ // This only works for those resources that have been read from the stream already, no guarantees for remainder of stream
+ public BundleInfoImpl[] getOrderedBundleInfos() {
+ List result = new ArrayList();
+
+ // add all bundle resources ordered by location in stream
+ for(Iterator i = m_names.iterator(); i.hasNext();) {
+ String indexEntry = (String) i.next();
+ AbstractInfo bundleInfo = getBundleInfoByPath(indexEntry);
+ if (bundleInfo != null) {
+ result.add(bundleInfo);
+ }
+ }
+
+ // add bundle resources marked missing to the end of the result
+ BundleInfoImpl[] bundleInfoImpls = getBundleInfoImpls();
+ for (int i = 0; i < bundleInfoImpls.length; i++) {
+ if(bundleInfoImpls[i].isMissing()) {
+ result.add(bundleInfoImpls[i]);
+ }
+ }
+ return (BundleInfoImpl[]) result.toArray(new BundleInfoImpl[result.size()]);
+ }
+
+ public ResourceInfoImpl[] getOrderedResourceInfos() {
+ throw new UnsupportedOperationException("Not applicable for stream-based deployment package");
+ }
+
+ public AbstractInfo getNextEntry() throws IOException {
+ ZipEntry nextEntry = m_input.getNextJarEntry();
+ if (nextEntry == null) {
+ return null;
+ }
+ String name = nextEntry.getName();
+ m_names.add(name);
+ AbstractInfo abstractInfoByPath = getAbstractInfoByPath(name);
+ return abstractInfoByPath;
+ }
+
+ public InputStream getCurrentEntryStream() {
+ return m_input;
+ }
+
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/VersionRange.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/VersionRange.java
new file mode 100644
index 0000000..f1e2838
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/VersionRange.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin;
+
+import org.osgi.framework.Version;
+
+/**
+ * This class represents a version range as defined in section 3.2.5 of the OSGi r4 specification.
+ */
+public class VersionRange {
+
+ public static final VersionRange infiniteRange = new VersionRange(Version.emptyVersion, true, null, true);
+
+ private Version m_low = null;
+ private boolean m_isLowInclusive = false;
+ private Version m_high = null;
+ private boolean m_isHighInclusive = false;
+ private String m_toString = null;
+
+ /**
+ * Create an instance of the VersionRange class.
+ *
+ * @param low Lower bound version
+ * @param isLowInclusive True if lower bound should be included in the range
+ * @param high Upper bound version
+ * @param isHighInclusive True if upper bound should be included in the range
+ */
+ public VersionRange(Version low, boolean isLowInclusive, Version high, boolean isHighInclusive) {
+ m_low = low;
+ m_isLowInclusive = isLowInclusive;
+ m_high = high;
+ m_isHighInclusive = isHighInclusive;
+ }
+
+ /**
+ * Creates an instance of the VersionRange class which resembles [version,*)
+ *
+ * @param version The lower boundary of the version range
+ */
+ public VersionRange(Version version) {
+ this(version, true, null, false);
+ }
+
+ /**
+ * Get the lower boundary of the version range, the boundary being inclusive or not is not taken into account.
+ *
+ * @return Version resembling the lower boundary of the version range
+ */
+ public Version getLow() {
+ return m_low;
+ }
+
+ /**
+ * Determines whether the lower boundary is inclusive or not.
+ *
+ * @return True if the lower boundary is inclusive, false otherwise.
+ */
+ public boolean isLowInclusive() {
+ return m_isLowInclusive;
+ }
+
+ /**
+ * Get the upper boundary of the version range, the boundary being inclusive or not is not taken in to account.
+ *
+ * @return Version resembling the upper boundary of the version range.
+ */
+ public Version getHigh() {
+ return m_high;
+ }
+
+ /**
+ * Determines whether the upper boundary is inclusive or not.
+ *
+ * @return True if the upper boundary is inclusive, false otherwise.
+ */
+ public boolean isHighInclusive() {
+ return m_isHighInclusive;
+ }
+
+ /**
+ * Determine if the specified version is part of the version range or not.
+ *
+ * @param version The version to verify
+ * @return True if the specified version is included in this version range, false otherwise.
+ */
+ public boolean isInRange(Version version) {
+ // We might not have an upper end to the range.
+ if (m_high == null) {
+ return (version.compareTo(m_low) >= 0);
+ }
+ else if (isLowInclusive() && isHighInclusive()) {
+ return (version.compareTo(m_low) >= 0) && (version.compareTo(m_high) <= 0);
+ }
+ else if (isHighInclusive()) {
+ return (version.compareTo(m_low) > 0) && (version.compareTo(m_high) <= 0);
+ }
+ else if (isLowInclusive()) {
+ return (version.compareTo(m_low) >= 0) && (version.compareTo(m_high) < 0);
+ }
+ return (version.compareTo(m_low) > 0) && (version.compareTo(m_high) < 0);
+ }
+
+ /**
+ * Parses a version range from the specified string.
+ *
+ * @param range String representation of the version range.
+ * @return A <code>VersionRange</code> object representing the version range.
+ * @throws IllegalArgumentException If <code>range</code> is improperly formatted.
+ */
+ public static VersionRange parse(String range) throws IllegalArgumentException {
+ // 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 String toString() {
+ if (m_toString == null) {
+ if (m_high != null) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(m_isLowInclusive ? '[' : '(');
+ sb.append(m_low.toString());
+ sb.append(',');
+ sb.append(m_high.toString());
+ sb.append(m_isHighInclusive ? ']' : ')');
+ m_toString = sb.toString();
+ }
+ else {
+ m_toString = m_low.toString();
+ }
+ }
+ return m_toString;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/Command.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/Command.java
new file mode 100644
index 0000000..cb5790e
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/Command.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * Commands describe a group of tasks to be executed within the execution a deployment session.
+ * A command that has already executed can be rolled back and a command that is currently in progress
+ * can be signaled to stop it's activities by canceling it.
+ */
+public abstract class Command {
+
+ private final List m_rollback = new ArrayList();
+ private final List m_commit = new ArrayList();
+ private volatile boolean m_cancelled;
+
+ /**
+ * Executes the command, the specified <code>DeploymentSession</code> can be used to obtain various
+ * information about the deployment session which the command is part of.
+ *
+ * @param session The deployment session this command is part of.
+ * @throws DeploymentException Thrown if the command could not successfully execute.
+ */
+ public abstract void execute(DeploymentSessionImpl session) throws DeploymentException;
+
+ /**
+ * Rolls back all actions that were added through the <code>addRollback(Runnable r)</code> method (in reverse
+ * adding order). It is not guaranteed that the state of everything related to the command will be as if the
+ * command was never executed, a best effort should be made though.
+ */
+ public void rollback() {
+ for (ListIterator i = m_rollback.listIterator(); i.hasPrevious();) {
+ Runnable runnable = (Runnable) i.previous();
+ runnable.run();
+ }
+ cleanUp();
+ }
+
+ /**
+ * Commits all changes the command may have defined when it was executed by calling the <code>execute()</code> method.
+ */
+ protected void commit() {
+ for (ListIterator i = m_commit.listIterator(); i.hasPrevious();) {
+ Runnable runnable = (Runnable) i.previous();
+ runnable.run();
+ }
+ cleanUp();
+ }
+
+ private void cleanUp() {
+ m_rollback.clear();
+ m_commit.clear();
+ m_cancelled = false;
+ }
+
+ /**
+ * Determines if the command was canceled. This method should be used regularly by implementing classes to determine if
+ * their command was canceled, if so they should return as soon as possible from their operations.
+ *
+ * @return true if the command was canceled, false otherwise.
+ */
+ protected boolean isCancelled() {
+ return m_cancelled;
+ }
+
+ /**
+ * Adds an action to be executed in case of a roll back.
+ *
+ * @param runnable The runnable to be executed in case of a roll back.
+ */
+ protected void addRollback(Runnable runnable) {
+ m_rollback.add(runnable);
+ }
+
+ /**
+ * Adds an action to be executed in case of a commit
+ *
+ * @param runnable The runnable to be executes in case of a commit.
+ */
+ protected void addCommit(Runnable runnable) {
+ m_commit.add(runnable);
+ }
+
+ /**
+ * Sets the command to being cancelled, this does not have an immediate effect. Commands that are executing should
+ * check regularly if they were cancelled and if so they should make an effort to stop their operations as soon as possible
+ * followed by throwing an <code>DeploymentException.CODE_CANCELLED</code> exception.
+ */
+ public void cancel() {
+ m_cancelled = true;
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/CommitResourceCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/CommitResourceCommand.java
new file mode 100644
index 0000000..1262706
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/CommitResourceCommand.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Command that commits all the resource processors that were added to the command.
+ */
+public class CommitResourceCommand extends Command implements Runnable {
+
+ private final List m_processors = new ArrayList();
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ for (ListIterator i = m_processors.listIterator(); i.hasPrevious();) {
+ ResourceProcessor processor = (ResourceProcessor) i.previous();
+ try {
+ processor.prepare();
+ }
+ catch (ResourceProcessorException e) {
+ session.getLog().log(LogService.LOG_ERROR, "Preparing commit for resource processor failed", e);
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Preparing commit for resource processor failed", e);
+ }
+ }
+ for (ListIterator i = m_processors.listIterator(); i.hasPrevious();) {
+ ResourceProcessor processor = (ResourceProcessor) i.previous();
+ try {
+ processor.commit();
+ }
+ catch (Exception e) {
+ session.getLog().log(LogService.LOG_ERROR, "Committing resource processor '" + processor + "' failed", e);
+ // TODO Throw exception?
+ }
+ }
+ }
+
+ public void rollback() {
+ for (ListIterator i = m_processors.listIterator(); i.hasPrevious();) {
+ ResourceProcessor processor = (ResourceProcessor) i.previous();
+ try {
+ processor.rollback();
+ }
+ catch (Exception e) {
+ // TODO Log this?
+ }
+ i.remove();
+ }
+ }
+
+ /**
+ * Add a resource processor, all resource processors that are added will be committed when the command is executed.
+ *
+ * @param processor The resource processor to add.
+ */
+ public void addResourceProcessor(ResourceProcessor processor) {
+ for (Iterator i = m_processors.iterator(); i.hasNext();) {
+ ResourceProcessor proc = (ResourceProcessor) i.next();
+ if (proc == processor) {
+ return;
+ }
+ }
+ m_processors.add(processor);
+ }
+
+ public void run() {
+ rollback();
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
new file mode 100644
index 0000000..6cf05da
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DeploymentSessionImpl.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.DeploymentAdminImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+import org.osgi.service.deploymentadmin.spi.DeploymentSession;
+import org.osgi.service.log.LogService;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+public class DeploymentSessionImpl implements DeploymentSession {
+
+ private final AbstractDeploymentPackage m_target;
+ private final AbstractDeploymentPackage m_source;
+ private final List m_commands;
+ private final DeploymentAdminImpl m_admin;
+ private volatile Command m_currentCommand = null;
+ private volatile boolean m_cancelled;
+
+ public DeploymentSessionImpl(AbstractDeploymentPackage source, AbstractDeploymentPackage target, List commands, DeploymentAdminImpl admin) {
+ m_source = source;
+ m_target = target;
+ m_commands = commands;
+ m_admin = admin;
+ }
+
+ /**
+ * Calling this method will cause the commands specified for this session to be executed. the commands will be rolled back if the session is
+ * canceled or if an exception is caused by one of the commands.
+ *
+ * @throws DeploymentException If the session was canceled (<code>DeploymentException.CODE_CANCELLED</code>) or if one of the commands caused an exception (<code>DeploymentException.*</code>)
+ */
+ public void call() throws DeploymentException {
+ List executedCommands = new ArrayList();
+ for (Iterator i = m_commands.iterator(); i.hasNext();) {
+ if (m_cancelled) {
+ // previous command did not pick up on cancel
+ rollback(executedCommands);
+ throw new DeploymentException(DeploymentException.CODE_CANCELLED);
+ }
+ m_currentCommand = (Command) i.next();
+ try {
+ executedCommands.add(m_currentCommand);
+ m_currentCommand.execute(this);
+ }
+ catch (DeploymentException de) {
+ rollback(executedCommands);
+ throw de;
+ }
+ }
+ for (Iterator i = m_commands.iterator(); i.hasNext();) {
+ ((Command) i.next()).commit();
+ }
+ m_currentCommand = null;
+ }
+
+ private void rollback(List executedCommands) {
+ for (ListIterator i = executedCommands.listIterator(); i.hasPrevious();) {
+ Command command = (Command) i.previous();
+ command.rollback();
+ }
+ }
+
+ /**
+ * Cancels the session if it is in progress.
+ *
+ * @return true if a session was in progress and now canceled, false otherwise.
+ */
+ public boolean cancel() {
+ m_cancelled = true;
+ if (m_currentCommand != null) {
+ m_currentCommand.cancel();
+ return true;
+ }
+ return false;
+ }
+
+ public File getDataFile(Bundle bundle) {
+ BundleContext context = null;
+ try {
+ Method getBundleContext = bundle.getClass().getDeclaredMethod("getBundleContext", null);
+ getBundleContext.setAccessible(true);
+ context = (BundleContext) getBundleContext.invoke(bundle, null);
+ }
+ catch (Exception ex) {
+ // TODO: log this
+ }
+ File result = null;
+ if (context != null) {
+ result = context.getDataFile("");
+ }
+ if (result == null) {
+ // TODO: log this
+ throw new IllegalStateException("");
+ }
+ return result;
+ }
+
+ public DeploymentPackage getSourceDeploymentPackage() {
+ return m_source;
+ }
+
+ public DeploymentPackage getTargetDeploymentPackage() {
+ return m_target;
+ }
+
+ /**
+ * Returns the bundle context of the bundle this class is part of.
+ *
+ * @return The <code>BundleContext</code>.
+ */
+ public BundleContext getBundleContext() {
+ return m_admin.getBundleContext();
+ }
+
+ /**
+ * Returns the currently present log service.
+ *
+ * @return The <code>LogService</code>.
+ */
+ public LogService getLog() {
+ return m_admin.getLog();
+ }
+
+ /**
+ * Returns the currently present package admin.
+ *
+ * @return The <code>PackageAdmin</code>
+ */
+ public PackageAdmin getPackageAdmin() {
+ return m_admin.getPackageAdmin();
+ }
+
+ /**
+ * Returns the target deployment package as an <code>AbstractDeploymentPackage</code>.
+ *
+ * @return The target deployment package of the session.
+ */
+ public AbstractDeploymentPackage getTargetAbstractDeploymentPackage() {
+ return m_target;
+ }
+
+ /**
+ * Returns the source deployment package as an <code>AbstractDeploymentPackage</code>.
+ *
+ * @return The source deployment packge of the session.
+ */
+ public AbstractDeploymentPackage getSourceAbstractDeploymentPackage() {
+ return m_source;
+ }
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropBundleCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropBundleCommand.java
new file mode 100644
index 0000000..b894702
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropBundleCommand.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.BundleInfoImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Command that uninstalls bundles, if rolled back the bundles are restored.
+ */
+public class DropBundleCommand extends Command {
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
+ AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
+ LogService log = session.getLog();
+
+ BundleInfoImpl[] orderedTargetBundles = target.getOrderedBundleInfos();
+ for (int i = orderedTargetBundles.length - 1; i >= 0; i--) {
+ BundleInfoImpl bundleInfo = orderedTargetBundles[i];
+ if (source.getBundleInfoByPath(bundleInfo.getPath()) == null) {
+ // stale bundle, save a copy for rolling back and uninstall it
+ String symbolicName = bundleInfo.getSymbolicName();
+ try {
+ Bundle bundle = target.getBundle(symbolicName);
+ bundle.uninstall();
+ addRollback(new InstallBundleRunnable(bundle, target.getBundleStream(symbolicName), log));
+ }
+ catch (BundleException be) {
+ log.log(LogService.LOG_WARNING, "Bundle '" + symbolicName + "' could not be uninstalled", be);
+ }
+ catch (IOException e) {
+ log.log(LogService.LOG_WARNING, "Could not get bundle data stream for bundle '" + symbolicName + "'", e);
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not prepare rollback for uninstalling bundle '" + symbolicName + "'");
+ }
+ }
+ }
+ }
+
+ private static class InstallBundleRunnable implements Runnable {
+
+ private final InputStream m_bundleStream;
+ private final Bundle m_bundle;
+ private final LogService m_log;
+
+ public InstallBundleRunnable(Bundle bundle, InputStream bundleStream, LogService log) {
+ m_bundle = bundle;
+ m_bundleStream = bundleStream;
+ m_log = log;
+ }
+
+ public void run() {
+ try {
+ m_bundle.update(m_bundleStream);
+ }
+ catch (BundleException e) {
+ m_log.log(LogService.LOG_WARNING, "Could not rollback uninstallation of bundle '" + m_bundle.getSymbolicName() + "'", e);
+ }
+ }
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropResourceCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropResourceCommand.java
new file mode 100644
index 0000000..be32fea
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/DropResourceCommand.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.ResourceInfoImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Command that drops resources.
+ */
+public class DropResourceCommand extends Command {
+
+ private final CommitResourceCommand m_commitCommand;
+
+ /**
+ * Creates an instance of this command. The commit command is used to make sure
+ * the resource processors used to drop resources will be committed at a later stage in the process.
+ *
+ * @param commitCommand The commit command that will be executed at a later stage in the process.
+ */
+ public DropResourceCommand(CommitResourceCommand commitCommand) {
+ m_commitCommand = commitCommand;
+ addRollback(m_commitCommand);
+ }
+
+ public void execute(DeploymentSessionImpl session) {
+ AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
+ AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
+ BundleContext context = session.getBundleContext();
+ LogService log = session.getLog();
+
+ ResourceInfoImpl[] orderedTargetResources = target.getOrderedResourceInfos();
+ for (int i = orderedTargetResources.length - 1; i >= 0; i--) {
+ ResourceInfoImpl resourceInfo = orderedTargetResources[i];
+ String path = resourceInfo.getPath();
+ if (source.getResourceInfoByPath(path) == null) {
+ ServiceReference ref = target.getResourceProcessor(path);
+ if (ref != null) {
+ ResourceProcessor resourceProcessor = (ResourceProcessor) context.getService(ref);
+ if (resourceProcessor != null) {
+ try {
+ m_commitCommand.addResourceProcessor(resourceProcessor);
+ resourceProcessor.dropped(path);
+ }
+ catch (ResourceProcessorException e) {
+ log.log(LogService.LOG_WARNING, "Not allowed to drop resource '" + path + "'", e);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/GetStorageAreaCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/GetStorageAreaCommand.java
new file mode 100644
index 0000000..f7fa8a6
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/GetStorageAreaCommand.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 org.apache.felix.deploymentadmin.spi;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.deploymentadmin.BundleInfo;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+import org.osgi.service.log.LogService;
+
+/**
+ * Command that determines the storage area's of all bundles in the source deployment
+ * package of a deployment session.
+ */
+public class GetStorageAreaCommand extends Command {
+
+ private final Map m_storageAreas = new HashMap();
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ DeploymentPackage target = session.getTargetDeploymentPackage();
+ BundleInfo[] infos = target.getBundleInfos();
+ for (int i = 0; i < infos.length; i++) {
+ if (isCancelled()) {
+ throw new DeploymentException(DeploymentException.CODE_CANCELLED);
+ }
+ Bundle bundle = target.getBundle(infos[i].getSymbolicName());
+ if (bundle != null) {
+ try {
+ File root = session.getDataFile(bundle);
+ m_storageAreas.put(bundle.getSymbolicName(), root);
+ }
+ catch (IllegalStateException ise) {
+ session.getLog().log(LogService.LOG_WARNING, "Could not get reference to storage area of bundle '" + bundle.getSymbolicName() +"'");
+ }
+ }
+ }
+ }
+
+ /**
+ * Determines the storage area's of all bundles in the source deployment package of
+ * a deployment session.
+ *
+ * @return <code>Map</code> with <code>File</code> object references to the storage area's, they bundle symbolic name is used as a key in the <code>Map</code>.
+ */
+ public Map getStorageAreas() {
+ return m_storageAreas;
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/ProcessResourceCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/ProcessResourceCommand.java
new file mode 100644
index 0000000..7389e54
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/ProcessResourceCommand.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.AbstractInfo;
+import org.apache.felix.deploymentadmin.ResourceInfoImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessor;
+import org.osgi.service.deploymentadmin.spi.ResourceProcessorException;
+
+/**
+ * Command that processes all the processed resources in the source deployment package
+ * of a deployment session by finding their Resource Processors and having those process
+ * the resources.
+ */
+public class ProcessResourceCommand extends Command {
+
+ private final CommitResourceCommand m_commitCommand;
+
+ /**
+ * Creates an instance of this command, the <code>CommitCommand</code> is used
+ * to ensure that all used <code>ResourceProcessor</code>s will be committed at a later
+ * stage in the deployment session.
+ *
+ * @param commitCommand The <code>CommitCommand</code> that will commit all resource processors used in this command.
+ */
+ public ProcessResourceCommand(CommitResourceCommand commitCommand) {
+ m_commitCommand = commitCommand;
+ addRollback(m_commitCommand);
+ }
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
+ BundleContext context = session.getBundleContext();
+
+ Map expectedResources = new HashMap();
+ AbstractInfo[] resourceInfos = (AbstractInfo[]) source.getBundleInfos();
+ for (int i = 0; i < resourceInfos.length; i++) {
+ AbstractInfo resourceInfo = resourceInfos[i];
+ if(!resourceInfo.isMissing()) {
+ expectedResources.put(resourceInfo.getPath(), resourceInfo);
+ }
+ }
+
+ try {
+ for (AbstractInfo jarEntry = source.getNextEntry(); (jarEntry != null) && (!expectedResources.isEmpty()); jarEntry = source.getNextEntry()) {
+ String name = jarEntry.getPath();
+
+ if (!expectedResources.containsKey(name)) {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Resource '" + name + "' is not described in the manifest.");
+ }
+
+ ResourceInfoImpl resourceInfo = (ResourceInfoImpl) expectedResources.remove(name);
+
+ String resourceProcessorPID = resourceInfo.getResourceProcessor();
+ if (resourceProcessorPID == null) {
+ // no resource processor specified, resource can be ignored
+ continue;
+ }
+
+ ServiceReference ref = source.getResourceProcessor(resourceProcessorPID);
+ if (ref != null) {
+ String serviceOwnerSymName = ref.getBundle().getSymbolicName();
+ if (source.getBundleInfoByName(serviceOwnerSymName) != null) {
+ ResourceProcessor resourceProcessor = (ResourceProcessor) context.getService(ref);
+ if (resourceProcessor != null) {
+ resourceProcessor.begin(session);
+ try {
+ m_commitCommand.addResourceProcessor(resourceProcessor);
+ resourceProcessor.process(name, source.getCurrentEntryStream());
+ }
+ catch (ResourceProcessorException rpe) {
+ if (rpe.getCode() == ResourceProcessorException.CODE_RESOURCE_SHARING_VIOLATION) {
+ throw new DeploymentException(DeploymentException.CODE_RESOURCE_SHARING_VIOLATION, "Violation while processing resource '" + name + "'", rpe);
+ }
+ else {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Error while processing resource '" + name + "'", rpe);
+ }
+ }
+ }
+ else {
+ throw new DeploymentException(DeploymentException.CODE_PROCESSOR_NOT_FOUND, "No resource processor for resource: '" + name + "'");
+ }
+ }
+ else {
+ throw new DeploymentException(DeploymentException.CODE_FOREIGN_CUSTOMIZER, "Resource processor for resource '" + name + "' belongs to foreign deployment package");
+ }
+ }
+ else {
+ throw new DeploymentException(DeploymentException.CODE_PROCESSOR_NOT_FOUND, "No resource processor for resource: '" + name + "'");
+ }
+ }
+ }
+ catch (IOException e) {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Problem while reading stream", e);
+ }
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/SnapshotCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/SnapshotCommand.java
new file mode 100644
index 0000000..3e78114
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/SnapshotCommand.java
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.deploymentadmin.BundleInfo;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.log.LogService;
+
+public class SnapshotCommand extends Command {
+
+ private final GetStorageAreaCommand m_getStorageAreaCommand;
+
+ public SnapshotCommand(GetStorageAreaCommand getStorageAreaCommand) {
+ m_getStorageAreaCommand = getStorageAreaCommand;
+ }
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
+ BundleContext context = session.getBundleContext();
+
+ BundleInfo[] infos = target.getBundleInfos();
+ Map storageAreas = m_getStorageAreaCommand.getStorageAreas();
+ for (int i = 0; i < infos.length; i++) {
+ if (isCancelled()) {
+ throw new DeploymentException(DeploymentException.CODE_CANCELLED);
+ }
+ Bundle bundle = target.getBundle(infos[i].getSymbolicName());
+ if (bundle != null) {
+ File root = (File) storageAreas.get(bundle.getSymbolicName());
+ if (root != null) {
+ File snapshot = context.getDataFile("snapshots");
+ snapshot.mkdirs();
+ snapshot = new File(snapshot, infos[i].getSymbolicName());
+ try {
+ snapshot.createNewFile();
+ store(root, snapshot);
+ addRollback(new RestoreSnapshotRunnable(snapshot, root));
+ addCommit(new DeleteSnapshotRunnable(snapshot));
+ }
+ catch (IOException e) {
+ snapshot.delete();
+ }
+ } else {
+ session.getLog().log(LogService.LOG_WARNING, "Could not retrieve storage area of bundle '" + bundle.getSymbolicName() + "', skipping it.");
+ }
+ }
+ }
+ }
+
+ private void delete(File root, boolean deleteRoot) {
+ if (root.isDirectory()) {
+ File[] childs = root.listFiles();
+ for (int i = 0; i < childs.length; i++) {
+ delete(childs[i], true);
+ }
+ }
+ if (deleteRoot) {
+ root.delete();
+ }
+ }
+
+ private void store(File source, File target) throws IOException {
+ ZipOutputStream output = null;
+ try {
+ File[] children = source.listFiles();
+ output = new ZipOutputStream(new FileOutputStream(target));
+ for (int i = 0; i < children.length; i++) {
+ storeRecursive(target, new File(children[i].getName()), output);
+ }
+ }
+ finally {
+ if (output != null) {
+ try {
+ output.close();
+ }
+ catch (Exception ex) {
+ // Not much we can do
+ }
+ }
+ }
+ }
+
+ private void storeRecursive(File current, File path, ZipOutputStream output) throws IOException {
+ output.putNextEntry(new ZipEntry(path.getPath()));
+ if (current.isDirectory()) {
+ output.closeEntry();
+ File[] childs = current.listFiles();
+ for (int i = 0; i < childs.length; i++) {
+ storeRecursive(childs[i], new File(path, childs[i].getName()), output);
+ }
+ }
+ else {
+ InputStream input = null;
+ try {
+ input = new FileInputStream(current);
+ byte[] buffer = new byte[4096];
+ for (int i = input.read(buffer); i != -1; i = input.read(buffer)) {
+ output.write(buffer, 0, i);
+ }
+ output.closeEntry();
+ }
+ finally {
+ try {
+ if (input != null) {
+ input.close();
+ }
+ }
+ catch (Exception ex) {
+ // Not much we can do
+ }
+ }
+ }
+ }
+
+ private void unpack(File source, File target) throws IOException {
+ ZipInputStream input = null;
+ try {
+ input = new ZipInputStream(new FileInputStream(source));
+ for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) {
+ if (entry.isDirectory()) {
+ (new File(target, entry.getName())).mkdirs();
+ }
+ else {
+ OutputStream output = null;
+ try {
+ output = new FileOutputStream(target);
+ byte[] buffer = new byte[4096];
+ for (int i = input.read(buffer); i > -1; i = input.read(buffer)) {
+ output.write(buffer, 0, i);
+ }
+ }
+ finally {
+ if (output != null) {
+ try {
+ output.close();
+ }
+ catch (Exception ex) {
+ // Not much we can do
+ }
+ }
+ }
+ }
+ input.closeEntry();
+ }
+ }
+ finally {
+ if (input != null) {
+ try {
+ input.close();
+ }
+ catch (Exception ex) {
+ // Not much we can do
+ }
+ }
+ }
+ }
+
+ class DeleteSnapshotRunnable implements Runnable {
+
+ private final File m_snapshot;
+
+ private DeleteSnapshotRunnable(File snapshot) {
+ m_snapshot = snapshot;
+ }
+
+ public void run() {
+ m_snapshot.delete();
+ }
+ }
+
+ private class RestoreSnapshotRunnable implements Runnable {
+
+ private final File m_snapshot;
+ private final File m_root;
+
+ private RestoreSnapshotRunnable(File snapshot, File root) {
+ m_snapshot = snapshot;
+ m_root = root;
+ }
+
+ public void run() {
+ try {
+ delete(m_root, false);
+ unpack(m_snapshot, m_root);
+ }
+ catch (Exception ex) {
+ // TODO: log this
+ }
+ finally {
+ m_snapshot.delete();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartBundleCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartBundleCommand.java
new file mode 100644
index 0000000..c8897e3
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartBundleCommand.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.BundleInfoImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkEvent;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.service.log.LogService;
+import org.osgi.service.packageadmin.PackageAdmin;
+
+/**
+ * Command that starts all bundles described in the source deployment package of a deployment session.
+ */
+public class StartBundleCommand extends Command {
+
+ private final RefreshPackagesMonitor m_refreshMonitor = new RefreshPackagesMonitor();
+ private static final int REFRESH_TIMEOUT = 10000;
+
+ public void execute(DeploymentSessionImpl session) {
+ AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
+ BundleContext context = session.getBundleContext();
+ PackageAdmin packageAdmin = session.getPackageAdmin();
+ RefreshPackagesListener listener = new RefreshPackagesListener();
+ LogService log = session.getLog();
+
+ context.addFrameworkListener(listener);
+ packageAdmin.refreshPackages(null);
+ m_refreshMonitor.waitForRefresh();
+ context.removeFrameworkListener(listener);
+
+ // start source bundles
+ BundleInfoImpl[] bundleInfos = source.getOrderedBundleInfos();
+ for (int i = 0; i < bundleInfos.length; i++) {
+ BundleInfoImpl bundleInfoImpl = bundleInfos[i];
+ if(!bundleInfoImpl.isCustomizer()) {
+ Bundle bundle = source.getBundle(bundleInfoImpl.getSymbolicName());
+ if (bundle != null) {
+ try {
+ bundle.start();
+ }
+ catch (BundleException be) {
+ log.log(LogService.LOG_WARNING, "Could not start bundle '" + bundle.getSymbolicName() + "'", be);
+ }
+ }
+ else {
+ log.log(LogService.LOG_WARNING, "Could not start bundle '" + bundleInfoImpl.getSymbolicName() + "' because it is no defined in the framework");
+ }
+ }
+ }
+ }
+
+ /**
+ * RefreshPackagesListener is only listing to FrameworkEvents of the type PACKAGES_REFRESHED. It will
+ * notify any object waiting the completion of a refreshpackages() call.
+ */
+ private class RefreshPackagesListener implements FrameworkListener {
+ public void frameworkEvent(FrameworkEvent event) {
+ if (event.getType() == FrameworkEvent.PACKAGES_REFRESHED) {
+ // TODO: m_log.log(LogService.LOG_INFO, "Packages refreshed event received");
+ m_refreshMonitor.proceed();
+ }
+ }
+ }
+
+ /**
+ * Use this monitor when its desired to wait for the completion of the asynchronous PackageAdmin.refreshPackages() call.
+ */
+ private class RefreshPackagesMonitor {
+ private boolean m_alreadyNotified = false;
+
+ /**
+ * Waits for the completion of the PackageAdmin.refreshPackages() call. Because
+ * its not sure whether all OSGi framework implementations implement this method as
+ * specified we have build in a timeout. So if a event about the completion of the
+ * refreshpackages() is never received, we continue after the timeout whether the refresh
+ * was done or not.
+ */
+ public synchronized void waitForRefresh() {
+ if (!m_alreadyNotified) {
+ // TODO: m_log.log(LogService.LOG_DEBUG, "wait for Packages refreshed event");
+ try {
+ wait(REFRESH_TIMEOUT);
+ }
+ catch (InterruptedException ie) {
+ // TODO: m_log.log(LogService.LOG_INFO, "interrupted while waiting for packages refreshed event", ie);
+ }
+ finally {
+ // just reset the misted notification variable, this Monitor object might be reused.
+ m_alreadyNotified = false;
+ }
+ }
+ else {
+ // TODO: m_log.log(LogService.LOG_DEBUG, "won't wait for Packages refreshed event, event is already received");
+ // just reset the misted notification variable, this Monitor object might be reused.
+ m_alreadyNotified = false;
+ }
+ }
+
+ /**
+ * After a PACKAGES_REFRESHED event notify all the parties interested in the completion of
+ * the PackageAdmin.refreshPackages() call.
+ */
+ public synchronized void proceed() {
+ m_alreadyNotified = true;
+ notifyAll();
+ }
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartCustomizerCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartCustomizerCommand.java
new file mode 100644
index 0000000..c1079f2
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StartCustomizerCommand.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.BundleInfoImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * Command that starts all customizer bundles defined in the source deployment packages of a deployment
+ * session. In addition all customizer bundles of the target deployment package that are not present in the source
+ * deployment package are started as well.
+ */
+public class StartCustomizerCommand extends Command {
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
+ AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
+ Set bundles = new HashSet();
+ Set sourceBundlePaths = new HashSet();
+ BundleInfoImpl[] targetInfos = target.getBundleInfoImpls();
+ BundleInfoImpl[] sourceInfos = source.getBundleInfoImpls();
+ for(int i = 0; i < sourceInfos.length; i++) {
+ if (sourceInfos[i].isCustomizer()) {
+ sourceBundlePaths.add(sourceInfos[i].getPath());
+ Bundle bundle = source.getBundle(sourceInfos[i].getSymbolicName());
+ if (bundle != null) {
+ bundles.add(bundle);
+ }
+ }
+ }
+ for(int i = 0; i < targetInfos.length; i++) {
+ if (targetInfos[i].isCustomizer() && !sourceBundlePaths.contains(targetInfos[i].getPath())) {
+ Bundle bundle = target.getBundle(targetInfos[i].getSymbolicName());
+ if (bundle != null) {
+ bundles.add(bundle);
+ }
+ }
+ }
+ for(Iterator i = bundles.iterator(); i.hasNext(); ) {
+ Bundle bundle = (Bundle) i.next();
+ try {
+ bundle.start();
+ }
+ catch (BundleException be) {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not start customizer bundle '" + bundle.getSymbolicName() + "'", be);
+ }
+ addRollback(new StopCustomizerRunnable(bundle));
+ }
+ }
+
+ private static class StopCustomizerRunnable implements Runnable {
+
+ private final Bundle m_bundle;
+
+ public StopCustomizerRunnable(Bundle bundle) {
+ m_bundle = bundle;
+ }
+
+ public void run() {
+ try {
+ m_bundle.stop();
+ }
+ catch (BundleException e) {
+ // TODO log this
+ e.printStackTrace();
+ }
+ }
+
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
new file mode 100644
index 0000000..ee56373
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/StopBundleCommand.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+import org.osgi.service.deploymentadmin.BundleInfo;
+import org.osgi.service.deploymentadmin.DeploymentException;
+import org.osgi.service.log.LogService;
+
+/**
+ * Command that stops all bundles described in the target deployment package of a deployment session.
+ */
+public class StopBundleCommand extends Command {
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ AbstractDeploymentPackage target = session.getTargetAbstractDeploymentPackage();
+ BundleInfo[] bundleInfos = target.getOrderedBundleInfos();
+ for (int i = 0; i < bundleInfos.length; i++) {
+ if (isCancelled()) {
+ throw new DeploymentException(DeploymentException.CODE_CANCELLED);
+ }
+ Bundle bundle = target.getBundle(bundleInfos[i].getSymbolicName());
+ if (bundle != null) {
+ addRollback(new StartBundleRunnable(bundle));
+ try {
+ bundle.stop();
+ }
+ catch (BundleException e) {
+ session.getLog().log(LogService.LOG_WARNING, "Could not stop bundle '" + bundle.getSymbolicName() + "'", e);
+ }
+ }
+ else {
+ session.getLog().log(LogService.LOG_WARNING, "Could not stop bundle '" + bundleInfos[i].getSymbolicName() + "' because it was not defined int he framework");
+ }
+ }
+ }
+
+ private class StartBundleRunnable implements Runnable {
+
+ private final Bundle m_bundle;
+
+ public StartBundleRunnable(Bundle bundle) {
+ m_bundle = bundle;
+ }
+
+ public void run() {
+ try {
+ m_bundle.start();
+ }
+ catch (BundleException e) {
+ // TODO: log this
+ e.printStackTrace();
+ }
+ }
+
+ }
+}
+
diff --git a/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/UpdateCommand.java b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/UpdateCommand.java
new file mode 100644
index 0000000..95b7d69
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/apache/felix/deploymentadmin/spi/UpdateCommand.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.deploymentadmin.spi;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.deploymentadmin.AbstractDeploymentPackage;
+import org.apache.felix.deploymentadmin.AbstractInfo;
+import org.apache.felix.deploymentadmin.BundleInfoImpl;
+import org.apache.felix.deploymentadmin.Constants;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.Version;
+import org.osgi.service.deploymentadmin.DeploymentException;
+
+/**
+ * Command that installs all bundles described in the source deployment package of a deployment
+ * session. If a bundle was already defined in the target deployment package of the same session
+ * it is updated, otherwise the bundle is simply installed.
+ */
+public class UpdateCommand extends Command {
+
+ public void execute(DeploymentSessionImpl session) throws DeploymentException {
+ AbstractDeploymentPackage source = session.getSourceAbstractDeploymentPackage();
+ AbstractDeploymentPackage targetPackage = session.getTargetAbstractDeploymentPackage();
+ BundleContext context = session.getBundleContext();
+
+ Map expectedBundles = new HashMap();
+ AbstractInfo[] bundleInfos = (AbstractInfo[]) source.getBundleInfos();
+ for (int i = 0; i < bundleInfos.length; i++) {
+ AbstractInfo bundleInfo = bundleInfos[i];
+ if(!bundleInfo.isMissing()) {
+ expectedBundles.put(bundleInfo.getPath(), bundleInfo);
+ }
+ }
+
+ try {
+ for (AbstractInfo entry = source.getNextEntry(); (entry != null) && (!expectedBundles.isEmpty()); entry = source.getNextEntry()) {
+ String name = entry.getPath();
+ if (!expectedBundles.containsKey(name)) {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Resource '" + name + "' is not described in the manifest.");
+ }
+
+ BundleInfoImpl bundleInfo = (BundleInfoImpl) expectedBundles.remove(name);
+ Bundle bundle = source.getBundle(bundleInfo.getSymbolicName());
+ try {
+ if (bundle == null) {
+ // new bundle, install it
+ bundle = context.installBundle(Constants.BUNDLE_LOCATION_PREFIX + bundleInfo.getSymbolicName(), new BundleInputStream(source.getCurrentEntryStream()));
+ addRollback(new UninstallBundleRunnable(bundle));
+ } else {
+ // existing bundle, update it
+ Version sourceVersion = bundleInfo.getVersion();
+ Version targetVersion = Version.parseVersion((String) bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION));
+ if (!sourceVersion.equals(targetVersion)) {
+ bundle.update(new BundleInputStream(source.getCurrentEntryStream()));
+ addRollback(new UpdateBundleRunnable(bundle, targetPackage, bundleInfo.getSymbolicName()));
+ }
+ }
+ }
+ catch (BundleException be) {
+ if (isCancelled()) {
+ return;
+ }
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Could not install new bundle '" + name + "'", be);
+ }
+ if (!bundle.getSymbolicName().equals(bundleInfo.getSymbolicName()) || !Version.parseVersion((String)bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION)).equals(bundleInfo.getVersion())) {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Installed/updated bundle version and/or symbolicnames do not match what was installed/updated");
+ }
+ }
+ }
+ catch (IOException e) {
+ throw new DeploymentException(DeploymentException.CODE_OTHER_ERROR, "Problem while reading stream", e);
+ }
+ }
+
+ private static class UninstallBundleRunnable implements Runnable {
+
+ private final Bundle m_bundle;
+
+ public UninstallBundleRunnable(Bundle bundle) {
+ m_bundle = bundle;
+ }
+
+ public void run() {
+ try {
+ m_bundle.uninstall();
+ }
+ catch (BundleException e) {
+ // TODO: log this
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static class UpdateBundleRunnable implements Runnable {
+
+ private final Bundle m_bundle;
+ private final AbstractDeploymentPackage m_targetPackage;
+ private final String m_symbolicName;
+
+ public UpdateBundleRunnable(Bundle bundle, AbstractDeploymentPackage targetPackage, String symbolicName) {
+ m_bundle = bundle;
+ m_targetPackage = targetPackage;
+ m_symbolicName = symbolicName;
+ }
+
+ public void run() {
+ try {
+ m_bundle.update(m_targetPackage.getBundleStream(m_symbolicName));
+ }
+ catch (Exception e) {
+ // TODO: log this
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private final class BundleInputStream extends InputStream {
+ private final InputStream m_inputStream;
+
+ private BundleInputStream(InputStream jarInputStream) {
+ m_inputStream = jarInputStream;
+ }
+
+ public int read() throws IOException {
+ checkCancel();
+ return m_inputStream.read();
+ }
+
+ public int read(byte[] buffer) throws IOException {
+ checkCancel();
+ return m_inputStream.read(buffer);
+ }
+
+ public int read(byte[] buffer, int off, int len) throws IOException {
+ checkCancel();
+ return m_inputStream.read(buffer, off, len);
+ }
+
+ private void checkCancel() throws IOException {
+ if (isCancelled()) {
+ throw new IOException("Stream was cancelled");
+ }
+ }
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/application/ApplicationContext.java b/deploymentadmin/src/main/java/org/osgi/application/ApplicationContext.java
new file mode 100644
index 0000000..c04fcf8
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/application/ApplicationContext.java
@@ -0,0 +1,355 @@
+/*
+ * $Header: /cvshome/build/org.osgi.application/src/org/osgi/application/ApplicationContext.java,v 1.15 2006/07/11 13:19:02 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.application;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+
+/**
+ * <code>ApplicationContext</code> is the access point for an OSGi-aware
+ * application to the features of the OSGi Service Platform. Each application
+ * instance will have its own <code>ApplicationContext</code> instance, which
+ * will not be reused after destorying the corresponding application instace.
+ * <p>
+ * Application instances can obtain their <code>ApplicationContext</code>
+ * using the {@link Framework#getApplicationContext} method.
+ * <p>
+ * The lifecycle of an <code>ApplicationContext</code> instance is bound to
+ * the lifecycle of the corresponding application instance. The
+ * <code>ApplicationContext</code> becomes available when the application is
+ * started and it is invalidated when the application instance is stopped (i.e.
+ * the "stop" method of the application activator object returned).
+ * All method calls (except {@link #getApplicationId()} and
+ * {@link #getInstanceId()}) to an invalidated context object result an
+ * <code>IllegalStateException</code>.
+ *
+ * @see org.osgi.application.Framework
+ */
+public interface ApplicationContext {
+
+ /**
+ * Adds the specified {@link ApplicationServiceListener} object to this context
+ * application instance's list of listeners. The specified <code>referenceName</code> is a
+ * reference name specified in the descriptor of the corresponding application. The registered
+ * <code>listener> will only receive the {@link ApplicationServiceEvent}s realted to the referred service.
+ * <p>
+ * If the <code>listener</code> was already added, calling this method will overwrite the previous
+ * registration.
+ * <p>
+ *
+ * @param listener
+ * The {@link org.osgi.application.ApplicationServiceListener} to be added. It must
+ * not be <code>null</code>
+ * @param referenceName the reference name of a service from the descriptor of the corresponding
+ * application. It must not be <code>null</code>.
+ * @throws java.lang.IllegalStateException
+ * If this context application instance has stopped.
+ * @throws java.lang.NullPointerException If <code>listener</code> or <code>referenceName</code>
+ * is <code>null</code>
+ * @throws java.lang.IllegalArgumentException If there is no service in the
+ * application descriptor with the specified <code>referenceName</code>.
+ */
+ public void addServiceListener(ApplicationServiceListener listener, String referenceName) throws java.lang.IllegalArgumentException;
+
+ /**
+ * Adds the specified {@link ApplicationServiceListener} object to this context
+ * application instance's list of listeners. The <code>referenceNames</code> parameter is an
+ * array of reference name specified in the descriptor of the corresponding application. The registered
+ * <code>listener> will only receive the {@link ApplicationServiceEvent}s realted to the referred
+ * services.
+ * <p>
+ * If the <code>listener</code> was already added, calling this method will overwrite the previous
+ * registration.
+ * <p>
+ *
+ * @param listener
+ * The {@link org.osgi.application.ApplicationServiceListener} to be added. It must not
+ * be <code>null</code>
+ * @param referenceNames and array of service reference names from the descriptor of the corresponding
+ * application. It must not be <code>null</code> and it must not be empty.
+ * @throws java.lang.IllegalStateException
+ * If this context application instance has stopped.
+ * @throws java.lang.NullPointerException If <code>listener</code> or <code>referenceNames</code>
+ * is <code>null</code>
+ * @throws java.lang.IllegalArgumentException If <code>referenceNames</code> array is empty or it
+ * contains unknown references
+ */
+ public void addServiceListener(ApplicationServiceListener listener, String[] referenceNames) throws java.lang.IllegalArgumentException;
+
+ /**
+ * Removes the specified {@link org.osgi.application.ApplicationServiceListener} object from this
+ * context application instances's list of listeners.
+ * <p>
+ * If <code>listener</code> is not contained in this context application
+ * instance's list of listeners, this method does nothing.
+ *
+ * @param listener
+ * The {@link org.osgi.application.ApplicationServiceListener} object to be removed.
+ * @throws java.lang.IllegalStateException
+ * If this context application instance has stopped.
+ */
+ public void removeServiceListener(ApplicationServiceListener listener);
+
+ /**
+ * This method returns the identifier of the corresponding application instace.
+ * This identifier is guarateed to be unique within the scope of the device.
+ *
+ * Note: this method can safely be called on an invalid
+ * <code>ApplicationContext</code> as well.
+ *
+ * @see org.osgi.service.application.ApplicationHandle#getInstanceId()
+ *
+ * @return the unique identifier of the corresponding application instance
+ */
+ public String getInstanceId();
+
+ /**
+ * This method return the identifier of the correspondig application type. This identifier
+ * is the same for the different instances of the same application but it is different for
+ * different application type.
+ * <p>
+ * Note: this method can safely be called on an invalid
+ * <code>ApplicationContext</code> as well.
+ *
+ * @see org.osgi.service.application.ApplicationDescriptor#getApplicationId()
+ *
+ * @return the identifier of the application type.
+ */
+ public String getApplicationId();
+
+ /**
+ * This method returns the service object for the specified
+ * <code>referenceName</code>. If the cardinality of the reference is
+ * 0..n or 1..n and multiple services are bound to the reference, the
+ * service with the highest ranking (as specified in its
+ * {@link org.osgi.framework.Constants#SERVICE_RANKING} property) is returned. If there
+ * is a tie in ranking, the service with the lowest service ID (as specified
+ * in its {@link org.osgi.framework.Constants#SERVICE_ID} property); that is, the
+ * service that was registered first is returned.
+ *
+ * @param referenceName
+ * The name of a reference as specified in a reference element in
+ * this context applications's description. It must not be <code>null</code>
+ * @return A service object for the referenced service or <code>null</code>
+ * if the reference cardinality is 0..1 or 0..n and no bound service
+ * is available.
+ * @throws java.lang.NullPointerException If <code>referenceName</code> is <code>null</code>.
+ * @throws java.lang.IllegalArgumentException If there is no service in the
+ * application descriptor with the specified <code>referenceName</code>.
+ * @throws java.lang.IllegalStateException
+ * If this context application instance has stopped.
+ */
+ public Object locateService(String referenceName);
+
+ /**
+ * This method returns the service objects for the specified
+ * <code>referenceName</code>.
+ *
+ * @param referenceName
+ * The name of a reference as specified in a reference element in
+ * this context applications's description. It must not be
+ * <code>null</code>.
+ * @return An array of service object for the referenced service or
+ * <code>null</code> if the reference cardinality is 0..1 or 0..n
+ * and no bound service is available.
+ * @throws java.lang.NullPointerException If <code>referenceName</code> is <code>null</code>.
+ * @throws java.lang.IllegalArgumentException If there is no service in the
+ * application descriptor with the specified <code>referenceName</code>.
+ * @throws java.lang.IllegalStateException
+ * If this context application instance has stopped.
+ */
+ public Object[] locateServices(String referenceName);
+
+ /**
+ * Returns the startup parameters specified when calling the
+ * {@link org.osgi.service.application.ApplicationDescriptor#launch}
+ * method.
+ * <p>
+ * Startup arguments can be specified as name, value pairs. The name
+ * must be of type {@link java.lang.String}, which must not be
+ * <code>null</code> or empty {@link java.lang.String} (<code>""</code>),
+ * the value can be any object including <code>null</code>.
+ *
+ * @return a {@link java.util.Map} containing the startup arguments.
+ * It can be <code>null</code>.
+ * @throws java.lang.IllegalStateException
+ * If this context application instance has stopped.
+ */
+ public Map getStartupParameters();
+
+ /**
+ * Application can query the service properties of a service object
+ * it is bound to. Application gets bound to a service object when
+ * it fisrt obtains a reference to the service by calling
+ * <code>locateService</code> or <code>locateServices</code> methods.
+ *
+ * @param serviceObject A service object the application is bound to.
+ * It must not be null.
+ * @return The service properties associated with the specified service
+ * object.
+ * @throws NullPointerException if the specified <code>serviceObject</code>
+ * is <code>null</code>
+ * @throws IllegalArgumentException if the application is not
+ * bound to the specified service object or it is not a service
+ * object at all.
+ * @throws java.lang.IllegalStateException
+ * If this context application instance has stopped.
+ */
+ public Map getServiceProperties(Object serviceObject);
+
+
+ /**
+ * Registers the specified service object with the specified properties
+ * under the specified class names into the Framework. A
+ * {@link org.osgi.framework.ServiceRegistration} object is returned. The
+ * {@link org.osgi.framework.ServiceRegistration} object is for the private use of the
+ * application registering the service and should not be shared with other
+ * applications. The registering application is defined to be the context application.
+ * Bundles can locate the service by using either the
+ * {@link org.osgi.framework.BundleContext#getServiceReferences} or
+ * {@link org.osgi.framework.BundleContext#getServiceReference} method. Other applications
+ * can locate this service by using {@link #locateService(String)} or {@link #locateServices(String)}
+ * method, if they declared their dependece on the registered service.
+ *
+ * <p>
+ * An application can register a service object that implements the
+ * {@link org.osgi.framework.ServiceFactory} interface to have more flexibility in providing
+ * service objects to other applications or bundles.
+ *
+ * <p>
+ * The following steps are required to register a service:
+ * <ol>
+ * <li>If <code>service</code> is not a <code>ServiceFactory</code>,
+ * an <code>IllegalArgumentException</code> is thrown if
+ * <code>service</code> is not an <code>instanceof</code> all the
+ * classes named.
+ * <li>The Framework adds these service properties to the specified
+ * <code>Dictionary</code> (which may be <code>null</code>): a property
+ * named {@link org.osgi.framework.Constants#SERVICE_ID} identifying the registration number of
+ * the service and a property named {@link org.osgi.framework.Constants#OBJECTCLASS} containing
+ * all the specified classes. If any of these properties have already been
+ * specified by the registering bundle, their values will be overwritten by
+ * the Framework.
+ * <li>The service is added to the Framework service registry and may now
+ * be used by others.
+ * <li>A service event of type {@link org.osgi.framework.ServiceEvent#REGISTERED} is
+ * fired. This event triggers the corresponding {@link ApplicationServiceEvent} to be
+ * delivered to the applications that registered the appropriate listener.
+ * <li>A <code>ServiceRegistration</code> object for this registration is
+ * returned.
+ * </ol>
+ *
+ * @param clazzes The class names under which the service can be located.
+ * The class names in this array will be stored in the service's
+ * properties under the key {@link org.osgi.framework.Constants#OBJECTCLASS}.
+ * This parameter must not be <code>null</code>.
+ * @param service The service object or a <code>ServiceFactory</code>
+ * object.
+ * @param properties The properties for this service. The keys in the
+ * properties object must all be <code>String</code> objects. See
+ * {@link org.osgi.framework.Constants} for a list of standard service property keys.
+ * Changes should not be made to this object after calling this
+ * method. To update the service's properties the
+ * {@link org.osgi.framework.ServiceRegistration#setProperties} method must be called.
+ * The set of properties may be <code>null</code> if the service
+ * has no properties.
+ *
+ * @return A {@link org.osgi.framework.ServiceRegistration} object for use by the application
+ * registering the service to update the service's properties or to
+ * unregister the service.
+ *
+ * @throws java.lang.IllegalArgumentException If one of the following is
+ * true:
+ * <ul>
+ * <li><code>service</code> is <code>null</code>.
+ * <li><code>service</code> is not a <code>ServiceFactory</code>
+ * object and is not an instance of all the named classes in
+ * <code>clazzes</code>.
+ * <li><code>properties</code> contains case variants of the same
+ * key name.
+ * </ul>
+ * @throws NullPointerException if <code>clazzes</code> is <code>null</code>
+ *
+ * @throws java.lang.SecurityException If the caller does not have the
+ * <code>ServicePermission</code> to register the service for all
+ * the named classes and the Java Runtime Environment supports
+ * permissions.
+ *
+ * @throws java.lang.IllegalStateException If this ApplicationContext is no
+ * longer valid.
+ *
+ * @see org.osgi.framework.BundleContext#registerService(java.lang.String[], java.lang.Object, java.util.Dictionary)
+ * @see org.osgi.framework.ServiceRegistration
+ * @see org.osgi.framework.ServiceFactory
+ */
+ public ServiceRegistration registerService(String[] clazzes,
+ Object service, Dictionary properties);
+
+ /**
+ * Registers the specified service object with the specified properties
+ * under the specified class name with the Framework.
+ *
+ * <p>
+ * This method is otherwise identical to
+ * {@link #registerService(java.lang.String[], java.lang.Object,
+ * java.util.Dictionary)} and is provided as a convenience when
+ * <code>service</code> will only be registered under a single class name.
+ * Note that even in this case the value of the service's
+ * {@link Constants#OBJECTCLASS} property will be an array of strings,
+ * rather than just a single string.
+ *
+ * @param clazz The class name under which the service can be located. It
+ * must not be <code>null</code>
+ * @param service The service object or a <code>ServiceFactory</code>
+ * object.
+ * @param properties The properties for this service.
+ *
+ * @return A <code>ServiceRegistration</code> object for use by the application
+ * registering the service to update the service's properties or to
+ * unregister the service.
+ *
+ * @throws java.lang.IllegalArgumentException If one of the following is
+ * true:
+ * <ul>
+ * <li><code>service</code> is <code>null</code>.
+ * <li><code>service</code> is not a <code>ServiceFactory</code>
+ * object and is not an instance of the named class in
+ * <code>clazz</code>.
+ * <li><code>properties</code> contains case variants of the same
+ * key name.
+ * </ul>
+ * @throws NullPointerException if <code>clazz</code> is <code>null</code>
+ *
+ * @throws java.lang.SecurityException If the caller does not have the
+ * <code>ServicePermission</code> to register the service
+ * the named class and the Java Runtime Environment supports
+ * permissions.
+ *
+ * @throws java.lang.IllegalStateException If this ApplicationContext is no
+ * longer valid.
+ * @see #registerService(java.lang.String[], java.lang.Object,
+ * java.util.Dictionary)
+ */
+ public ServiceRegistration registerService(String clazz, Object service,
+ Dictionary properties);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/application/ApplicationServiceEvent.java b/deploymentadmin/src/main/java/org/osgi/application/ApplicationServiceEvent.java
new file mode 100644
index 0000000..cfb4cec
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/application/ApplicationServiceEvent.java
@@ -0,0 +1,83 @@
+/*
+ * $Header: /cvshome/build/org.osgi.application/src/org/osgi/application/ApplicationServiceEvent.java,v 1.6 2006/07/11 13:19:02 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.application;
+
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * An event from the Framework describing a service lifecycle change.
+ * <p>
+ * <code>ApplicationServiceEvent</code> objects are delivered to a
+ * <code>ApplicationServiceListener</code> objects when a change occurs in this service's
+ * lifecycle. The delivery of an <code>ApplicationServiceEvent</code> is
+ * always triggered by a {@link org.osgi.framework.ServiceEvent}.
+ * <code>ApplicationServiceEvent</code> extends the content of <code>ServiceEvent</code>
+ * with the service object the event is referring to as applications has no means to
+ * find the corresponding service object for a {@link org.osgi.framework.ServiceReference}.
+ * A type code is used to identify the event type for future
+ * extendability. The available type codes are defined in {@link org.osgi.framework.ServiceEvent}.
+ *
+ * <p>
+ * OSGi Alliance reserves the right to extend the set of types.
+ *
+ * @see org.osgi.framework.ServiceEvent
+ * @see ApplicationServiceListener
+ *
+ * @version $Revision: 1.6 $
+ */
+public class ApplicationServiceEvent extends ServiceEvent {
+
+ private static final long serialVersionUID = -4762149286971897323L;
+ final Object serviceObject;
+
+ /**
+ * Creates a new application service event object.
+ *
+ * @param type The event type. Available type codes are defines in
+ * {@link org.osgi.framework.ServiceEvent}
+ * @param reference A <code>ServiceReference</code> object to the service
+ * that had a lifecycle change. This reference will be used as the <code>source</code>
+ * in the {@link java.util.EventObject} baseclass, therefore, it must not be
+ * null.
+ * @param serviceObject The service object bound to this application instance. It can
+ * be <code>null</code> if this application is not bound to this service yet.
+ * @throws IllegalArgumentException if the specified <code>reference</code> is null.
+ */
+ public ApplicationServiceEvent(int type, ServiceReference reference, Object serviceObject) {
+ super(type, reference);
+ this.serviceObject = serviceObject;
+ }
+
+ /**
+ * This method returns the service object of this service bound to the listener
+ * application instace. A service object becomes bound to the application when it
+ * first obtains a service object reference to that service by calling the
+ * <code>ApplicationContext.locateService</code> or <code>locateServices</code>
+ * methods. If the application is not bound to the service yet, this method returns
+ * <code>null</code>.
+ *
+ * @return the service object bound to the listener application or <code>null</code>
+ * if it isn't bound to this service yet.
+ */
+ public Object getServiceObject() {
+ return this.serviceObject;
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/application/ApplicationServiceListener.java b/deploymentadmin/src/main/java/org/osgi/application/ApplicationServiceListener.java
new file mode 100644
index 0000000..1a7d36f
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/application/ApplicationServiceListener.java
@@ -0,0 +1,68 @@
+/*
+ * $Header: /cvshome/build/org.osgi.application/src/org/osgi/application/ApplicationServiceListener.java,v 1.6 2006/07/12 21:21:34 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.application;
+
+import java.util.EventListener;
+
+import org.osgi.framework.*;
+
+/**
+ * An <code>ApplicationServiceEvent</code> listener. When a
+ * <code>ServiceEvent</code> is
+ * fired, it is converted to an <code>ApplictionServiceEvent</code>
+ * and it is synchronously delivered to an <code>ApplicationServiceListener</code>.
+ *
+ * <p>
+ * <code>ApplicationServiceListener</code> is a listener interface that may be
+ * implemented by an application developer.
+ * <p>
+ * An <code>ApplicationServiceListener</code> object is registered with the Framework
+ * using the <code>ApplicationContext.addServiceListener</code> method.
+ * <code>ApplicationServiceListener</code> objects are called with an
+ * <code>ApplicationServiceEvent</code> object when a service is registered, modified, or
+ * is in the process of unregistering.
+ *
+ * <p>
+ * <code>ApplicationServiceEvent</code> object delivery to
+ * <code>ApplicationServiceListener</code>
+ * objects is filtered by the filter specified when the listener was registered.
+ * If the Java Runtime Environment supports permissions, then additional
+ * filtering is done. <code>ApplicationServiceEvent</code> objects are only delivered to
+ * the listener if the application which defines the listener object's class has the
+ * appropriate <code>ServicePermission</code> to get the service using at
+ * least one of the named classes the service was registered under, and the application
+ * specified its dependece on the corresponding service in the application metadata.
+ *
+ * <p>
+ * <code>ApplicationServiceEvent</code> object delivery to <code>ApplicationServiceListener</code>
+ * objects is further filtered according to package sources as defined in
+ * {@link ServiceReference#isAssignableTo(Bundle, String)}.
+ *
+ * @version $Revision: 1.6 $
+ * @see ApplicationServiceEvent
+ * @see ServicePermission
+ */
+public interface ApplicationServiceListener extends EventListener {
+ /**
+ * Receives notification that a service has had a lifecycle change.
+ *
+ * @param event The <code>ApplicationServiceEvent</code> object.
+ */
+ public void serviceChanged(ApplicationServiceEvent event);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/application/Framework.java b/deploymentadmin/src/main/java/org/osgi/application/Framework.java
new file mode 100644
index 0000000..4696115
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/application/Framework.java
@@ -0,0 +1,59 @@
+/*
+ * $Header: /cvshome/build/org.osgi.application/src/org/osgi/application/Framework.java,v 1.9 2006/07/11 13:19:02 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.application;
+
+import java.util.Hashtable;
+
+/**
+ * Using this class, OSGi-aware applications can obtain their {@link ApplicationContext}.
+ *
+ */
+public final class Framework {
+
+ private Framework() { }
+
+ private static Hashtable appContextHash;
+
+ /**
+ * This method needs an argument, an object that represents the application instance.
+ * An application consists of a set of object, however there is a single object, which
+ * is used by the corresponding application container to manage the lifecycle on the
+ * application instance. The lifetime of this object equals the lifetime of
+ * the application instance; therefore, it is suitable to represent the instance.
+ * <P>
+ * The returned {@link ApplicationContext} object is singleton for the
+ * specified application instance. Subsequent calls to this method with the same
+ * application instance must return the same context object
+ *
+ * @param applicationInstance is the activator object of an application instance
+ * @throws java.lang.NullPointerException If <code>applicationInstance</code>
+ * is <code>null</code>
+ * @throws java.lang.IllegalArgumentException if called with an object that is not
+ * the activator object of an application.
+ * @return the {@link ApplicationContext} of the specified application instance.
+ */
+ public static ApplicationContext getApplicationContext(Object applicationInstance) {
+ if( applicationInstance == null )
+ throw new NullPointerException( "Instance cannot be null!" );
+ ApplicationContext appContext = (ApplicationContext)appContextHash.get( applicationInstance );
+ if( appContext == null )
+ throw new IllegalArgumentException( "ApplicationContext not found!" );
+ return appContext;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/application/package.html b/deploymentadmin/src/main/java/org/osgi/application/package.html
new file mode 100644
index 0000000..e3386d0
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/application/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.application/src/org/osgi/application/package.html,v 1.2 2006/07/12 21:07:09 hargrave Exp $ -->
+<BODY>
+<p>Foreign Application Package Version 1.0.
+<p>Bundles wishing to use this package must list the package
+in the Import-Package header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.application; version=1.0
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/application/packageinfo b/deploymentadmin/src/main/java/org/osgi/application/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/application/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationAdminPermission.java b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationAdminPermission.java
new file mode 100644
index 0000000..5615148
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationAdminPermission.java
@@ -0,0 +1,407 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.application/src/org/osgi/service/application/ApplicationAdminPermission.java,v 1.34 2006/07/12 21:22:11 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.application;
+
+import java.security.Permission;
+import java.util.*;
+
+import org.osgi.framework.*;
+
+/**
+ * This class implements permissions for manipulating applications and
+ * their instances.
+ * <P>
+ * ApplicationAdminPermission can be targeted to applications that matches the
+ * specified filter.
+ * <P>
+ * ApplicationAdminPermission may be granted for different actions:
+ * <code>lifecycle</code>, <code>schedule</code> and <code>lock</code>.
+ * The permission <code>schedule</code> implies the permission
+ * <code>lifecycle</code>.
+ */
+public class ApplicationAdminPermission extends Permission {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Allows the lifecycle management of the target applications.
+ */
+ public static final String LIFECYCLE_ACTION = "lifecycle";
+
+ /**
+ * Allows scheduling of the target applications. The permission to
+ * schedule an application implies that the scheduler can also
+ * manage the lifecycle of that application i.e. <code>schedule</code>
+ * implies <code>lifecycle</code>
+ */
+ public static final String SCHEDULE_ACTION = "schedule";
+
+ /**
+ * Allows setting/unsetting the locking state of the target applications.
+ */
+ public static final String LOCK_ACTION = "lock";
+
+ private ApplicationDescriptor applicationDescriptor;
+
+ /**
+ * Constructs an ApplicationAdminPermission. The <code>filter</code>
+ * specifies the target application. The <code>filter</code> is an
+ * LDAP-style filter, the recognized properties are <code>signer</code>
+ * and <code>pid</code>. The pattern specified in the <code>signer</code>
+ * is matched with the Distinguished Name chain used to sign the application.
+ * Wildcards in a DN are not matched according to the filter string rules,
+ * but according to the rules defined for a DN chain. The attribute
+ * <code>pid</code> is matched with the PID of the application according to
+ * the filter string rules.
+ * <p>
+ * If the <code>filter</code> is <code>null</code> then it matches
+ * <code>"*"</code>. If
+ * <code>actions</code> is <code>"*"</code> then it identifies all the
+ * possible actions.
+ *
+ * @param filter
+ * filter to identify application. The value <code>null</code>
+ * is equivalent to <code>"*"</code> and it indicates "all application".
+ * @param actions
+ * comma-separated list of the desired actions granted on the
+ * applications or "*" means all the actions. It must not be
+ * <code>null</code>. The order of the actions in the list is
+ * not significant.
+ * @throws InvalidSyntaxException
+ * is thrown if the specified <code>filter</code> is not syntactically
+ * correct.
+ *
+ * @exception NullPointerException
+ * is thrown if the actions parameter is <code>null</code>
+ *
+ * @see ApplicationDescriptor
+ * @see org.osgi.framework.AdminPermission
+ */
+ public ApplicationAdminPermission(String filter, String actions) throws InvalidSyntaxException {
+ super(filter == null ? "*" : filter);
+
+ if( filter == null )
+ filter = "*";
+
+ if( actions == null )
+ throw new NullPointerException( "Action string cannot be null!" );
+
+ this.applicationDescriptor = null;
+ this.filter = (filter == null ? "*" : filter);
+ this.actions = actions;
+
+ if( !filter.equals( "*" ) && !filter.equals( "<<SELF>>" ) )
+ FrameworkUtil.createFilter( this.filter ); // check if the filter is valid
+ init();
+ }
+
+ /**
+ * This contructor should be used when creating <code>ApplicationAdminPermission</code>
+ * instance for <code>checkPermission</code> call.
+ * @param application the tareget of the operation, it must not be <code>null</code>
+ * @param actions the required operation. it must not be <code>null</code>
+ * @throws NullPointerException if any of the arguments is null.
+ */
+ public ApplicationAdminPermission(ApplicationDescriptor application, String actions) {
+ super(application.getApplicationId());
+
+ if( application == null || actions == null )
+ throw new NullPointerException( "ApplicationDescriptor and action string cannot be null!" );
+
+ this.filter = application.getApplicationId();
+ this.applicationDescriptor = application;
+ this.actions = actions;
+
+ init();
+ }
+
+ /**
+ * This method can be used in the {@link java.security.ProtectionDomain}
+ * implementation in the <code>implies</code> method to insert the
+ * application ID of the current application into the permission being
+ * checked. This enables the evaluation of the
+ * <code><<SELF>></code> pseudo targets.
+ * @param applicationId the ID of the current application.
+ * @return the permission updated with the ID of the current application
+ */
+ public ApplicationAdminPermission setCurrentApplicationId(String applicationId) {
+ ApplicationAdminPermission newPerm = null;
+
+ if( this.applicationDescriptor == null ) {
+ try {
+ newPerm = new ApplicationAdminPermission( this.filter, this.actions );
+ }catch( InvalidSyntaxException e ) {
+ throw new RuntimeException( "Internal error" ); /* this can never happen */
+ }
+ }
+ else
+ newPerm = new ApplicationAdminPermission( this.applicationDescriptor, this.actions );
+
+ newPerm.applicationID = applicationId;
+
+ return newPerm;
+ }
+
+ /**
+ * Checks if the specified <code>permission</code> is implied by this permission.
+ * The method returns true under the following conditions:
+ * <UL>
+ * <LI> This permission was created by specifying a filter (see {@link #ApplicationAdminPermission(String, String)})
+ * <LI> The implied <code>otherPermission</code> was created for a particular {@link ApplicationDescriptor}
+ * (see {@link #ApplicationAdminPermission(ApplicationDescriptor, String)})
+ * <LI> The <code>filter</code> of this permission mathes the <code>ApplicationDescriptor</code> specified
+ * in the <code>otherPermission</code>. If the filter in this permission is the
+ * <code><<SELF>></code> pseudo target, then the currentApplicationId set in the
+ * <code>otherPermission</code> is compared to the application Id of the target
+ * <code>ApplicationDescriptor</code>.
+ * <LI> The list of permitted actions in this permission contains all actions required in the
+ * <code>otherPermission</code>
+ * </UL>
+ * Otherwise the method returns false.
+ * @param otherPermission the implied permission
+ * @return true if this permission implies the <code>otherPermission</code>, false otherwise.
+ */
+ public boolean implies(Permission otherPermission) {
+ if( otherPermission == null )
+ return false;
+
+ if(!(otherPermission instanceof ApplicationAdminPermission))
+ return false;
+
+ ApplicationAdminPermission other = (ApplicationAdminPermission) otherPermission;
+
+ if( !filter.equals("*") ) {
+ if( other.applicationDescriptor == null )
+ return false;
+
+ if( filter.equals( "<<SELF>>") ) {
+ if( other.applicationID == null )
+ return false; /* it cannot be, this might be a bug */
+
+ if( !other.applicationID.equals( other.applicationDescriptor.getApplicationId() ) )
+ return false;
+ }
+ else {
+ Hashtable props = new Hashtable();
+ props.put( "pid", other.applicationDescriptor.getApplicationId() );
+ props.put( "signer", new SignerWrapper( other.applicationDescriptor ) );
+
+ Filter flt = getFilter();
+ if( flt == null )
+ return false;
+
+ if( !flt.match( props ) )
+ return false;
+ }
+ }
+
+ if( !actionsVector.containsAll( other.actionsVector ) )
+ return false;
+
+ return true;
+ }
+
+ public boolean equals(Object with) {
+ if( with == null || !(with instanceof ApplicationAdminPermission) )
+ return false;
+
+ ApplicationAdminPermission other = (ApplicationAdminPermission)with;
+
+ // Compare actions:
+ if( other.actionsVector.size() != actionsVector.size() )
+ return false;
+
+ for( int i=0; i != actionsVector.size(); i++ )
+ if( !other.actionsVector.contains( actionsVector.get( i ) ) )
+ return false;
+
+
+ return equal(this.filter, other.filter ) && equal(this.applicationDescriptor, other.applicationDescriptor)
+ && equal(this.applicationID, other.applicationID);
+ }
+
+ /**
+ * Compares parameters for equality. If both object are null, they are considered
+ * equal.
+ * @param a object to compare
+ * @param b other object to compare
+ * @return true if both objects are equal or both are null
+ */
+ private static boolean equal(Object a, Object b) {
+ // This equation is true if both references are null or both point
+ // to the same object. In both cases they are considered as equal.
+ if( a == b ) {
+ return true;
+ }
+
+ return a.equals(b);
+ }
+
+ public int hashCode() {
+ int hc = 0;
+ for( int i=0; i != actionsVector.size(); i++ )
+ hc ^= ((String)actionsVector.get( i )).hashCode();
+ hc ^= (null == this.filter )? 0 : this.filter.hashCode();
+ hc ^= (null == this.applicationDescriptor) ? 0 : this.applicationDescriptor.hashCode();
+ hc ^= (null == this.applicationID) ? 0 : this.applicationID.hashCode();
+ return hc;
+ }
+
+ /**
+ * Returns the actions of this permission.
+ * @return the actions specified when this permission was created
+ */
+ public String getActions() {
+ return actions;
+ }
+
+ private String applicationID;
+
+ private static final Vector ACTIONS = new Vector();
+ private Vector actionsVector;
+ private final String filter;
+ private final String actions;
+ private Filter appliedFilter = null;
+
+ static {
+ ACTIONS.add(LIFECYCLE_ACTION);
+ ACTIONS.add(SCHEDULE_ACTION);
+ ACTIONS.add(LOCK_ACTION);
+ }
+
+ private static Vector actionsVector(String actions) {
+ Vector v = new Vector();
+ StringTokenizer t = new StringTokenizer(actions.toUpperCase(), ",");
+ while (t.hasMoreTokens()) {
+ String action = t.nextToken().trim();
+ v.add(action.toLowerCase());
+ }
+
+ if( v.contains( SCHEDULE_ACTION ) && !v.contains( LIFECYCLE_ACTION ) )
+ v.add( LIFECYCLE_ACTION );
+
+ return v;
+ }
+
+
+ private static class SignerWrapper extends Object {
+ private String pattern;
+ private ApplicationDescriptor appDesc;
+
+ public SignerWrapper(String pattern) {
+ this.pattern = pattern;
+ }
+
+ SignerWrapper(ApplicationDescriptor appDesc) {
+ this.appDesc = appDesc;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof SignerWrapper))
+ return false;
+ SignerWrapper other = (SignerWrapper) o;
+ ApplicationDescriptor matchAppDesc = (ApplicationDescriptor) (appDesc != null ? appDesc : other.appDesc);
+ String matchPattern = appDesc != null ? other.pattern : pattern;
+ return matchAppDesc.matchDNChain(matchPattern);
+ }
+ }
+
+ private void init() {
+ actionsVector = actionsVector( actions );
+
+ if ( actions.equals("*") )
+ actionsVector = actionsVector( LIFECYCLE_ACTION + "," + SCHEDULE_ACTION + "," + LOCK_ACTION );
+ else if (!ACTIONS.containsAll(actionsVector))
+ throw new IllegalArgumentException("Illegal action!");
+
+ applicationID = null;
+ }
+
+ private Filter getFilter() {
+ String transformedFilter = filter;
+
+ if (appliedFilter == null) {
+ try {
+ int pos = filter.indexOf("signer"); //$NON-NLS-1$
+ if (pos != -1){
+
+ //there may be a signer attribute
+ StringBuffer filterBuf = new StringBuffer(filter);
+ int numAsteriskFound = 0; //use as offset to replace in buffer
+
+ int walkbackPos; //temp pos
+
+ //find occurences of (signer= and escape out *'s
+ while (pos != -1) {
+
+ //walk back and look for '(' to see if this is an attr
+ walkbackPos = pos-1;
+
+ //consume whitespace
+ while(walkbackPos >= 0 && Character.isWhitespace(filter.charAt(walkbackPos))) {
+ walkbackPos--;
+ }
+ if (walkbackPos <0) {
+ //filter is invalid - FilterImpl will throw error
+ break;
+ }
+
+ //check to see if we have unescaped '('
+ if (filter.charAt(walkbackPos) != '(' || (walkbackPos > 0 && filter.charAt(walkbackPos-1) == '\\')) {
+ //'(' was escaped or not there
+ pos = filter.indexOf("signer",pos+6); //$NON-NLS-1$
+ continue;
+ }
+ pos+=6; //skip over 'signer'
+
+ //found signer - consume whitespace before '='
+ while (Character.isWhitespace(filter.charAt(pos))) {
+ pos++;
+ }
+
+ //look for '='
+ if (filter.charAt(pos) != '=') {
+ //attr was signerx - keep looking
+ pos = filter.indexOf("signer",pos); //$NON-NLS-1$
+ continue;
+ }
+ pos++; //skip over '='
+
+ //found signer value - escape '*'s
+ while (!(filter.charAt(pos) == ')' && filter.charAt(pos-1) != '\\')) {
+ if (filter.charAt(pos) == '*') {
+ filterBuf.insert(pos+numAsteriskFound,'\\');
+ numAsteriskFound++;
+ }
+ pos++;
+ }
+
+ //end of signer value - look for more?
+ pos = filter.indexOf("signer",pos); //$NON-NLS-1$
+ } //end while (pos != -1)
+ transformedFilter = filterBuf.toString();
+ } //end if (pos != -1)
+
+ appliedFilter = FrameworkUtil.createFilter( transformedFilter );
+ } catch (InvalidSyntaxException e) {
+ //we will return null
+ }
+ }
+ return appliedFilter;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationDescriptor.java b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationDescriptor.java
new file mode 100644
index 0000000..4764bd7
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationDescriptor.java
@@ -0,0 +1,714 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.application/src/org/osgi/service/application/ApplicationDescriptor.java,v 1.61 2006/07/10 12:02:31 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.application;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.*;
+import java.util.Map;
+
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * An OSGi service that represents an installed application and stores
+ * information about it. The application descriptor can be used for instance
+ * creation.
+ */
+
+public abstract class ApplicationDescriptor {
+ /*
+ * NOTE: An implementor may also choose to replace this class in
+ * their distribution with a class that directly interfaces with the
+ * org.osgi.service.application implementation. This replacement class MUST NOT alter the
+ * public/protected signature of this class.
+ */
+
+ /**
+ * The property key for the localized name of the application.
+ */
+ public static final String APPLICATION_NAME = "application.name";
+
+ /**
+ * The property key for the localized icon of the application.
+ */
+ public static final String APPLICATION_ICON = "application.icon";
+
+ /**
+ * The property key for the unique identifier (PID) of the application.
+ */
+ public static final String APPLICATION_PID = Constants.SERVICE_PID;
+
+ /**
+ * The property key for the version of the application.
+ */
+ public static final String APPLICATION_VERSION = "application.version";
+
+ /**
+ * The property key for the name of the application vendor.
+ */
+ public static final String APPLICATION_VENDOR = Constants.SERVICE_VENDOR;
+
+
+ /**
+ * The property key for the visibility property of the application.
+ */
+ public static final String APPLICATION_VISIBLE = "application.visible";
+
+ /**
+ * The property key for the launchable property of the application.
+ */
+ public static final String APPLICATION_LAUNCHABLE = "application.launchable";
+
+ /**
+ * The property key for the locked property of the application.
+ */
+ public static final String APPLICATION_LOCKED = "application.locked";
+
+ /**
+ * The property key for the localized description of the application.
+ */
+ public static final String APPLICATION_DESCRIPTION = "application.description";
+
+ /**
+ * The property key for the localized documentation of the application.
+ */
+ public static final String APPLICATION_DOCUMENTATION = "application.documentation";
+
+ /**
+ * The property key for the localized copyright notice of the application.
+ */
+ public static final String APPLICATION_COPYRIGHT = "application.copyright";
+
+ /**
+ * The property key for the localized license of the application.
+ */
+ public static final String APPLICATION_LICENSE = "application.license";
+
+ /**
+ * The property key for the application container of the application.
+ */
+ public static final String APPLICATION_CONTAINER = "application.container";
+
+ /**
+ * The property key for the location of the application.
+ */
+ public static final String APPLICATION_LOCATION = "application.location";
+
+
+ private final String pid;
+
+
+ /**
+ * Constructs the <code>ApplicationDescriptor</code>.
+ *
+ * @param applicationId
+ * The identifier of the application. Its value is also available
+ * as the <code>service.pid</code> service property of this
+ * <code>ApplicationDescriptor</code> service. This parameter must not
+ * be <code>null</code>.
+ * @throws NullPointerException if the specified <code>applicationId</code> is null.
+ */
+ protected ApplicationDescriptor(String applicationId) {
+ if(null == applicationId ) {
+ throw new NullPointerException("Application ID must not be null!");
+ }
+
+ this.pid = applicationId;
+ try {
+ delegate = new Delegate();
+ delegate.setApplicationDescriptor( this, applicationId );
+ }
+ catch (Exception e) {
+ // Too bad ...
+ e.printStackTrace();
+ System.err
+ .println("No implementation available for ApplicationDescriptor, property is: "
+ + Delegate.cName);
+ }
+ }
+
+ /**
+ * Returns the identifier of the represented application.
+ *
+ * @return the identifier of the represented application
+ */
+ public final String getApplicationId() {
+ return pid;
+ }
+
+ /**
+ * This method verifies whether the specified <code>pattern</code>
+ * matches the Distinguished Names of any of the certificate chains
+ * used to authenticate this application.
+ * <P>
+ * The <code>pattern</code> must adhere to the
+ * syntax defined in {@link org.osgi.service.application.ApplicationAdminPermission}
+ * for signer attributes.
+ * <p>
+ * This method is used by {@link ApplicationAdminPermission#implies(java.security.Permission)} method
+ * to match target <code>ApplicationDescriptor</code> and filter.
+ *
+ * @param pattern a pattern for a chain of Distinguished Names. It must not be null.
+ * @return <code>true</code> if the specified pattern matches at least
+ * one of the certificate chains used to authenticate this application
+ * @throws NullPointerException if the specified <code>pattern</code> is null.
+ * @throws IllegalStateException if the application descriptor was
+ * unregistered
+ */
+ public abstract boolean matchDNChain( String pattern );
+
+ /**
+ * Returns the properties of the application descriptor as key-value pairs.
+ * The return value contains the locale aware and unaware properties as
+ * well. The returned <code>Map</code> will include the service
+ * properties of this <code>ApplicationDescriptor</code> as well.
+ * <p>
+ * This method will call the <code>getPropertiesSpecific</code> method
+ * to enable the container implementation to insert application model and/or
+ * container implementation specific properties.
+ * <P>
+ * The returned {@link java.util.Map} will contain the standard OSGi service
+ * properties as well
+ * (e.g. service.id, service.vendor etc.) and specialized application
+ * descriptors may offer further service properties. The returned Map contains
+ * a snapshot of the properties. It will not reflect further changes in the
+ * property values nor will the update of the Map change the corresponding
+ * service property.
+ *
+ * @param locale
+ * the locale string, it may be null, the value null means the
+ * default locale. If the provided locale is the empty String
+ * (<code>""</code>)then raw (non-localized) values are returned.
+ *
+ * @return copy of the service properties of this application descriptor service,
+ * according to the specified locale. If locale is null then the
+ * default locale's properties will be returned. (Since service
+ * properties are always exist it cannot return null.)
+ *
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ */
+ public final Map getProperties(String locale) {
+ Map props = getPropertiesSpecific( locale );
+
+ /* currently the ApplicationDescriptor manages the load/save of locking */
+ boolean isLocked = delegate.isLocked(); // the real locking state
+ Boolean containerLocked = (Boolean)props.remove( APPLICATION_LOCKED );
+ if( containerLocked != null && containerLocked.booleanValue() != isLocked ) {
+ try {
+ if( isLocked ) /* if the container's information is not correct */
+ lockSpecific(); /* about the locking state (after loading the lock states) */
+ else
+ unlockSpecific();
+ }catch( Exception e ) {}
+ }
+ /* replace the container's lock with the application model's lock, that's the correct */
+ props.put( APPLICATION_LOCKED, new Boolean( isLocked ) );
+ return props;
+ }
+
+ /**
+ * Container implementations can provide application model specific
+ * and/or container implementation specific properties via this
+ * method.
+ *
+ * Localizable properties must be returned localized if the provided
+ * <code>locale</code> argument is not the empty String. The value
+ * <code>null</code> indicates to use the default locale, for other
+ * values the specified locale should be used.
+ *
+ * The returned {@link java.util.Map} must contain the standard OSGi service
+ * properties as well
+ * (e.g. service.id, service.vendor etc.) and specialized application
+ * descriptors may offer further service properties.
+ * The returned <code>Map</code>
+ * contains a snapshot of the properties. It will not reflect further changes in the
+ * property values nor will the update of the Map change the corresponding
+ * service property.
+
+ * @param locale the locale to be used for localizing the properties.
+ * If <code>null</code> the default locale should be used. If it is
+ * the empty String (<code>""</code>) then raw (non-localized) values
+ * should be returned.
+ *
+ * @return the application model specific and/or container implementation
+ * specific properties of this application descriptor.
+ *
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ */
+ protected abstract Map getPropertiesSpecific(String locale);
+
+ /**
+ * Launches a new instance of an application. The <code>args</code> parameter specifies
+ * the startup parameters for the instance to be launched, it may be null.
+ * <p>
+ * The following steps are made:
+ * <UL>
+ * <LI>Check for the appropriate permission.
+ * <LI>Check the locking state of the application. If locked then return
+ * null otherwise continue.
+ * <LI>Calls the <code>launchSpecific()</code> method to create and start an application
+ * instance.
+ * <LI>Returns the <code>ApplicationHandle</code> returned by the
+ * launchSpecific()
+ * </UL>
+ * The caller has to have ApplicationAdminPermission(applicationPID,
+ * "launch") in order to be able to perform this operation.
+ * <P>
+ * The <code>Map</code> argument of the launch method contains startup
+ * arguments for the
+ * application. The keys used in the Map must be non-null, non-empty <code>String<code>
+ * objects. They can be standard or application
+ * specific. OSGi defines the <code>org.osgi.triggeringevent</code>
+ * key to be used to
+ * pass the triggering event to a scheduled application, however
+ * in the future it is possible that other well-known keys will be defined.
+ * To avoid unwanted clashes of keys, the following rules should be applied:
+ * <ul>
+ * <li>The keys starting with the dash (-) character are application
+ * specific, no well-known meaning should be associated with them.</li>
+ * <li>Well-known keys should follow the reverse domain name based naming.
+ * In particular, the keys standardized in OSGi should start with
+ * <code>org.osgi.</code>.</li>
+ * </ul>
+ * <P>
+ * The method is synchonous, it return only when the application instance was
+ * successfully started or the attempt to start it failed.
+ * <P>
+ * This method never returns <code>null</code>. If launching an application fails,
+ * the appropriate exception is thrown.
+ *
+ * @param arguments
+ * Arguments for the newly launched application, may be null
+ *
+ * @return the registered ApplicationHandle, which represents the newly
+ * launched application instance. Never returns <code>null</code>.
+ *
+ * @throws SecurityException
+ * if the caller doesn't have "lifecycle"
+ * ApplicationAdminPermission for the application.
+ * @throws ApplicationException
+ * if starting the application failed
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ * @throws IllegalArgumentException
+ * if the specified <code>Map</code> contains invalid keys
+ * (null objects, empty <code>String</code> or a key that is not
+ * <code>String</code>)
+ */
+ public final ApplicationHandle launch(Map arguments)
+ throws ApplicationException {
+ try {
+ delegate.launch(arguments);
+ }catch( SecurityException se ) {
+ isLaunchableSpecific(); /* check whether the bundle was uninstalled */
+ /* if yes, throws IllegalStateException */
+ throw se; /* otherwise throw the catched SecurityException */
+ }
+ if( !isLaunchableSpecific() )
+ throw new ApplicationException(ApplicationException.APPLICATION_NOT_LAUNCHABLE,
+ "Cannot launch the application!");
+ try {
+ return launchSpecific(arguments);
+ } catch(IllegalStateException ise) {
+ throw ise;
+ } catch(SecurityException se) {
+ throw se;
+ } catch( ApplicationException ae) {
+ throw ae;
+ } catch(Exception t) {
+ throw new ApplicationException(ApplicationException.APPLICATION_INTERNAL_ERROR, t);
+ }
+ }
+
+ /**
+ * Called by launch() to create and start a new instance in an application
+ * model specific way. It also creates and registeres the application handle
+ * to represent the newly created and started instance and registeres it.
+ * The method is synchonous, it return only when the application instance was
+ * successfully started or the attempt to start it failed.
+ * <P>
+ * This method must not return <code>null</code>. If launching the application
+ * failed, and exception must be thrown.
+ *
+ * @param arguments
+ * the startup parameters of the new application instance, may be
+ * null
+ *
+ * @return the registered application model
+ * specific application handle for the newly created and started
+ * instance.
+ *
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ * @throws Exception
+ * if any problem occures.
+ */
+ protected abstract ApplicationHandle launchSpecific(Map arguments)
+ throws Exception;
+
+ /**
+ * This method is called by launch() to verify that according to the
+ * container, the application is launchable.
+ *
+ * @return true, if the application is launchable according to the
+ * container, false otherwise.
+ *
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ */
+ protected abstract boolean isLaunchableSpecific();
+
+ /**
+ * Schedules the application at a specified event. Schedule information
+ * should not get lost even if the framework or the device restarts so it
+ * should be stored in a persistent storage. The method registers a
+ * {@link ScheduledApplication} service in Service Registry, representing
+ * the created schedule.
+ * <p>
+ * The <code>Map</code> argument of the method contains startup
+ * arguments for the application. The keys used in the Map must be non-null,
+ * non-empty <code>String<code> objects.
+ * <p>
+ * The created schedules have a unique identifier within the scope of this
+ * <code>ApplicationDescriptor</code>. This identifier can be specified
+ * in the <code>scheduleId</code> argument. If this argument is <code>null</code>,
+ * the identifier is automatically generated.
+ *
+ * @param scheduleId
+ * the identifier of the created schedule. It can be <code>null</code>,
+ * in this case the identifier is automatically generated.
+ * @param arguments
+ * the startup arguments for the scheduled application, may be
+ * null
+ * @param topic
+ * specifies the topic of the triggering event, it may contain a
+ * trailing asterisk as wildcard, the empty string is treated as
+ * "*", must not be null
+ * @param eventFilter
+ * specifies and LDAP filter to filter on the properties of the
+ * triggering event, may be null
+ * @param recurring
+ * if the recurring parameter is false then the application will
+ * be launched only once, when the event firstly occurs. If the
+ * parameter is true then scheduling will take place for every
+ * event occurrence; i.e. it is a recurring schedule
+ *
+ * @return the registered scheduled application service
+ *
+ * @throws NullPointerException
+ * if the topic is <code>null</code>
+ * @throws InvalidSyntaxException
+ * if the specified <code>eventFilter</code> is not syntactically correct
+ * @throws ApplicationException
+ * if the schedule couldn't be created. The possible error
+ * codes are
+ * <ul>
+ * <li> {@link ApplicationException#APPLICATION_DUPLICATE_SCHEDULE_ID}
+ * if the specified <code>scheduleId</code> is already used
+ * for this <code>ApplicationDescriptor</code>
+ * <li> {@link ApplicationException#APPLICATION_SCHEDULING_FAILED}
+ * if the scheduling failed due to some internal reason
+ * (e.g. persistent storage error).
+ * </ul>
+ * @throws SecurityException
+ * if the caller doesn't have "schedule"
+ * ApplicationAdminPermission for the application.
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ * @throws IllegalArgumentException
+ * if the specified <code>Map</code> contains invalid keys
+ * (null objects, empty <code>String</code> or a key that is not
+ * <code>String</code>)
+ */
+ public final ScheduledApplication schedule(String scheduleId, Map arguments, String topic,
+ String eventFilter, boolean recurring) throws InvalidSyntaxException,
+ ApplicationException {
+ isLaunchableSpecific(); // checks if the ApplicationDescriptor was already unregistered
+ try {
+ return delegate.schedule(scheduleId, arguments, topic, eventFilter, recurring);
+ }catch( SecurityException se ) {
+ isLaunchableSpecific(); /* check whether the bundle was uninstalled */
+ /* if yes, throws IllegalStateException */
+ throw se; /* otherwise throw the catched SecurityException */
+ }
+ }
+
+ /**
+ * Sets the lock state of the application. If an application is locked then
+ * launching a new instance is not possible. It does not affect the already
+ * launched instances.
+ *
+ * @throws SecurityException
+ * if the caller doesn't have "lock" ApplicationAdminPermission
+ * for the application.
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ */
+ public final void lock() {
+ try {
+ delegate.lock();
+ }catch( SecurityException se ) {
+ isLaunchableSpecific(); /* check whether the bundle was uninstalled */
+ /* if yes, throws IllegalStateException */
+ throw se; /* otherwise throw the catched SecurityException */
+ }
+ lockSpecific();
+ }
+
+ /**
+ * This method is used to notify the container implementation that the
+ * corresponding application has been locked and it should update the
+ * <code>application.locked</code> service property accordingly.
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ */
+ protected abstract void lockSpecific();
+
+ /**
+ * Unsets the lock state of the application.
+ *
+ * @throws SecurityException
+ * if the caller doesn't have "lock" ApplicationAdminPermission
+ * for the application.
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ */
+ public final void unlock() {
+ try {
+ delegate.unlock();
+ }catch( SecurityException se ) {
+ isLaunchableSpecific(); /* check whether the bundle was uninstalled */
+ /* if yes, throws IllegalStateException */
+ throw se; /* otherwise throw the catched SecurityException */
+ }
+ unlockSpecific();
+ }
+
+ /**
+ * This method is used to notify the container implementation that the
+ * corresponding application has been unlocked and it should update the
+ * <code>application.locked</code> service property accordingly.
+
+ * @throws IllegalStateException
+ * if the application descriptor is unregistered
+ */
+ protected abstract void unlockSpecific();
+
+ Delegate delegate;
+ /**
+ * This class will load the class named
+ * by the org.osgi.vendor.application.ApplicationDescriptor and delegate
+ * method calls to an instance of the class.
+ */
+ static class Delegate {
+ static String cName;
+ static Class implementation;
+ static Method setApplicationDescriptor;
+ static Method isLocked;
+ static Method lock;
+ static Method unlock;
+ static Method schedule;
+ static Method launch;
+
+ static {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ cName = System.getProperty("org.osgi.vendor.application.ApplicationDescriptor");
+ if (cName == null) {
+ throw new NoClassDefFoundError("org.osgi.vendor.application.ApplicationDescriptor property must be set");
+ }
+
+ try {
+ implementation = Class.forName(cName);
+ }
+ catch (ClassNotFoundException e) {
+ throw new NoClassDefFoundError(e.toString());
+ }
+
+ try {
+ setApplicationDescriptor = implementation.getMethod("setApplicationDescriptor",
+ new Class[] {ApplicationDescriptor.class, String.class});
+ isLocked = implementation.getMethod("isLocked",
+ new Class[] {});
+ lock = implementation.getMethod("lock",
+ new Class[] {});
+ unlock = implementation.getMethod("unlock",
+ new Class[] {});
+ schedule = implementation.getMethod("schedule",
+ new Class[] {String.class, Map.class, String.class, String.class,
+ boolean.class});
+ launch = implementation.getMethod("launch",
+ new Class[] {Map.class});
+ }
+ catch (NoSuchMethodException e) {
+ throw new NoSuchMethodError(e.toString());
+ }
+
+ return null;
+ }
+ });
+ }
+
+ Object target;
+
+ Delegate() throws Exception {
+ target = AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ return implementation.newInstance();
+ }
+ });
+ }
+
+ void setApplicationDescriptor(ApplicationDescriptor d, String pid ) {
+ try {
+ try {
+ setApplicationDescriptor.invoke(target, new Object[] {d, pid});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ boolean isLocked() {
+ try {
+ try {
+ return ((Boolean)isLocked.invoke(target, new Object[] {})).booleanValue();
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ void lock() {
+ try {
+ try {
+ lock.invoke(target, new Object[] {});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ void unlock() {
+ try {
+ try {
+ unlock.invoke(target, new Object[] {});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ ScheduledApplication schedule(String scheduleId, Map args, String topic, String filter,
+ boolean recurs) throws InvalidSyntaxException, ApplicationException {
+ try {
+ try {
+ return (ScheduledApplication)schedule.invoke(target, new Object[] {scheduleId, args, topic, filter,
+ new Boolean(recurs)});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (InvalidSyntaxException e) {
+ throw e;
+ }
+ catch (ApplicationException e) {
+ throw e;
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ void launch(Map arguments) throws ApplicationException {
+ try {
+ try {
+ launch.invoke(target, new Object[] {arguments});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (ApplicationException e) {
+ throw e;
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationException.java b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationException.java
new file mode 100644
index 0000000..c30e112
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationException.java
@@ -0,0 +1,136 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.application/src/org/osgi/service/application/ApplicationException.java,v 1.10 2006/07/10 11:49:12 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.application;
+
+/**
+ * This exception is used to indicate problems related to application
+ * lifecycle management.
+ *
+ * <code>ApplicationException</code> object is created by the Application Admin to denote
+ * an exception condition in the lifecycle of an application.
+ * <code>ApplicationException</code>s should not be created by developers.
+ * <br/>
+ * <code>ApplicationException</code>s are associated with an error code. This code
+ * describes the type of problem reported in this exception. The possible codes are:
+ * <ul>
+ * <li> {@link #APPLICATION_LOCKED} - The application couldn't be launched because it is locked.</li>
+ * <li> {@link #APPLICATION_NOT_LAUNCHABLE} - The application is not in launchable state.</li>
+ * <li> {@link #APPLICATION_INTERNAL_ERROR} - An exception was thrown by the application or its
+ * container during launch.</li>
+ * <li> {@link #APPLICATION_SCHEDULING_FAILED} - The scheduling of an application
+ * failed.
+ * </ul>
+ *
+ */
+public class ApplicationException extends Exception {
+ private static final long serialVersionUID = -7173190453622508207L;
+ private final Throwable cause;
+ private final int errorCode;
+
+ /**
+ * The application couldn't be launched because it is locked.
+ */
+ public static final int APPLICATION_LOCKED = 0x01;
+
+ /**
+ * The application is not in launchable state, it's
+ * {@link ApplicationDescriptor#APPLICATION_LAUNCHABLE}
+ * attribute is false.
+ */
+ public static final int APPLICATION_NOT_LAUNCHABLE = 0x02;
+
+ /**
+ * An exception was thrown by the application or the corresponding
+ * container during launch. The exception is available in {@link #getCause()}.
+ */
+ public static final int APPLICATION_INTERNAL_ERROR = 0x03;
+
+ /**
+ * The application schedule could not be created due to some internal error
+ * (for example, the schedule information couldn't be saved).
+ */
+ public static final int APPLICATION_SCHEDULING_FAILED = 0x04;
+
+ /**
+ * The application scheduling failed because the specified identifier
+ * is already in use.
+ */
+ public static final int APPLICATION_DUPLICATE_SCHEDULE_ID = 0x05;
+
+ /**
+ * Creates an <code>ApplicationException</code> with the specified error code.
+ * @param errorCode The code of the error
+ */
+ public ApplicationException(int errorCode) {
+ this(errorCode,(Throwable) null);
+ }
+
+ /**
+ * Creates a <code>ApplicationException</code> that wraps another exception.
+ *
+ * @param errorCode The code of the error
+ * @param cause The cause of this exception.
+ */
+ public ApplicationException(int errorCode, Throwable cause) {
+ super();
+ this.cause = cause;
+ this.errorCode = errorCode;
+ }
+
+ /**
+ * Creates an <code>ApplicationException</code> with the specified error code.
+ * @param errorCode The code of the error
+ * @param message The associated message
+ */
+ public ApplicationException(int errorCode, String message) {
+ this(errorCode, message,null);
+ }
+
+ /**
+ * Creates a <code>ApplicationException</code> that wraps another exception.
+ *
+ * @param errorCode The code of the error
+ * @param message The associated message.
+ * @param cause The cause of this exception.
+ */
+ public ApplicationException(int errorCode, String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ this.errorCode = errorCode;
+ }
+
+ /**
+ * Returns the cause of this exception or <code>null</code> if no cause
+ * was specified when this exception was created.
+ *
+ * @return The cause of this exception or <code>null</code> if no cause
+ * was specified.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * Returns the error code associcated with this exception.
+ * @return The error code of this exception.
+ */
+ public int getErrorCode() {
+ return errorCode;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationHandle.java b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationHandle.java
new file mode 100644
index 0000000..a0bdc89
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/application/ApplicationHandle.java
@@ -0,0 +1,291 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.application/src/org/osgi/service/application/ApplicationHandle.java,v 1.41 2006/07/10 12:02:31 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.application;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.*;
+
+import org.osgi.framework.Constants;
+
+/**
+ * ApplicationHandle is an OSGi service interface which represents an instance
+ * of an application. It provides the functionality to query and manipulate the
+ * lifecycle state of the represented application instance. It defines constants
+ * for the lifecycle states.
+ */
+public abstract class ApplicationHandle {
+ /*
+ * NOTE: An implementor may also choose to replace this class in
+ * their distribution with a class that directly interfaces with the
+ * org.osgi.service.application implementation. This replacement class MUST NOT alter the
+ * public/protected signature of this class.
+ */
+
+ /**
+ * The property key for the unique identifier (PID) of the application
+ * instance.
+ */
+ public static final String APPLICATION_PID = Constants.SERVICE_PID;
+
+ /**
+ * The property key for the pid of the corresponding application descriptor.
+ */
+ public final static String APPLICATION_DESCRIPTOR = "application.descriptor";
+
+ /**
+ * The property key for the state of this appliction instance.
+ */
+ public final static String APPLICATION_STATE = "application.state";
+
+ /**
+ * The application instance is running. This is the initial state of a newly
+ * created application instance.
+ */
+ public final static String RUNNING = "RUNNING";
+
+ /**
+ * The application instance is being stopped. This is the state of the
+ * application instance during the execution of the <code>destroy()</code>
+ * method.
+ */
+ public final static String STOPPING = "STOPPING";
+
+ private final String instanceId;
+
+ private final ApplicationDescriptor descriptor;
+
+ /**
+ * Application instance identifier is specified by the container when the
+ * instance is created. The instance identifier must remain static for the
+ * lifetime of the instance, it must remain the same even across framework
+ * restarts for the same application instance. This value must be the same
+ * as the <code>service.pid</code> service property of this application
+ * handle.
+ * <p>
+ * The instance identifier should follow the following scheme:
+ * <<i>application descriptor PID</i>>.<<i>index</i>>
+ * where <<i>application descriptor PID</i>> is the PID of the
+ * corresponding <code>ApplicationDescriptor</code> and <<i>index</i>>
+ * is a unique integer index assigned by the application container.
+ * Even after destroying the application index the same index value should not
+ * be reused in a reasonably long timeframe.
+ *
+ * @param instanceId the instance identifier of the represented application
+ * instance. It must not be null.
+ *
+ * @param descriptor the <code>ApplicationDescriptor</code> of the represented
+ * application instance. It must not be null.
+ *
+ * @throws NullPointerException if any of the arguments is null.
+ */
+ protected ApplicationHandle(String instanceId, ApplicationDescriptor descriptor ) {
+ if( (null == instanceId) || (null == descriptor) ) {
+ throw new NullPointerException("Parameters must not be null!");
+ }
+
+ this.instanceId = instanceId;
+ this.descriptor = descriptor;
+
+ try {
+ delegate = new Delegate();
+ delegate.setApplicationHandle( this, descriptor.delegate );
+ }
+ catch (Exception e) {
+ // Too bad ...
+ e.printStackTrace();
+ System.err
+ .println("No implementation available for ApplicationDescriptor, property is: "
+ + Delegate.cName);
+ }
+ }
+
+ /**
+ * Retrieves the <code>ApplicationDescriptor</code> to which this
+ * <code>ApplicationHandle</code> belongs.
+ *
+ * @return The corresponding <code>ApplicationDescriptor</code>
+ */
+ public final ApplicationDescriptor getApplicationDescriptor() {
+ return descriptor;
+ }
+
+ /**
+ * Get the state of the application instance.
+ *
+ * @return the state of the application.
+ *
+ * @throws IllegalStateException
+ * if the application handle is unregistered
+ */
+ public abstract String getState();
+
+ /**
+ * Returns the unique identifier of this instance. This value is also
+ * available as a service property of this application handle's service.pid.
+ *
+ * @return the unique identifier of the instance
+ */
+ public final String getInstanceId() {
+ return instanceId;
+ }
+
+ /**
+ * The application instance's lifecycle state can be influenced by this
+ * method. It lets the application instance perform operations to stop
+ * the application safely, e.g. saving its state to a permanent storage.
+ * <p>
+ * The method must check if the lifecycle transition is valid; a STOPPING
+ * application cannot be stopped. If it is invalid then the method must
+ * exit. Otherwise the lifecycle state of the application instance must be
+ * set to STOPPING. Then the destroySpecific() method must be called to
+ * perform any application model specific steps for safe stopping of the
+ * represented application instance.
+ * <p>
+ * At the end the <code>ApplicationHandle</code> must be unregistered.
+ * This method should free all the resources related to this
+ * <code>ApplicationHandle</code>.
+ * <p>
+ * When this method is completed the application instance has already made
+ * its operations for safe stopping, the ApplicationHandle has been
+ * unregistered and its related resources has been freed. Further calls on
+ * this application should not be made because they may have unexpected
+ * results.
+ *
+ * @throws SecurityException
+ * if the caller doesn't have "lifecycle"
+ * <code>ApplicationAdminPermission</code> for the corresponding application.
+ *
+ * @throws IllegalStateException
+ * if the application handle is unregistered
+ */
+ public final void destroy() {
+ try {
+ delegate.destroy();
+ }catch( SecurityException se ) {
+ descriptor.isLaunchableSpecific(); /* check whether the bundle was uninstalled */
+ /* if yes, throws IllegalStateException */
+ throw se; /* otherwise throw the catched SecurityException */
+ }
+ destroySpecific();
+ }
+
+ /**
+ * Called by the destroy() method to perform application model specific
+ * steps to stop and destroy an application instance safely.
+ *
+ * @throws IllegalStateException
+ * if the application handle is unregistered
+ */
+ protected abstract void destroySpecific();
+
+ Delegate delegate;
+
+
+ /**
+ * This class will load the class named
+ * by the org.osgi.vendor.application.ApplicationHandle and delegate
+ * method calls to an instance of the class.
+ */
+ static class Delegate {
+ static String cName;
+ static Class implementation;
+ static Method setApplicationHandle;
+ static Method destroy;
+
+ static {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run(){
+ cName = System.getProperty("org.osgi.vendor.application.ApplicationHandle");
+ if (cName == null) {
+ throw new NoClassDefFoundError("org.osgi.vendor.application.ApplicationHandle property must be set");
+ }
+
+ try {
+ implementation = Class.forName(cName);
+ }
+ catch (ClassNotFoundException e) {
+ throw new NoClassDefFoundError(e.toString());
+ }
+
+ try {
+ setApplicationHandle = implementation.getMethod("setApplicationHandle",
+ new Class[] {ApplicationHandle.class, Object.class});
+ destroy = implementation.getMethod("destroy",
+ new Class[] {});
+ }
+ catch (NoSuchMethodException e) {
+ throw new NoSuchMethodError(e.toString());
+ }
+
+ return null;
+ }
+ });
+ }
+
+ Object target;
+
+ Delegate() throws Exception {
+ target = AccessController.doPrivileged(new PrivilegedExceptionAction() {
+ public Object run() throws Exception {
+ return implementation.newInstance();
+ }
+ });
+ }
+
+ void setApplicationHandle(ApplicationHandle d, ApplicationDescriptor.Delegate descriptor ) {
+ try {
+ try {
+ setApplicationHandle.invoke(target, new Object[] {d, descriptor.target});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ void destroy() {
+ try {
+ try {
+ destroy.invoke(target, new Object[] {});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/application/ScheduledApplication.java b/deploymentadmin/src/main/java/org/osgi/service/application/ScheduledApplication.java
new file mode 100644
index 0000000..46cfe5e
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/application/ScheduledApplication.java
@@ -0,0 +1,176 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.application/src/org/osgi/service/application/ScheduledApplication.java,v 1.20 2006/07/06 14:59:29 sboshev Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.application;
+
+import java.util.Map;
+
+/**
+ * It is allowed to schedule an application based on a specific event.
+ * ScheduledApplication service keeps the schedule information. When the
+ * specified event is fired a new instance must be launched. Note that launching
+ * operation may fail because e.g. the application is locked.
+ * <p>
+ * Each <code>ScheduledApplication</code> instance has an identifier which is
+ * unique within the scope of the application being scheduled.
+ * <p>
+ * <code>ScheduledApplication</code> instances are registered as services.
+ * The {@link #APPLICATION_PID} service property contains the PID of the
+ * application being scheduled, the {@link #SCHEDULE_ID} service property
+ * contains the schedule identifier.
+ */
+public interface ScheduledApplication {
+
+ /**
+ * The property key for the identifier of the application being scheduled.
+ */
+ public static final String APPLICATION_PID = ApplicationDescriptor.APPLICATION_PID;
+
+ /**
+ * The property key for the schedule identifier. The identifier is unique
+ * within the scope of the application being scheduled.
+ */
+ public static final String SCHEDULE_ID = "schedule.id";
+
+ /**
+ * The key for the startup argument used to pass the event object that
+ * triggered the schedule to launch the application instance.
+ * The event is passed in a {@link java.security.GuardedObject}
+ * protected by the corresponding
+ * {@link org.osgi.service.event.TopicPermission}.
+ */
+ public static final String TRIGGERING_EVENT = "org.osgi.triggeringevent";
+
+ /**
+ * The topic name for the virtual timer topic. Time based schedules
+ * should be created using this topic.
+ */
+ public static final String TIMER_TOPIC = "org/osgi/application/timer";
+
+ /**
+ * The name of the <i>year</i> attribute of a virtual timer event. The value is
+ * defined by {@link java.util.Calendar#YEAR}.
+ */
+ public static final String YEAR = "year";
+
+ /**
+ * The name of the <i>month</i> attribute of a virtual timer event. The value is
+ * defined by {@link java.util.Calendar#MONTH}.
+ */
+ public static final String MONTH = "month";
+
+ /**
+ * The name of the <i>day of month</i> attribute of a virtual timer event. The value is
+ * defined by {@link java.util.Calendar#DAY_OF_MONTH}.
+ */
+ public static final String DAY_OF_MONTH = "day_of_month";
+
+ /**
+ * The name of the <i>day of week</i> attribute of a virtual timer event. The value is
+ * defined by {@link java.util.Calendar#DAY_OF_WEEK}.
+ */
+ public static final String DAY_OF_WEEK = "day_of_week";
+
+ /**
+ * The name of the <i>hour of day</i> attribute of a virtual timer event. The value is
+ * defined by {@link java.util.Calendar#HOUR_OF_DAY}.
+ */
+ public static final String HOUR_OF_DAY = "hour_of_day";
+
+ /**
+ * The name of the <i>minute</i> attribute of a virtual timer event. The value is
+ * defined by {@link java.util.Calendar#MINUTE}.
+ */
+ public static final String MINUTE = "minute";
+
+
+ /**
+ * Returns the identifier of this schedule. The identifier is unique within
+ * the scope of the application that the schedule is related to.
+ * @return the identifier of this schedule
+ *
+ */
+ public String getScheduleId();
+
+ /**
+ * Queries the topic of the triggering event. The topic may contain a
+ * trailing asterisk as wildcard.
+ *
+ * @return the topic of the triggering event
+ *
+ * @throws IllegalStateException
+ * if the scheduled application service is unregistered
+ */
+ public String getTopic();
+
+ /**
+ * Queries the event filter for the triggering event.
+ *
+ * @return the event filter for triggering event
+ *
+ * @throws IllegalStateException
+ * if the scheduled application service is unregistered
+ */
+ public String getEventFilter();
+
+ /**
+ * Queries if the schedule is recurring.
+ *
+ * @return true if the schedule is recurring, otherwise returns false
+ *
+ * @throws IllegalStateException
+ * if the scheduled application service is unregistered
+ */
+ public boolean isRecurring();
+
+ /**
+ * Retrieves the ApplicationDescriptor which represents the application and
+ * necessary for launching.
+ *
+ * @return the application descriptor that
+ * represents the scheduled application
+ *
+ * @throws IllegalStateException
+ * if the scheduled application service is unregistered
+ */
+ public ApplicationDescriptor getApplicationDescriptor();
+
+ /**
+ * Queries the startup arguments specified when the application was
+ * scheduled. The method returns a copy of the arguments, it is not possible
+ * to modify the arguments after scheduling.
+ *
+ * @return the startup arguments of the scheduled application. It may be
+ * null if null argument was specified.
+ *
+ * @throws IllegalStateException
+ * if the scheduled application service is unregistered
+ */
+ public Map getArguments();
+
+ /**
+ * Cancels this schedule of the application.
+ *
+ * @throws SecurityException
+ * if the caller doesn't have "schedule"
+ * ApplicationAdminPermission for the scheduled application.
+ * @throws IllegalStateException
+ * if the scheduled application service is unregistered
+ */
+ public void remove();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/application/package.html b/deploymentadmin/src/main/java/org/osgi/service/application/package.html
new file mode 100644
index 0000000..57ba7be
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/application/package.html
@@ -0,0 +1 @@
+<!-- $Header: /cvshome/build/org.osgi.service.application/src/org/osgi/service/application/package.html,v 1.5 2006/07/12 21:07:13 hargrave Exp $ -->
<BODY>
<p>Application Package Version 1.0.
<p>Bundles wishing to use this package must list the package
in the Import-Package header of the bundle's manifest.
For example:
<pre>
Import-Package: org.osgi.service.application; version=1.0
</pre>
</BODY>
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/application/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/application/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/application/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/Configuration.java b/deploymentadmin/src/main/java/org/osgi/service/cm/Configuration.java
new file mode 100644
index 0000000..af6d739
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/Configuration.java
@@ -0,0 +1,227 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/Configuration.java,v 1.17 2006/06/16 16:31:28 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+import java.io.IOException;
+import java.util.Dictionary;
+
+/**
+ * The configuration information for a <code>ManagedService</code> or
+ * <code>ManagedServiceFactory</code> object.
+ *
+ * The Configuration Admin service uses this interface to represent the
+ * configuration information for a <code>ManagedService</code> or for a
+ * service instance of a <code>ManagedServiceFactory</code>.
+ *
+ * <p>
+ * A <code>Configuration</code> object contains a configuration dictionary and
+ * allows the properties to be updated via this object. Bundles wishing to
+ * receive configuration dictionaries do not need to use this class - they
+ * register a <code>ManagedService</code> or
+ * <code>ManagedServiceFactory</code>. Only administrative bundles, and
+ * bundles wishing to update their own configurations need to use this class.
+ *
+ * <p>
+ * The properties handled in this configuration have case insensitive
+ * <code>String</code> objects as keys. However, case is preserved from the
+ * last set key/value.
+ * <p>
+ * A configuration can be <i>bound </i> to a bundle location (
+ * <code>Bundle.getLocation()</code>). The purpose of binding a
+ * <code>Configuration</code> object to a location is to make it impossible
+ * for another bundle to forge a PID that would match this configuration. When a
+ * configuration is bound to a specific location, and a bundle with a different
+ * location registers a corresponding <code>ManagedService</code> object or
+ * <code>ManagedServiceFactory</code> object, then the configuration is not
+ * passed to the updated method of that object.
+ *
+ * <p>
+ * If a configuration's location is <code>null</code>, it is not yet bound to
+ * a location. It will become bound to the location of the first bundle that
+ * registers a <code>ManagedService</code> or
+ * <code>ManagedServiceFactory</code> object with the corresponding PID.
+ * <p>
+ * The same <code>Configuration</code> object is used for configuring both a
+ * Managed Service Factory and a Managed Service. When it is important to
+ * differentiate between these two the term "factory configuration" is used.
+ *
+ * @version $Revision: 1.17 $
+ */
+public interface Configuration {
+ /**
+ * Get the PID for this <code>Configuration</code> object.
+ *
+ * @return the PID for this <code>Configuration</code> object.
+ * @throws IllegalStateException if this configuration has been deleted
+ */
+ public String getPid();
+
+ /**
+ * Return the properties of this <code>Configuration</code> object.
+ *
+ * The <code>Dictionary</code> object returned is a private copy for the
+ * caller and may be changed without influencing the stored configuration.
+ * The keys in the returned dictionary are case insensitive and are always
+ * of type <code>String</code>.
+ *
+ * <p>
+ * If called just after the configuration is created and before update has
+ * been called, this method returns <code>null</code>.
+ *
+ * @return A private copy of the properties for the caller or
+ * <code>null</code>. These properties must not contain the
+ * "service.bundleLocation" property. The value of this property may
+ * be obtained from the <code>getBundleLocation</code> method.
+ * @throws IllegalStateException if this configuration has been deleted
+ */
+ public Dictionary getProperties();
+
+ /**
+ * Update the properties of this <code>Configuration</code> object.
+ *
+ * Stores the properties in persistent storage after adding or overwriting
+ * the following properties:
+ * <ul>
+ * <li>"service.pid" : is set to be the PID of this configuration.</li>
+ * <li>"service.factoryPid" : if this is a factory configuration it is set
+ * to the factory PID else it is not set.</li>
+ * </ul>
+ * These system properties are all of type <code>String</code>.
+ *
+ * <p>
+ * If the corresponding Managed Service/Managed Service Factory is
+ * registered, its updated method must be called asynchronously. Else, this
+ * callback is delayed until aforementioned registration occurs.
+ *
+ * <p>
+ * Also intiates an asynchronous call to all
+ * <code>ConfigurationListener</code>s with a
+ * <code>ConfigurationEvent.CM_UPDATED</code> event.
+ *
+ * @param properties the new set of properties for this configuration
+ * @throws IOException if update cannot be made persistent
+ * @throws IllegalArgumentException if the <code>Dictionary</code> object
+ * contains invalid configuration types or contains case variants of
+ * the same key name.
+ * @throws IllegalStateException if this configuration has been deleted
+ */
+ public void update(Dictionary properties) throws IOException;
+
+ /**
+ * Delete this <code>Configuration</code> object.
+ *
+ * Removes this configuration object from the persistent store. Notify
+ * asynchronously the corresponding Managed Service or Managed Service
+ * Factory. A <code>ManagedService</code> object is notified by a call to
+ * its <code>updated</code> method with a <code>null</code> properties
+ * argument. A <code>ManagedServiceFactory</code> object is notified by a
+ * call to its <code>deleted</code> method.
+ *
+ * <p>
+ * Also intiates an asynchronous call to all
+ * <code>ConfigurationListener</code>s with a
+ * <code>ConfigurationEvent.CM_DELETED</code> event.
+ *
+ * @throws IOException If delete fails
+ * @throws IllegalStateException if this configuration has been deleted
+ */
+ public void delete() throws IOException;
+
+ /**
+ * For a factory configuration return the PID of the corresponding Managed
+ * Service Factory, else return <code>null</code>.
+ *
+ * @return factory PID or <code>null</code>
+ * @throws IllegalStateException if this configuration has been deleted
+ */
+ public String getFactoryPid();
+
+ /**
+ * Update the <code>Configuration</code> object with the current
+ * properties.
+ *
+ * Initiate the <code>updated</code> callback to the Managed Service or
+ * Managed Service Factory with the current properties asynchronously.
+ *
+ * <p>
+ * This is the only way for a bundle that uses a Configuration Plugin
+ * service to initate a callback. For example, when that bundle detects a
+ * change that requires an update of the Managed Service or Managed Service
+ * Factory via its <code>ConfigurationPlugin</code> object.
+ *
+ * @see ConfigurationPlugin
+ * @throws IOException if update cannot access the properties in persistent
+ * storage
+ * @throws IllegalStateException if this configuration has been deleted
+ */
+ public void update() throws IOException;
+
+ /**
+ * Bind this <code>Configuration</code> object to the specified bundle
+ * location.
+ *
+ * If the bundleLocation parameter is <code>null</code> then the
+ * <code>Configuration</code> object will not be bound to a location. It
+ * will be set to the bundle's location before the first time a Managed
+ * Service/Managed Service Factory receives this <code>Configuration</code>
+ * object via the updated method and before any plugins are called. The
+ * bundle location will be set persistently.
+ *
+ * @param bundleLocation a bundle location or <code>null</code>
+ * @throws IllegalStateException If this configuration has been deleted.
+ * @throws SecurityException If the caller does not have
+ * <code>ConfigurationPermission[*,CONFIGURE]</code>.
+ */
+ public void setBundleLocation(String bundleLocation);
+
+ /**
+ * Get the bundle location.
+ *
+ * Returns the bundle location to which this configuration is bound, or
+ * <code>null</code> if it is not yet bound to a bundle location.
+ *
+ * @return location to which this configuration is bound, or
+ * <code>null</code>.
+ * @throws IllegalStateException If this <code>Configuration</code> object
+ * has been deleted.
+ * @throws SecurityException If the caller does not have
+ * <code>ConfigurationPermission[*,CONFIGURE]</code>.
+ */
+ public String getBundleLocation();
+
+ /**
+ * Equality is defined to have equal PIDs
+ *
+ * Two Configuration objects are equal when their PIDs are equal.
+ *
+ * @param other <code>Configuration</code> object to compare against
+ * @return <code>true</code> if equal, <code>false</code> if not a
+ * <code>Configuration</code> object or one with a different PID.
+ */
+ public boolean equals(Object other);
+
+ /**
+ * Hash code is based on PID.
+ *
+ * The hashcode for two Configuration objects must be the same when the
+ * Configuration PID's are the same.
+ *
+ * @return hash code for this Configuration object
+ */
+ public int hashCode();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationAdmin.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationAdmin.java
new file mode 100644
index 0000000..0d8c6a8
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationAdmin.java
@@ -0,0 +1,256 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ConfigurationAdmin.java,v 1.16 2006/07/11 00:54:03 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+import java.io.IOException;
+import java.util.Dictionary;
+
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * Service for administering configuration data.
+ *
+ * <p>
+ * The main purpose of this interface is to store bundle configuration data
+ * persistently. This information is represented in <code>Configuration</code>
+ * objects. The actual configuration data is a <code>Dictionary</code> of
+ * properties inside a <code>Configuration</code> object.
+ *
+ * <p>
+ * There are two principally different ways to manage configurations. First
+ * there is the concept of a Managed Service, where configuration data is
+ * uniquely associated with an object registered with the service registry.
+ *
+ * <p>
+ * Next, there is the concept of a factory where the Configuration Admin service
+ * will maintain 0 or more <code>Configuration</code> objects for a Managed
+ * Service Factory that is registered with the Framework.
+ *
+ * <p>
+ * The first concept is intended for configuration data about "things/services"
+ * whose existence is defined externally, e.g. a specific printer. Factories are
+ * intended for "things/services" that can be created any number of times, e.g.
+ * a configuration for a DHCP server for different networks.
+ *
+ * <p>
+ * Bundles that require configuration should register a Managed Service or a
+ * Managed Service Factory in the service registry. A registration property
+ * named <code>service.pid</code> (persistent identifier or PID) must be used
+ * to identify this Managed Service or Managed Service Factory to the
+ * Configuration Admin service.
+ *
+ * <p>
+ * When the ConfigurationAdmin detects the registration of a Managed Service, it
+ * checks its persistent storage for a configuration object whose PID matches
+ * the PID registration property (<code>service.pid</code>) of the Managed
+ * Service. If found, it calls {@link ManagedService#updated} method with the
+ * new properties. The implementation of a Configuration Admin service must run
+ * these call-backs asynchronously to allow proper synchronization.
+ *
+ * <p>
+ * When the Configuration Admin service detects a Managed Service Factory
+ * registration, it checks its storage for configuration objects whose
+ * <code>factoryPid</code> matches the PID of the Managed Service Factory. For
+ * each such <code>Configuration</code> objects, it calls the
+ * <code>ManagedServiceFactory.updated</code> method asynchronously with the
+ * new properties. The calls to the <code>updated</code> method of a
+ * <code>ManagedServiceFactory</code> must be executed sequentially and not
+ * overlap in time.
+ *
+ * <p>
+ * In general, bundles having permission to use the Configuration Admin service
+ * can only access and modify their own configuration information. Accessing or
+ * modifying the configuration of another bundle requires
+ * <code>ConfigurationPermission[*,CONFIGURE]</code>.
+ *
+ * <p>
+ * <code>Configuration</code> objects can be <i>bound </i> to a specified
+ * bundle location. In this case, if a matching Managed Service or Managed
+ * Service Factory is registered by a bundle with a different location, then the
+ * Configuration Admin service must not do the normal callback, and it should
+ * log an error. In the case where a <code>Configuration</code> object is not
+ * bound, its location field is <code>null</code>, the Configuration Admin
+ * service will bind it to the location of the bundle that registers the first
+ * Managed Service or Managed Service Factory that has a corresponding PID
+ * property. When a <code>Configuration</code> object is bound to a bundle
+ * location in this manner, the Confguration Admin service must detect if the
+ * bundle corresponding to the location is uninstalled. If this occurs, the
+ * <code>Configuration</code> object is unbound, that is its location field is
+ * set back to <code>null</code>.
+ *
+ * <p>
+ * The method descriptions of this class refer to a concept of "the calling
+ * bundle". This is a loose way of referring to the bundle which obtained the
+ * Configuration Admin service from the service registry. Implementations of
+ * <code>ConfigurationAdmin</code> must use a
+ * {@link org.osgi.framework.ServiceFactory} to support this concept.
+ *
+ * @version $Revision: 1.16 $
+ */
+public interface ConfigurationAdmin {
+ /**
+ * Service property naming the Factory PID in the configuration dictionary.
+ * The property's value is of type <code>String</code>.
+ *
+ * @since 1.1
+ */
+ public final static String SERVICE_FACTORYPID = "service.factoryPid";
+ /**
+ * Service property naming the location of the bundle that is associated
+ * with a a <code>Configuration</code> object. This property can be
+ * searched for but must not appear in the configuration dictionary for
+ * security reason. The property's value is of type <code>String</code>.
+ *
+ * @since 1.1
+ */
+ public final static String SERVICE_BUNDLELOCATION = "service.bundleLocation";
+
+ /**
+ * Create a new factory <code>Configuration</code> object with a new PID.
+ *
+ * The properties of the new <code>Configuration</code> object are
+ * <code>null</code> until the first time that its
+ * {@link Configuration#update(Dictionary)} method is called.
+ *
+ * <p>
+ * It is not required that the <code>factoryPid</code> maps to a
+ * registered Managed Service Factory.
+ * <p>
+ * The <code>Configuration</code> object is bound to the location of the
+ * calling bundle.
+ *
+ * @param factoryPid PID of factory (not <code>null</code>).
+ * @return A new <code>Configuration</code> object.
+ * @throws IOException if access to persistent storage fails.
+ * @throws SecurityException if caller does not have <code>ConfigurationPermission[*,CONFIGURE]</code> and <code>factoryPid</code> is bound to another bundle.
+ */
+ public Configuration createFactoryConfiguration(String factoryPid)
+ throws IOException;
+
+ /**
+ * Create a new factory <code>Configuration</code> object with a new PID.
+ *
+ * The properties of the new <code>Configuration</code> object are
+ * <code>null</code> until the first time that its
+ * {@link Configuration#update(Dictionary)} method is called.
+ *
+ * <p>
+ * It is not required that the <code>factoryPid</code> maps to a
+ * registered Managed Service Factory.
+ *
+ * <p>
+ * The <code>Configuration</code> is bound to the location specified. If
+ * this location is <code>null</code> it will be bound to the location of
+ * the first bundle that registers a Managed Service Factory with a
+ * corresponding PID.
+ *
+ * @param factoryPid PID of factory (not <code>null</code>).
+ * @param location A bundle location string, or <code>null</code>.
+ * @return a new <code>Configuration</code> object.
+ * @throws IOException if access to persistent storage fails.
+ * @throws SecurityException if caller does not have <code>ConfigurationPermission[*,CONFIGURE]</code>.
+ */
+ public Configuration createFactoryConfiguration(String factoryPid, String location)
+ throws IOException;
+
+ /**
+ * Get an existing <code>Configuration</code> object from the persistent
+ * store, or create a new <code>Configuration</code> object.
+ *
+ * <p>
+ * If a <code>Configuration</code> with this PID already exists in
+ * Configuration Admin service return it. The location parameter is ignored
+ * in this case.
+ *
+ * <p>
+ * Else, return a new <code>Configuration</code> object. This new object
+ * is bound to the location and the properties are set to <code>null</code>.
+ * If the location parameter is <code>null</code>, it will be set when a
+ * Managed Service with the corresponding PID is registered for the first
+ * time.
+ *
+ * @param pid Persistent identifier.
+ * @param location The bundle location string, or <code>null</code>.
+ * @return An existing or new <code>Configuration</code> object.
+ * @throws IOException if access to persistent storage fails.
+ * @throws SecurityException if the caller does not have <code>ConfigurationPermission[*,CONFIGURE]</code>.
+ */
+ public Configuration getConfiguration(String pid, String location)
+ throws IOException;
+
+ /**
+ * Get an existing or new <code>Configuration</code> object from the
+ * persistent store.
+ *
+ * If the <code>Configuration</code> object for this PID does not exist,
+ * create a new <code>Configuration</code> object for that PID, where
+ * properties are <code>null</code>. Bind its location to the calling
+ * bundle's location.
+ *
+ * <p>
+ * Otherwise, if the location of the existing <code>Configuration</code> object
+ * is <code>null</code>, set it to the calling bundle's location.
+ *
+ * @param pid persistent identifier.
+ * @return an existing or new <code>Configuration</code> matching the PID.
+ * @throws IOException if access to persistent storage fails.
+ * @throws SecurityException if the <code>Configuration</code> object is bound to a location different from that of the calling bundle and it has no <code>ConfigurationPermission[*,CONFIGURE]</code>.
+ */
+ public Configuration getConfiguration(String pid) throws IOException;
+
+ /**
+ * List the current <code>Configuration</code> objects which match the
+ * filter.
+ *
+ * <p>
+ * Only <code>Configuration</code> objects with non- <code>null</code>
+ * properties are considered current. That is,
+ * <code>Configuration.getProperties()</code> is guaranteed not to return
+ * <code>null</code> for each of the returned <code>Configuration</code>
+ * objects.
+ *
+ * <p>
+ * Normally only <code>Configuration</code> objects that are bound to the
+ * location of the calling bundle are returned, or all if the caller has
+ * <code>ConfigurationPermission[*,CONFIGURE]</code>.
+ *
+ * <p>
+ * The syntax of the filter string is as defined in the <code>Filter</code>
+ * class. The filter can test any configuration parameters including the
+ * following system properties:
+ * <ul>
+ * <li><code>service.pid</code>-<code>String</code>- the PID under
+ * which this is registered</li>
+ * <li><code>service.factoryPid</code>-<code>String</code>- the
+ * factory if applicable</li>
+ * <li><code>service.bundleLocation</code>-<code>String</code>- the
+ * bundle location</li>
+ * </ul>
+ * The filter can also be <code>null</code>, meaning that all
+ * <code>Configuration</code> objects should be returned.
+ *
+ * @param filter a <code>Filter</code> object, or <code>null</code> to
+ * retrieve all <code>Configuration</code> objects.
+ * @return all matching <code>Configuration</code> objects, or
+ * <code>null</code> if there aren't any
+ * @throws IOException if access to persistent storage fails
+ * @throws InvalidSyntaxException if the filter string is invalid
+ */
+ public Configuration[] listConfigurations(String filter) throws IOException,
+ InvalidSyntaxException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationEvent.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationEvent.java
new file mode 100644
index 0000000..3e1b776
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationEvent.java
@@ -0,0 +1,167 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ConfigurationEvent.java,v 1.9 2006/06/16 16:31:28 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * A Configuration Event.
+ *
+ * <p>
+ * <code>ConfigurationEvent</code> objects are delivered to all registered
+ * <code>ConfigurationListener</code> service objects. ConfigurationEvents
+ * must be asynchronously delivered in chronological order with respect to each
+ * listener.
+ *
+ * <p>
+ * A type code is used to identify the type of event. The following event types
+ * are defined:
+ * <ul>
+ * <li>{@link #CM_UPDATED}
+ * <li>{@link #CM_DELETED}
+ * </ul>
+ * Additional event types may be defined in the future.
+ *
+ * <p>
+ * Security Considerations. <code>ConfigurationEvent</code> objects do not
+ * provide <code>Configuration</code> objects, so no sensitive configuration
+ * information is available from the event. If the listener wants to locate the
+ * <code>Configuration</code> object for the specified pid, it must use
+ * <code>ConfigurationAdmin</code>.
+ *
+ * @see ConfigurationListener
+ *
+ * @version $Revision: 1.9 $
+ * @since 1.2
+ */
+public class ConfigurationEvent {
+ /**
+ * A <code>Configuration</code> has been updated.
+ *
+ * <p>
+ * This <code>ConfigurationEvent</code> type that indicates that a
+ * <code>Configuration</code> object has been updated with new properties.
+ *
+ * An event is fired when a call to <code>Configuration.update</code>
+ * successfully changes a configuration.
+ *
+ * <p>
+ * The value of <code>CM_UPDATED</code> is 1.
+ */
+ public static final int CM_UPDATED = 1;
+ /**
+ * A <code>Configuration</code> has been deleted.
+ *
+ * <p>
+ * This <code>ConfigurationEvent</code> type that indicates that a
+ * <code>Configuration</code> object has been deleted.
+ *
+ * An event is fired when a call to <code>Configuration.delete</code>
+ * successfully deletes a configuration.
+ *
+ * <p>
+ * The value of <code>CM_DELETED</code> is 2.
+ */
+ public static final int CM_DELETED = 2;
+ /**
+ * Type of this event.
+ *
+ * @see #getType
+ */
+ private final int type;
+ /**
+ * The factory pid associated with this event.
+ */
+ private final String factoryPid;
+ /**
+ * The pid associated with this event.
+ */
+ private final String pid;
+ /**
+ * The ConfigurationAdmin service which created this event.
+ */
+ private final ServiceReference reference;
+
+ /**
+ * Constructs a <code>ConfigurationEvent</code> object from the given
+ * <code>ServiceReference</code> object, event type, and pids.
+ *
+ * @param reference The <code>ServiceReference</code> object of the
+ * Configuration Admin service that created this event.
+ * @param type The event type. See {@link #getType}.
+ * @param factoryPid The factory pid of the associated configuration if the
+ * target of the configuration is a ManagedServiceFactory. Otherwise
+ * <code>null</code> if the target of the configuration is a
+ * ManagedService.
+ * @param pid The pid of the associated configuration.
+ */
+ public ConfigurationEvent(ServiceReference reference, int type,
+ String factoryPid, String pid) {
+ this.reference = reference;
+ this.type = type;
+ this.factoryPid = factoryPid;
+ this.pid = pid;
+ }
+
+ /**
+ * Returns the factory pid of the associated configuration.
+ *
+ * @return Returns the factory pid of the associated configuration if the
+ * target of the configuration is a ManagedServiceFactory. Otherwise
+ * <code>null</code> if the target of the configuration is a
+ * ManagedService.
+ */
+ public String getFactoryPid() {
+ return factoryPid;
+ }
+
+ /**
+ * Returns the pid of the associated configuration.
+ *
+ * @return Returns the pid of the associated configuration.
+ */
+ public String getPid() {
+ return pid;
+ }
+
+ /**
+ * Return the type of this event.
+ * <p>
+ * The type values are:
+ * <ul>
+ * <li>{@link #CM_UPDATED}
+ * <li>{@link #CM_DELETED}
+ * </ul>
+ *
+ * @return The type of this event.
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Return the <code>ServiceReference</code> object of the Configuration
+ * Admin service that created this event.
+ *
+ * @return The <code>ServiceReference</code> object for the Configuration
+ * Admin service that created this event.
+ */
+ public ServiceReference getReference() {
+ return reference;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationException.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationException.java
new file mode 100644
index 0000000..dbbf216
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationException.java
@@ -0,0 +1,112 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ConfigurationException.java,v 1.13 2006/07/11 13:15:52 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+/**
+ * An <code>Exception</code> class to inform the Configuration Admin service
+ * of problems with configuration data.
+ *
+ * @version $Revision: 1.13 $
+ */
+public class ConfigurationException extends Exception {
+ static final long serialVersionUID = -1690090413441769377L;
+
+ private final String property;
+ private final String reason;
+
+ /**
+ * Nested exception.
+ */
+ private final Throwable cause;
+
+ /**
+ * Create a <code>ConfigurationException</code> object.
+ *
+ * @param property name of the property that caused the problem,
+ * <code>null</code> if no specific property was the cause
+ * @param reason reason for failure
+ */
+ public ConfigurationException(String property, String reason) {
+ super(property + " : " + reason);
+ this.property = property;
+ this.reason = reason;
+ this.cause = null;
+ }
+
+ /**
+ * Create a <code>ConfigurationException</code> object.
+ *
+ * @param property name of the property that caused the problem,
+ * <code>null</code> if no specific property was the cause
+ * @param reason reason for failure
+ * @param cause The cause of this exception.
+ * @since 1.2
+ */
+ public ConfigurationException(String property, String reason,
+ Throwable cause) {
+ super(property + " : " + reason);
+ this.property = property;
+ this.reason = reason;
+ this.cause = cause;
+ }
+
+ /**
+ * Return the property name that caused the failure or null.
+ *
+ * @return name of property or null if no specific property caused the
+ * problem
+ */
+ public String getProperty() {
+ return property;
+ }
+
+ /**
+ * Return the reason for this exception.
+ *
+ * @return reason of the failure
+ */
+ public String getReason() {
+ return reason;
+ }
+
+ /**
+ * Returns the cause of this exception or <code>null</code> if no cause
+ * was specified when this exception was created.
+ *
+ * @return The cause of this exception or <code>null</code> if no cause
+ * was specified.
+ * @since 1.2
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * The cause of this exception can only be set when constructed.
+ *
+ * @param cause Cause of the exception.
+ * @return This object.
+ * @throws java.lang.IllegalStateException This method will always throw an
+ * <code>IllegalStateException</code> since the cause of this
+ * exception can only be set when constructed.
+ * @since 1.2
+ */
+ public Throwable initCause(Throwable cause) {
+ throw new IllegalStateException();
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationListener.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationListener.java
new file mode 100644
index 0000000..a171f90
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationListener.java
@@ -0,0 +1,50 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ConfigurationListener.java,v 1.10 2006/06/16 16:31:28 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+/**
+ * Listener for Configuration Events. When a <code>ConfigurationEvent</code>
+ * is fired, it is asynchronously delivered to a
+ * <code>ConfigurationListener</code>.
+ *
+ * <p>
+ * <code>ConfigurationListener</code> objects are registered with the
+ * Framework service registry and are notified with a
+ * <code>ConfigurationEvent</code> object when an event is fired.
+ * <p>
+ * <code>ConfigurationListener</code> objects can inspect the received
+ * <code>ConfigurationEvent</code> object to determine its type, the pid of
+ * the <code>Configuration</code> object with which it is associated, and the
+ * Configuration Admin service that fired the event.
+ *
+ * <p>
+ * Security Considerations. Bundles wishing to monitor configuration events will
+ * require <code>ServicePermission[ConfigurationListener,REGISTER]</code> to
+ * register a <code>ConfigurationListener</code> service.
+ *
+ * @version $Revision: 1.10 $
+ * @since 1.2
+ */
+public interface ConfigurationListener {
+ /**
+ * Receives notification of a Configuration that has changed.
+ *
+ * @param event The <code>ConfigurationEvent</code>.
+ */
+ public void configurationEvent(ConfigurationEvent event);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationPermission.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationPermission.java
new file mode 100644
index 0000000..f4901b8
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationPermission.java
@@ -0,0 +1,218 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ConfigurationPermission.java,v 1.22 2006/07/08 00:42:00 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.cm;
+
+import java.security.*;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+ * Indicates a bundle's authority to configure bundles.
+ *
+ * This permission has only a single action: CONFIGURE.
+ *
+ * @version $Revision: 1.22 $
+ * @since 1.2
+ */
+
+public final class ConfigurationPermission extends BasicPermission {
+ static final long serialVersionUID = 5716868734811965383L;
+ /**
+ * The action string <code>configure</code>.
+ */
+ public final static String CONFIGURE = "configure";
+
+ /**
+ * Create a new ConfigurationPermission.
+ *
+ * @param name Name must be "*".
+ * @param actions <code>configure</code> (canonical order).
+ */
+
+ public ConfigurationPermission(String name, String actions) {
+ super(name);
+ if (!name.equals("*")) {
+ throw new IllegalArgumentException("name must be *");
+ }
+ actions = actions.trim();
+ if (actions.equalsIgnoreCase(CONFIGURE)||actions.equals("*"))
+ return;
+
+ throw new IllegalArgumentException("actions must be " + CONFIGURE);
+ }
+
+ /**
+ * Determines if a <code>ConfigurationPermission</code> object "implies"
+ * the specified permission.
+ *
+ * @param p The target permission to check.
+ * @return <code>true</code> if the specified permission is implied by
+ * this object; <code>false</code> otherwise.
+ */
+
+ public boolean implies(Permission p) {
+ return p instanceof ConfigurationPermission;
+ }
+
+ /**
+ * Determines the equality of two <code>ConfigurationPermission</code>
+ * objects.
+ * <p>
+ * Two <code>ConfigurationPermission</code> objects are equal.
+ *
+ * @param obj The object being compared for equality with this object.
+ * @return <code>true</code> if <code>obj</code> is equivalent to this
+ * <code>ConfigurationPermission</code>; <code>false</code>
+ * otherwise.
+ */
+ public boolean equals(Object obj) {
+ return obj instanceof ConfigurationPermission;
+ }
+
+ /**
+ * Returns the hash code value for this object.
+ *
+ * @return Hash code value for this object.
+ */
+
+ public int hashCode() {
+ return getName().hashCode() ^ getActions().hashCode();
+ }
+
+ /**
+ * Returns the canonical string representation of the
+ * <code>ConfigurationPermission</code> actions.
+ *
+ * <p>
+ * Always returns present <code>ConfigurationPermission</code> actions in
+ * the following order: <code>CONFIGURE</code>
+ *
+ * @return Canonical string representation of the
+ * <code>ConfigurationPermission</code> actions.
+ */
+ public String getActions() {
+ return CONFIGURE;
+ }
+
+ /**
+ * Returns a new <code>PermissionCollection</code> object suitable for
+ * storing <code>ConfigurationPermission</code>s.
+ *
+ * @return A new <code>PermissionCollection</code> object.
+ */
+ public PermissionCollection newPermissionCollection() {
+ return new ConfigurationPermissionCollection();
+ }
+}
+
+/**
+ * Stores a set of <code>ConfigurationPermission</code> permissions.
+ *
+ * @see java.security.Permission
+ * @see java.security.Permissions
+ * @see java.security.PermissionCollection
+ */
+final class ConfigurationPermissionCollection extends PermissionCollection {
+ static final long serialVersionUID = -6917638867081695839L;
+ /**
+ * True if collection is non-empty.
+ *
+ * @serial
+ */
+ private boolean hasElement;
+
+ /**
+ * Creates an empty <tt>ConfigurationPermissionCollection</tt> object.
+ *
+ */
+ public ConfigurationPermissionCollection() {
+ hasElement = false;
+ }
+
+ /**
+ * Adds the specified permission to the
+ * <tt>ConfigurationPermissionCollection</tt>. The key for the hash is
+ * the interface name of the service.
+ *
+ * @param permission The <tt>Permission</tt> object to add.
+ *
+ * @exception IllegalArgumentException If the permission is not an
+ * <tt>ConfigurationPermission</tt>.
+ *
+ * @exception SecurityException If this ConfigurationPermissionCollection
+ * object has been marked read-only.
+ */
+
+ public void add(Permission permission) {
+ if (!(permission instanceof ConfigurationPermission)) {
+ throw new IllegalArgumentException("invalid permission: "
+ + permission);
+ }
+
+ if (isReadOnly())
+ throw new SecurityException("attempt to add a Permission to a "
+ + "readonly PermissionCollection");
+
+ hasElement = true;
+ }
+
+ /**
+ * Determines if the specified set of permissions implies the permissions
+ * expressed in the parameter <tt>permission</tt>.
+ *
+ * @param p The Permission object to compare.
+ *
+ * @return true if permission is a proper subset of a permission in the set;
+ * false otherwise.
+ */
+
+ public boolean implies(Permission p) {
+ return hasElement && (p instanceof ConfigurationPermission);
+ }
+
+ /**
+ * Returns an enumeration of an <tt>ConfigurationPermission</tt> object.
+ *
+ * @return Enumeration of an <tt>ConfigurationPermission</tt> object.
+ */
+
+ public Enumeration elements() {
+ final boolean nonEmpty = hasElement;
+ return new Enumeration() {
+ private boolean more = nonEmpty;
+
+ public boolean hasMoreElements() {
+ return more;
+ }
+
+ public Object nextElement() {
+ if (more) {
+ more = false;
+
+ return new ConfigurationPermission("*",
+ ConfigurationPermission.CONFIGURE);
+ }
+ else {
+ throw new NoSuchElementException();
+ }
+ }
+ };
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationPlugin.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationPlugin.java
new file mode 100644
index 0000000..d3206f8
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ConfigurationPlugin.java
@@ -0,0 +1,131 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ConfigurationPlugin.java,v 1.11 2006/06/16 16:31:28 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * A service interface for processing configuration dictionary before the
+ * update.
+ *
+ * <p>
+ * A bundle registers a <code>ConfigurationPlugin</code> object in order to
+ * process configuration updates before they reach the Managed Service or
+ * Managed Service Factory. The Configuration Admin service will detect
+ * registrations of Configuration Plugin services and must call these services
+ * every time before it calls the <code>ManagedService</code> or
+ * <code>ManagedServiceFactory</code>
+ * <code>updated</code> method. The
+ * Configuration Plugin service thus has the opportunity to view and modify the
+ * properties before they are passed to the ManagedS ervice or Managed Service
+ * Factory.
+ *
+ * <p>
+ * Configuration Plugin (plugin) services have full read/write access to all
+ * configuration information. Therefore, bundles using this facility should be
+ * trusted. Access to this facility should be limited with
+ * <code>ServicePermission[ConfigurationPlugin,REGISTER]</code>.
+ * Implementations of a Configuration Plugin service should assure that they
+ * only act on appropriate configurations.
+ *
+ * <p>
+ * The <code>Integer</code> <code>service.cmRanking</code> registration
+ * property may be specified. Not specifying this registration property, or
+ * setting it to something other than an <code>Integer</code>, is the same as
+ * setting it to the <code>Integer</code> zero. The
+ * <code>service.cmRanking</code> property determines the order in which
+ * plugins are invoked. Lower ranked plugins are called before higher ranked
+ * ones. In the event of more than one plugin having the same value of
+ * <code>service.cmRanking</code>, then the Configuration Admin service
+ * arbitrarily chooses the order in which they are called.
+ *
+ * <p>
+ * By convention, plugins with <code>service.cmRanking< 0</code> or
+ * <code>service.cmRanking > 1000</code> should not make modifications to
+ * the properties.
+ *
+ * <p>
+ * The Configuration Admin service has the right to hide properties from
+ * plugins, or to ignore some or all the changes that they make. This might be
+ * done for security reasons. Any such behavior is entirely implementation
+ * defined.
+ *
+ * <p>
+ * A plugin may optionally specify a <code>cm.target</code> registration
+ * property whose value is the PID of the Managed Service or Managed Service
+ * Factory whose configuration updates the plugin is intended to intercept. The
+ * plugin will then only be called with configuration updates that are targetted
+ * at the Managed Service or Managed Service Factory with the specified PID.
+ * Omitting the <code>cm.target</code> registration property means that the
+ * plugin is called for all configuration updates.
+ *
+ * @version $Revision: 1.11 $
+ */
+public interface ConfigurationPlugin {
+ /**
+ * A service property to limit the Managed Service or Managed Service
+ * Factory configuration dictionaries a Configuration Plugin service
+ * receives.
+ *
+ * This property contains a <code>String[]</code> of PIDs. A Configuration
+ * Admin service must call a Configuration Plugin service only when this
+ * property is not set, or the target service's PID is listed in this
+ * property.
+ */
+ public static final String CM_TARGET = "cm.target";
+ /**
+ * A service property to specify the order in which plugins are invoked.
+ *
+ * This property contains an <code>Integer</code> ranking of the plugin.
+ * Not specifying this registration property, or setting it to something
+ * other than an <code>Integer</code>, is the same as setting it to the
+ * <code>Integer</code> zero. This property determines the order in which
+ * plugins are invoked. Lower ranked plugins are called before higher ranked
+ * ones.
+ *
+ * @since 1.2
+ */
+ public static final String CM_RANKING = "service.cmRanking";
+
+ /**
+ * View and possibly modify the a set of configuration properties before
+ * they are sent to the Managed Service or the Managed Service Factory. The
+ * Configuration Plugin services are called in increasing order of their
+ * <code>service.cmRanking</code> property. If this property is undefined
+ * or is a non- <code>Integer</code> type, 0 is used.
+ *
+ * <p>
+ * This method should not modify the properties unless the
+ * <code>service.cmRanking</code> of this plugin is in the range
+ * <code>0 <= service.cmRanking <= 1000</code>.
+ * <p>
+ * If this method throws any <code>Exception</code>, the Configuration
+ * Admin service must catch it and should log it.
+ *
+ * @param reference reference to the Managed Service or Managed Service
+ * Factory
+ * @param properties The configuration properties. This argument must not
+ * contain the "service.bundleLocation" property. The value of this
+ * property may be obtained from the
+ * <code>Configuration.getBundleLocation</code> method.
+ */
+ public void modifyConfiguration(ServiceReference reference,
+ Dictionary properties);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ManagedService.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ManagedService.java
new file mode 100644
index 0000000..d4e30a2
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ManagedService.java
@@ -0,0 +1,140 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ManagedService.java,v 1.12 2006/06/16 16:31:28 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+import java.util.Dictionary;
+
+/**
+ * A service that can receive configuration data from a Configuration Admin
+ * service.
+ *
+ * <p>
+ * A Managed Service is a service that needs configuration data. Such an object
+ * should be registered with the Framework registry with the
+ * <code>service.pid</code> property set to some unique identitifier called a
+ * PID.
+ *
+ * <p>
+ * If the Configuration Admin service has a <code>Configuration</code> object
+ * corresponding to this PID, it will callback the <code>updated()</code>
+ * method of the <code>ManagedService</code> object, passing the properties of
+ * that <code>Configuration</code> object.
+ *
+ * <p>
+ * If it has no such <code>Configuration</code> object, then it calls back
+ * with a <code>null</code> properties argument. Registering a Managed Service
+ * will always result in a callback to the <code>updated()</code> method
+ * provided the Configuration Admin service is, or becomes active. This callback
+ * must always be done asynchronously.
+ *
+ * <p>
+ * Else, every time that either of the <code>updated()</code> methods is
+ * called on that <code>Configuration</code> object, the
+ * <code>ManagedService.updated()</code> method with the new properties is
+ * called. If the <code>delete()</code> method is called on that
+ * <code>Configuration</code> object, <code>ManagedService.updated()</code>
+ * is called with a <code>null</code> for the properties parameter. All these
+ * callbacks must be done asynchronously.
+ *
+ * <p>
+ * The following example shows the code of a serial port that will create a port
+ * depending on configuration information.
+ *
+ * <pre>
+ *
+ * class SerialPort implements ManagedService {
+ *
+ * ServiceRegistration registration;
+ * Hashtable configuration;
+ * CommPortIdentifier id;
+ *
+ * synchronized void open(CommPortIdentifier id,
+ * BundleContext context) {
+ * this.id = id;
+ * registration = context.registerService(
+ * ManagedService.class.getName(),
+ * this,
+ * getDefaults()
+ * );
+ * }
+ *
+ * Hashtable getDefaults() {
+ * Hashtable defaults = new Hashtable();
+ * defaults.put( "port", id.getName() );
+ * defaults.put( "product", "unknown" );
+ * defaults.put( "baud", "9600" );
+ * defaults.put( Constants.SERVICE_PID,
+ * "com.acme.serialport." + id.getName() );
+ * return defaults;
+ * }
+ *
+ * public synchronized void updated(
+ * Dictionary configuration ) {
+ * if ( configuration ==
+ * <code>
+ * null
+ * </code>
+ * )
+ * registration.setProperties( getDefaults() );
+ * else {
+ * setSpeed( configuration.get("baud") );
+ * registration.setProperties( configuration );
+ * }
+ * }
+ * ...
+ * }
+ *
+ * </pre>
+ *
+ * <p>
+ * As a convention, it is recommended that when a Managed Service is updated, it
+ * should copy all the properties it does not recognize into the service
+ * registration properties. This will allow the Configuration Admin service to
+ * set properties on services which can then be used by other applications.
+ *
+ * @version $Revision: 1.12 $
+ */
+public interface ManagedService {
+ /**
+ * Update the configuration for a Managed Service.
+ *
+ * <p>
+ * When the implementation of <code>updated(Dictionary)</code> detects any
+ * kind of error in the configuration properties, it should create a new
+ * <code>ConfigurationException</code> which describes the problem. This
+ * can allow a management system to provide useful information to a human
+ * administrator.
+ *
+ * <p>
+ * If this method throws any other <code>Exception</code>, the
+ * Configuration Admin service must catch it and should log it.
+ * <p>
+ * The Configuration Admin service must call this method asynchronously
+ * which initiated the callback. This implies that implementors of Managed
+ * Service can be assured that the callback will not take place during
+ * registration when they execute the registration in a synchronized method.
+ *
+ * @param properties A copy of the Configuration properties, or
+ * <code>null</code>. This argument must not contain the
+ * "service.bundleLocation" property. The value of this property may
+ * be obtained from the <code>Configuration.getBundleLocation</code>
+ * method.
+ * @throws ConfigurationException when the update fails
+ */
+ public void updated(Dictionary properties) throws ConfigurationException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/ManagedServiceFactory.java b/deploymentadmin/src/main/java/org/osgi/service/cm/ManagedServiceFactory.java
new file mode 100644
index 0000000..838df68
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/ManagedServiceFactory.java
@@ -0,0 +1,162 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/ManagedServiceFactory.java,v 1.12 2006/07/11 00:54:03 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.cm;
+
+import java.util.Dictionary;
+
+/**
+ * Manage multiple service instances.
+ *
+ * Bundles registering this interface are giving the Configuration Admin service
+ * the ability to create and configure a number of instances of a service that
+ * the implementing bundle can provide. For example, a bundle implementing a
+ * DHCP server could be instantiated multiple times for different interfaces
+ * using a factory.
+ *
+ * <p>
+ * Each of these <i>service instances </i> is represented, in the persistent
+ * storage of the Configuration Admin service, by a factory
+ * <code>Configuration</code> object that has a PID. When such a
+ * <code>Configuration</code> is updated, the Configuration Admin service
+ * calls the <code>ManagedServiceFactory</code> updated method with the new
+ * properties. When <code>updated</code> is called with a new PID, the Managed
+ * Service Factory should create a new factory instance based on these
+ * configuration properties. When called with a PID that it has seen before, it
+ * should update that existing service instance with the new configuration
+ * information.
+ *
+ * <p>
+ * In general it is expected that the implementation of this interface will
+ * maintain a data structure that maps PIDs to the factory instances that it has
+ * created. The semantics of a factory instance are defined by the Managed
+ * Service Factory. However, if the factory instance is registered as a service
+ * object with the service registry, its PID should match the PID of the
+ * corresponding <code>Configuration</code> object (but it should <b>not </b>
+ * be registered as a Managed Service!).
+ *
+ * <p>
+ * An example that demonstrates the use of a factory. It will create serial
+ * ports under command of the Configuration Admin service.
+ *
+ * <pre>
+ *
+ * class SerialPortFactory
+ * implements ManagedServiceFactory {
+ * ServiceRegistration registration;
+ * Hashtable ports;
+ * void start(BundleContext context) {
+ * Hashtable properties = new Hashtable();
+ * properties.put( Constants.SERVICE_PID,
+ * "com.acme.serialportfactory" );
+ * registration = context.registerService(
+ * ManagedServiceFactory.class.getName(),
+ * this,
+ * properties
+ * );
+ * }
+ * public void updated( String pid,
+ * Dictionary properties ) {
+ * String portName = (String) properties.get("port");
+ * SerialPortService port =
+ * (SerialPort) ports.get( pid );
+ * if ( port == null ) {
+ * port = new SerialPortService();
+ * ports.put( pid, port );
+ * port.open();
+ * }
+ * if ( port.getPortName().equals(portName) )
+ * return;
+ * port.setPortName( portName );
+ * }
+ * public void deleted( String pid ) {
+ * SerialPortService port =
+ * (SerialPort) ports.get( pid );
+ * port.close();
+ * ports.remove( pid );
+ * }
+ * ...
+ * }
+ *
+ * </pre>
+ *
+ * @version $Revision: 1.12 $
+ */
+public interface ManagedServiceFactory {
+ /**
+ * Return a descriptive name of this factory.
+ *
+ * @return the name for the factory, which might be localized
+ */
+ public String getName();
+
+ /**
+ * Create a new instance, or update the configuration of an existing
+ * instance.
+ *
+ * If the PID of the <code>Configuration</code> object is new for the
+ * Managed Service Factory, then create a new factory instance, using the
+ * configuration <code>properties</code> provided. Else, update the
+ * service instance with the provided <code>properties</code>.
+ *
+ * <p>
+ * If the factory instance is registered with the Framework, then the
+ * configuration <code>properties</code> should be copied to its registry
+ * properties. This is not mandatory and security sensitive properties
+ * should obviously not be copied.
+ *
+ * <p>
+ * If this method throws any <code>Exception</code>, the Configuration
+ * Admin service must catch it and should log it.
+ *
+ * <p>
+ * When the implementation of updated detects any kind of error in the
+ * configuration properties, it should create a new
+ * {@link ConfigurationException} which describes the problem.
+ *
+ * <p>
+ * The Configuration Admin service must call this method asynchronously.
+ * This implies that implementors of the <code>ManagedServiceFactory</code>
+ * class can be assured that the callback will not take place during
+ * registration when they execute the registration in a synchronized method.
+ *
+ * @param pid The PID for this configuration.
+ * @param properties A copy of the configuration properties. This argument
+ * must not contain the service.bundleLocation" property. The value
+ * of this property may be obtained from the
+ * <code>Configuration.getBundleLocation</code> method.
+ * @throws ConfigurationException when the configuration properties are
+ * invalid.
+ */
+ public void updated(String pid, Dictionary properties)
+ throws ConfigurationException;
+
+ /**
+ * Remove a factory instance.
+ *
+ * Remove the factory instance associated with the PID. If the instance was
+ * registered with the service registry, it should be unregistered.
+ * <p>
+ * If this method throws any <code>Exception</code>, the Configuration
+ * Admin service must catch it and should log it.
+ * <p>
+ * The Configuration Admin service must call this method asynchronously.
+ *
+ * @param pid the PID of the service to be removed
+ */
+ public void deleted(String pid);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/package.html b/deploymentadmin/src/main/java/org/osgi/service/cm/package.html
new file mode 100644
index 0000000..4ff9ffd
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/package.html
@@ -0,0 +1 @@
+<!-- $Header: /cvshome/build/org.osgi.service.cm/src/org/osgi/service/cm/package.html,v 1.3 2006/07/12 21:07:02 hargrave Exp $ -->
<BODY>
<p>Configuration Admin Package Version 1.2.
<p>Bundles wishing to use this package must list the package
in the Import-Package header of the bundle's manifest.
For example:
<pre>
Import-Package: org.osgi.service.cm; version=1.2
</pre>
</BODY>
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/cm/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/cm/packageinfo
new file mode 100644
index 0000000..ef7df68
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/cm/packageinfo
@@ -0,0 +1 @@
+version 1.2
diff --git a/deploymentadmin/src/main/java/org/osgi/service/component/ComponentConstants.java b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentConstants.java
new file mode 100644
index 0000000..34b0388
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentConstants.java
@@ -0,0 +1,71 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.component/src/org/osgi/service/component/ComponentConstants.java,v 1.14 2006/06/16 16:31:26 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.component;
+
+/**
+ * Defines standard names for Service Component constants.
+ *
+ * @version $Revision: 1.14 $
+ */
+public interface ComponentConstants {
+ /**
+ * Manifest header (named "Service-Component") specifying the XML
+ * documents within a bundle that contain the bundle's Service Component
+ * descriptions.
+ * <p>
+ * The attribute value may be retrieved from the <code>Dictionary</code>
+ * object returned by the <code>Bundle.getHeaders</code> method.
+ */
+ public static final String SERVICE_COMPONENT = "Service-Component";
+
+ /**
+ * A component property for a component configuration that contains the name
+ * of the component as specified in the <code>name</code> attribute of the
+ * <code>component</code> element. The type of this property must be
+ * <code>String</code>.
+ */
+ public final static String COMPONENT_NAME = "component.name";
+
+ /**
+ * A component property that contains the generated id for a component
+ * configuration. The type of this property must be <code>Long</code>.
+ *
+ * <p>
+ * The value of this property is assigned by the Service Component Runtime
+ * when a component configuration is created. The Service Component Runtime
+ * assigns a unique value that is larger than all previously assigned values
+ * since the Service Component Runtime was started. These values are NOT
+ * persistent across restarts of the Service Component Runtime.
+ */
+ public final static String COMPONENT_ID = "component.id";
+
+ /**
+ * A service registration property for a Component Factory that contains the
+ * value of the <code>factory</code> attribute. The type of this property
+ * must be <code>String</code>.
+ */
+ public final static String COMPONENT_FACTORY = "component.factory";
+
+ /**
+ * The suffix for reference target properties. These properties contain the
+ * filter to select the target services for a reference. The type of this
+ * property must be <code>String</code>.
+ */
+ public final static String REFERENCE_TARGET_SUFFIX = ".target";
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/component/ComponentContext.java b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentContext.java
new file mode 100644
index 0000000..c763293
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentContext.java
@@ -0,0 +1,198 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.component/src/org/osgi/service/component/ComponentContext.java,v 1.20 2006/06/16 16:31:26 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.component;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.*;
+
+/**
+ * A Component Context object is used by a component instance to interact with
+ * its execution context including locating services by reference name. Each
+ * component instance has a unique Component Context.
+ *
+ * <p>
+ * A component's implementation class may optionaly implement an activate
+ * method:
+ *
+ * <pre>
+ * protected void activate(ComponentContext context);
+ * </pre>
+ *
+ * If a component implements this method, this method will be called when a
+ * component configuration is activated to provide the component instance's
+ * Component Context object.
+ *
+ * <p>
+ * A component's implementation class may optionaly implement a deactivate
+ * method:
+ *
+ * <pre>
+ * protected void deactivate(ComponentContext context);
+ * </pre>
+ *
+ * If a component implements this method, this method will be called when the
+ * component configuration is deactivated.
+ *
+ * <p>
+ * The activate and deactivate methods will be called using reflection and must
+ * be protected or public accessible. These methods should not be public methods
+ * so that they do not appear as public methods on the component instance when
+ * used as a service object. These methods will be located by looking through
+ * the component's implementation class hierarchy for the first declaration of
+ * the method. If the method is found, if it is declared protected or public,
+ * the method will be called. Otherwise, the method will not be called.
+ *
+ * @version $Revision: 1.20 $
+ */
+public interface ComponentContext {
+ /**
+ * Returns the component properties for this Component Context.
+ *
+ * @return The properties for this Component Context. The Dictionary is read
+ * only and cannot be modified.
+ */
+ public Dictionary getProperties();
+
+ /**
+ * Returns the service object for the specified reference name.
+ *
+ * <p>
+ * If the cardinality of the reference is <code>0..n</code> or
+ * <code>1..n</code> and multiple services are bound to the reference, the
+ * service with the highest ranking (as specified in its
+ * <code>Constants.SERVICE_RANKING</code> property) is returned. If there
+ * is a tie in ranking, the service with the lowest service ID (as specified
+ * in its <code>Constants.SERVICE_ID</code> property); that is, the
+ * service that was registered first is returned.
+ *
+ * @param name The name of a reference as specified in a
+ * <code>reference</code> element in this component's description.
+ * @return A service object for the referenced service or <code>null</code>
+ * if the reference cardinality is <code>0..1</code> or
+ * <code>0..n</code> and no bound service is available.
+ * @throws ComponentException If the Service Component Runtime catches an
+ * exception while activating the bound service.
+ */
+ public Object locateService(String name);
+
+ /**
+ * Returns the service object for the specified reference name and
+ * <code>ServiceReference</code>.
+ *
+ * @param name The name of a reference as specified in a
+ * <code>reference</code> element in this component's description.
+ * @param reference The <code>ServiceReference</code> to a bound service.
+ * This must be a <code>ServiceReference</code> provided to the
+ * component via the bind or unbind method for the specified
+ * reference name.
+ * @return A service object for the referenced service or <code>null</code>
+ * if the specified <code>ServiceReference</code> is not a bound
+ * service for the specified reference name.
+ * @throws ComponentException If the Service Component Runtime catches an
+ * exception while activating the bound service.
+ */
+ public Object locateService(String name, ServiceReference reference);
+
+ /**
+ * Returns the service objects for the specified reference name.
+ *
+ * @param name The name of a reference as specified in a
+ * <code>reference</code> element in this component's description.
+ * @return An array of service objects for the referenced service or
+ * <code>null</code> if the reference cardinality is
+ * <code>0..1</code> or <code>0..n</code> and no bound service
+ * is available.
+ * @throws ComponentException If the Service Component Runtime catches an
+ * exception while activating a bound service.
+ */
+ public Object[] locateServices(String name);
+
+ /**
+ * Returns the <code>BundleContext</code> of the bundle which contains
+ * this component.
+ *
+ * @return The <code>BundleContext</code> of the bundle containing this
+ * component.
+ */
+ public BundleContext getBundleContext();
+
+ /**
+ * If the component instance is registered as a service using the
+ * <code>servicefactory="true"</code> attribute, then this
+ * method returns the bundle using the service provided by the component
+ * instance.
+ * <p>
+ * This method will return <code>null</code> if:
+ * <ul>
+ * <li>The component instance is not a service, then no bundle can be using
+ * it as a service.
+ * <li>The component instance is a service but did not specify the
+ * <code>servicefactory="true"</code> attribute, then all
+ * bundles using the service provided by the component instance will share
+ * the same component instance.
+ * <li>The service provided by the component instance is not currently
+ * being used by any bundle.
+ * </ul>
+ *
+ * @return The bundle using the component instance as a service or
+ * <code>null</code>.
+ */
+ public Bundle getUsingBundle();
+
+ /**
+ * Returns the Component Instance object for the component instance
+ * associated with this Component Context.
+ *
+ * @return The Component Instance object for the component instance.
+ */
+ public ComponentInstance getComponentInstance();
+
+ /**
+ * Enables the specified component name. The specified component name must
+ * be in the same bundle as this component.
+ *
+ * @param name The name of a component or <code>null</code> to indicate
+ * all components in the bundle.
+ */
+ public void enableComponent(String name);
+
+ /**
+ * Disables the specified component name. The specified component name must
+ * be in the same bundle as this component.
+ *
+ * @param name The name of a component.
+ */
+ public void disableComponent(String name);
+
+ /**
+ * If the component instance is registered as a service using the
+ * <code>service</code> element, then this method returns the service
+ * reference of the service provided by this component instance.
+ * <p>
+ * This method will return <code>null</code> if the component instance is
+ * not registered as a service.
+ *
+ * @return The <code>ServiceReference</code> object for the component
+ * instance or <code>null</code> if the component instance is not
+ * registered as a service.
+ */
+ public ServiceReference getServiceReference();
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/component/ComponentException.java b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentException.java
new file mode 100644
index 0000000..be539a9
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentException.java
@@ -0,0 +1,87 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.component/src/org/osgi/service/component/ComponentException.java,v 1.13 2006/07/11 13:15:56 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.component;
+
+/**
+ * Unchecked exception which may be thrown by the Service Component Runtime.
+ *
+ * @version $Revision: 1.13 $
+ */
+public class ComponentException extends RuntimeException {
+ static final long serialVersionUID = -7438212656298726924L;
+ /**
+ * Nested exception.
+ */
+ private final Throwable cause;
+
+ /**
+ * Construct a new ComponentException with the specified message and cause.
+ *
+ * @param message The message for the exception.
+ * @param cause The cause of the exception. May be <code>null</code>.
+ */
+ public ComponentException(String message, Throwable cause) {
+ super(message);
+ this.cause = cause;
+ }
+
+ /**
+ * Construct a new ComponentException with the specified message.
+ *
+ * @param message The message for the exception.
+ */
+ public ComponentException(String message) {
+ super(message);
+ this.cause = null;
+ }
+
+ /**
+ * Construct a new ComponentException with the specified cause.
+ *
+ * @param cause The cause of the exception. May be <code>null</code>.
+ */
+ public ComponentException(Throwable cause) {
+ super();
+ this.cause = cause;
+ }
+
+ /**
+ * Returns the cause of this exception or <code>null</code> if no cause
+ * was specified when this exception was created.
+ *
+ * @return The cause of this exception or <code>null</code> if no cause
+ * was specified.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * The cause of this exception can only be set when constructed.
+ *
+ * @param cause Cause of the exception.
+ * @return This object.
+ * @throws java.lang.IllegalStateException This method will always throw an
+ * <code>IllegalStateException</code> since the cause of this
+ * exception can only be set when constructed.
+ */
+ public Throwable initCause(Throwable cause) {
+ throw new IllegalStateException();
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/component/ComponentFactory.java b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentFactory.java
new file mode 100644
index 0000000..8dc522a
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentFactory.java
@@ -0,0 +1,48 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.component/src/org/osgi/service/component/ComponentFactory.java,v 1.19 2006/06/16 16:31:26 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.component;
+
+import java.util.Dictionary;
+
+/**
+ * When a component is declared with the <code>factory</code> attribute on its
+ * <code>component</code> element, the Service Component Runtime will register
+ * a Component Factory service to allow new component configurations to be
+ * created and activated rather than automatically creating and activating
+ * component configuration as necessary.
+ *
+ * @version $Revision: 1.19 $
+ */
+public interface ComponentFactory {
+ /**
+ * Create and activate a new component configuration. Additional properties
+ * may be provided for the component configuration.
+ *
+ * @param properties Additional properties for the component configuration
+ * or <code>null</code> if there are no additional properties.
+ * @return A <code>ComponentInstance</code> object encapsulating the
+ * component instance of the component configuration. The component
+ * configuration has been activated and, if the component specifies
+ * a <code>service</code> element, the component instance has been
+ * registered as a service.
+ * @throws ComponentException If the Service Component Runtime is unable to
+ * activate the component configuration.
+ */
+ public ComponentInstance newInstance(Dictionary properties);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/component/ComponentInstance.java b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentInstance.java
new file mode 100644
index 0000000..9f2d9f5
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/component/ComponentInstance.java
@@ -0,0 +1,47 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.component/src/org/osgi/service/component/ComponentInstance.java,v 1.13 2006/06/16 16:31:26 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.component;
+
+/**
+ * A ComponentInstance encapsulates a component instance of an activated
+ * component configuration. ComponentInstances are created whenever a component
+ * configuration is activated.
+ *
+ * <p>
+ * ComponentInstances are never reused. A new ComponentInstance object will be
+ * created when the component configuration is activated again.
+ *
+ * @version $Revision: 1.13 $
+ */
+public interface ComponentInstance {
+ /**
+ * Dispose of the component configuration for this component instance. The
+ * component configuration will be deactivated. If the component
+ * configuration has already been deactivated, this method does nothing.
+ */
+ public void dispose();
+
+ /**
+ * Returns the component instance of the activated component configuration.
+ *
+ * @return The component instance or <code>null</code> if the component
+ * configuration has been deactivated.
+ */
+ public Object getInstance();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/component/package.html b/deploymentadmin/src/main/java/org/osgi/service/component/package.html
new file mode 100644
index 0000000..5cbcb64
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/component/package.html
@@ -0,0 +1 @@
+<!-- $Header: /cvshome/build/org.osgi.service.component/src/org/osgi/service/component/package.html,v 1.3 2006/07/12 21:07:00 hargrave Exp $ -->
<BODY>
<p>Service Component Package Version 1.0.
<p>Bundles wishing to use this package must list the package
in the Import-Package header of the bundle's manifest.
For example:
<pre>
Import-Package: org.osgi.service.component; version=1.0
</pre>
</BODY>
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/component/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/component/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/component/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/BundleInfo.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/BundleInfo.java
new file mode 100644
index 0000000..9bd31fb
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/BundleInfo.java
@@ -0,0 +1,43 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/BundleInfo.java,v 1.3 2006/06/16 16:31:39 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.deploymentadmin;
+
+import org.osgi.framework.Version;
+
+/**
+ * Represents a bundle in the array given back by the {@link DeploymentPackage#getBundleInfos()}
+ * method.
+ */
+public interface BundleInfo {
+
+ /**
+ * Returns the Bundle Symbolic Name of the represented bundle.
+ *
+ * @return the Bundle Symbolic Name
+ */
+ String getSymbolicName();
+
+ /**
+ * Returns the version of the represented bundle.
+ *
+ * @return the version of the represented bundle
+ */
+ Version getVersion();
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdmin.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdmin.java
new file mode 100644
index 0000000..b506540
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdmin.java
@@ -0,0 +1,140 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/DeploymentAdmin.java,v 1.26 2006/06/16 16:31:39 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.deploymentadmin;
+
+import java.io.InputStream;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * This is the interface of the Deployment Admin service.<p>
+ *
+ * The OSGi Service Platform provides mechanisms to manage the life cycle of
+ * bundles, configuration objects, permission objects, etc. but the overall consistency
+ * of the runtime configuration is the responsibility of the management
+ * agent. In other words, the management agent decides to install, update,
+ * or uninstall bundles, create or delete configuration or permission objects, as
+ * well as manage other resource types, etc.<p>
+ *
+ * The Deployment Admin service standardizes the access to some of the responsibilities
+ * of the management agent. The service provides functionality to manage Deployment Packages
+ * (see {@link DeploymentPackage}). A Deployment Package groups resources as a unit
+ * of management. A Deployment Package is something that can be installed, updated,
+ * and uninstalled as a unit.<p>
+ *
+ * The Deployment Admin functionality is exposed as a standard OSGi service with no
+ * mandatory service parameters.
+ */
+public interface DeploymentAdmin {
+
+ /**
+ * Installs a Deployment Package from an input stream. If a version of that Deployment Package
+ * is already installed and the versions are different, the installed version is updated
+ * with this new version even if it is older (downgrade). If the two versions are the same, then this
+ * method simply returns with the old (target) Deployment Package without any action.
+ *
+ * @param in the input stream the Deployment Package can be read from. It mustn't be <code>null</code>.
+ * @return A DeploymentPackage object representing the newly installed/updated Deployment Package.
+ * It is never <code>null</code>.
+ * @throws IllegalArgumentException if the got InputStream parameter is <code>null</code>
+ * @throws DeploymentException if the installation was not successful. For detailed error code description
+ * see {@link DeploymentException}.
+ * @throws SecurityException if the caller doesn't have the appropriate
+ * {@link DeploymentAdminPermission}("<filter>", "install") permission.
+ * @see DeploymentAdminPermission
+ * @see DeploymentPackage
+ * @see DeploymentPackage
+ */
+ DeploymentPackage installDeploymentPackage(InputStream in) throws DeploymentException;
+
+ /**
+ * Lists the Deployment Packages currently installed on the platform.<p>
+ *
+ * {@link DeploymentAdminPermission}("<filter>", "list") is
+ * needed for this operation to the effect that only those packages are listed in
+ * the array to which the caller has appropriate DeploymentAdminPermission. It has
+ * the consequence that the method never throws SecurityException only doesn't
+ * put certain Deployment Packages into the array.<p>
+ *
+ * During an installation of an existing package (update) or during an uninstallation,
+ * the target must remain in this list until the installation (uninstallation) process
+ * is completed, after which the source (or <code>null</code> in case of uninstall)
+ * replaces the target.
+ *
+ * @return the array of <code>DeploymentPackage</code> objects representing all the
+ * installed Deployment Packages (including the "system" Deployment Package).
+ * The return value cannot be <code>null</code>. In case of missing permissions it may
+ * give back an empty array.
+ * @see DeploymentPackage
+ * @see DeploymentAdminPermission
+ */
+ DeploymentPackage[] listDeploymentPackages();
+
+ /**
+ * Gets the currenlty installed {@link DeploymentPackage} instance which has the given
+ * symbolic name.<p>
+ *
+ * During an installation of an existing package (update) or during an uninstallation,
+ * the target Deployment Package must remain the return value until the installation
+ * (uninstallation) process is completed, after which the source (or <code>null</code>
+ * in case of uninstall) is the return value.
+ *
+ * @param symbName the symbolic name of the Deployment Package to be retrieved. It mustn't be
+ * <code>null</code>.
+ * @return The <code>DeploymentPackage</code> for the given symbolic name.
+ * If there is no Deployment Package with that symbolic name currently installed,
+ * <code>null</code> is returned.
+ * @throws IllegalArgumentException if the given <code>symbName</code> is <code>null</code>
+ * @throws SecurityException if the caller doesn't have the appropriate
+ * {@link DeploymentAdminPermission}("<filter>", "list") permission.
+ * @see DeploymentPackage
+ * @see DeploymentAdminPermission
+ */
+ DeploymentPackage getDeploymentPackage(String symbName);
+
+ /**
+ * Gives back the installed {@link DeploymentPackage} that owns the bundle. Deployment Packages own their
+ * bundles by their Bundle Symbolic Name. It means that if a bundle belongs to an installed
+ * Deployment Packages (and at most to one) the Deployment Admin assigns the bundle to its owner
+ * Deployment Package by the Symbolic Name of the bundle.<p>
+ *
+ * @param bundle the bundle whose owner is queried
+ * @return the Deployment Package Object that owns the bundle or <code>null</code> if the bundle doesn't
+ * belong to any Deployment Packages (standalone bundles)
+ * @throws IllegalArgumentException if the given <code>bundle</code> is <code>null</code>
+ * @throws SecurityException if the caller doesn't have the appropriate
+ * {@link DeploymentAdminPermission}("<filter>", "list") permission.
+ * @see DeploymentPackage
+ * @see DeploymentAdminPermission
+ */
+ DeploymentPackage getDeploymentPackage(Bundle bundle);
+
+ /**
+ * This method cancels the currently active deployment session. This method addresses the need
+ * to cancel the processing of excessively long running, or resource consuming install, update
+ * or uninstall operations.<p>
+ *
+ * @return true if there was an active session and it was successfully cancelled.
+ * @throws SecurityException if the caller doesn't have the appropriate
+ * {@link DeploymentAdminPermission}("<filter>", "cancel") permission.
+ * @see DeploymentAdminPermission
+ */
+ boolean cancel();
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdminPermission.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdminPermission.java
new file mode 100644
index 0000000..34509cf
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentAdminPermission.java
@@ -0,0 +1,331 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/DeploymentAdminPermission.java,v 1.40 2006/07/12 21:22:10 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.deploymentadmin;
+
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.*;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * DeploymentAdminPermission controls access to the Deployment Admin service.<p>
+ *
+ * The permission uses a filter string formatted similarly to the {@link org.osgi.framework.Filter}.
+ * The filter determines the target of the permission. The <code>DeploymentAdminPermission</code> uses the
+ * <code>name</code> and the <code>signer</code> filter attributes only. The value of the <code>signer</code>
+ * attribute is matched against the signer chain (represented with its semicolon separated Distinguished Name chain)
+ * of the Deployment Package, and the value of the <code>name</code> attribute is matched against the value of the
+ * "DeploymentPackage-Name" manifest header of the Deployment Package. Example:
+ *
+ * <ul>
+ * <li>(signer=cn = Bugs Bunny, o = ACME, c = US)</li>
+ * <li>(name=org.osgi.ExampleApp)</li>
+ * </ul>
+ *
+ * Wildcards also can be used:<p>
+ *
+ * <pre>
+ * (signer=cn=*,o=ACME,c=*)
+ * </pre>
+ * "cn" and "c" may have an arbitrary value
+ *
+ * <pre>
+ * (signer=*, o=ACME, c=US)
+ * </pre>
+ * Only the value of "o" and "c" are significant
+ *
+ * <pre>
+ * (signer=* ; ou=S & V, o=Tweety Inc., c=US)
+ * </pre>
+ * The first element of the certificate chain is
+ * not important, only the second (the
+ * Distingushed Name of the root certificate)
+ *
+ * <pre>
+ * (signer=- ; *, o=Tweety Inc., c=US)
+ * </pre>
+ * The same as the previous but '-' represents
+ * zero or more certificates, whereas the asterisk
+ * only represents a single certificate
+ *
+ * <pre>
+ * (name=*)
+ * </pre>
+ * The name of the Deployment Package doesn't matter
+ *
+ * <pre>
+ * (name=org.osgi.*)
+ * </pre>
+ * The name has to begin with "org.osgi."
+ *
+ * <p>The following actions are allowed:<p>
+ *
+ * <b>list</b>
+ * <p>
+ * A holder of this permission can access the inventory information of the deployment
+ * packages selected by the <filter> string. The filter selects the deployment packages
+ * on which the holder of the permission can acquire detailed inventory information.
+ * See {@link DeploymentAdmin#getDeploymentPackage(Bundle)},
+ * {@link DeploymentAdmin#getDeploymentPackage(String)} and
+ * {@link DeploymentAdmin#listDeploymentPackages}.<p>
+ *
+ * <b>install</b><p>
+ *
+ * A holder of this permission can install/update deployment packages if the deployment
+ * package satisfies the <filter> string. See {@link DeploymentAdmin#installDeploymentPackage}.<p>
+ *
+ * <b>uninstall</b><p>
+ *
+ * A holder of this permission can uninstall deployment packages if the deployment
+ * package satisfies the <filter> string. See {@link DeploymentPackage#uninstall}.<p>
+ *
+ * <b>uninstall_forced</b><p>
+ *
+ * A holder of this permission can forcefully uninstall deployment packages if the deployment
+ * package satisfies the <filter> string. See {@link DeploymentPackage#uninstallForced}.<p>
+ *
+ * <b>cancel</b><p>
+ *
+ * A holder of this permission can cancel an active deployment action. This action being
+ * cancelled could correspond to the install, update or uninstall of a deployment package
+ * that satisfies the <filter> string. See {@link DeploymentAdmin#cancel}<p>
+ *
+ * <b>metadata</b><p>
+ *
+ * A holder of this permission is able to retrieve metadata information about a Deployment
+ * Package (e.g. is able to ask its manifest hedares).
+ * See {@link org.osgi.service.deploymentadmin.DeploymentPackage#getBundle(String)},
+ * {@link org.osgi.service.deploymentadmin.DeploymentPackage#getBundleInfos()},
+ * {@link org.osgi.service.deploymentadmin.DeploymentPackage#getHeader(String)},
+ * {@link org.osgi.service.deploymentadmin.DeploymentPackage#getResourceHeader(String, String)},
+ * {@link org.osgi.service.deploymentadmin.DeploymentPackage#getResourceProcessor(String)},
+ * {@link org.osgi.service.deploymentadmin.DeploymentPackage#getResources()}<p>
+ *
+ * The actions string is converted to lowercase before processing.
+ */
+public final class DeploymentAdminPermission extends Permission {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constant String to the "install" action.<p>
+ *
+ * @see DeploymentAdmin#installDeploymentPackage(InputStream)
+ */
+ public static final String INSTALL = "install";
+
+ /**
+ * Constant String to the "list" action.<p>
+ *
+ * @see DeploymentAdmin#listDeploymentPackages()
+ * @see DeploymentAdmin#getDeploymentPackage(String)
+ * @see DeploymentAdmin#getDeploymentPackage(Bundle)
+ */
+ public static final String LIST = "list";
+
+ /**
+ * Constant String to the "uninstall" action.<p>
+ *
+ * @see DeploymentPackage#uninstall()
+ */
+ public static final String UNINSTALL = "uninstall";
+
+ /**
+ * Constant String to the "uninstall_forced" action.<p>
+ *
+ * @see DeploymentPackage#uninstallForced()
+ */
+ public static final String UNINSTALL_FORCED = "uninstall_forced";
+
+ /**
+ * Constant String to the "cancel" action.<p>
+ *
+ * @see DeploymentAdmin#cancel
+ */
+ public static final String CANCEL = "cancel";
+
+ /**
+ * Constant String to the "metadata" action.<p>
+ *
+ * @see org.osgi.service.deploymentadmin.DeploymentPackage#getBundle(String)
+ * @see org.osgi.service.deploymentadmin.DeploymentPackage#getBundleInfos()
+ * @see org.osgi.service.deploymentadmin.DeploymentPackage#getHeader(String)
+ * @see org.osgi.service.deploymentadmin.DeploymentPackage#getResourceHeader(String, String)
+ * @see org.osgi.service.deploymentadmin.DeploymentPackage#getResourceProcessor(String)
+ * @see org.osgi.service.deploymentadmin.DeploymentPackage#getResources()
+ */
+ public static final String METADATA = "metadata";
+
+ private static final String delegateProperty = "org.osgi.vendor.deploymentadmin";
+ private static final Constructor constructor;
+ private final Permission delegate;
+ static {
+ constructor = (Constructor) AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ String pckg = System.getProperty(delegateProperty);
+ if (null == pckg)
+ throw new RuntimeException("Property '" + delegateProperty + "' is not set");
+ try {
+ Class c = Class.forName(pckg + ".DeploymentAdminPermission");
+ return c.getConstructor(new Class[] {String.class, String.class});
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }});
+ }
+
+ /**
+ * Creates a new <code>DeploymentAdminPermission</code> object for the given <code>name</code> and
+ * <code>action</code>.<p>
+ * The <code>name</code> parameter identifies the target depolyment package the permission
+ * relates to. The <code>actions</code> parameter contains the comma separated list of allowed actions.
+ *
+ * @param name filter string, must not be null.
+ * @param actions action string, must not be null. "*" means all the possible actions.
+ * @throws IllegalArgumentException if the filter is invalid, the list of actions
+ * contains unknown operations or one of the parameters is null
+ */
+ public DeploymentAdminPermission(String name, String actions) {
+ super(name);
+ try {
+ try {
+ delegate = (Permission) constructor.newInstance(new Object[] {name, actions});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Checks two DeploymentAdminPermission objects for equality.
+ * Two permission objects are equal if: <p>
+ *
+ * <ul>
+ * <li>their target filters are semantically equal and</li>
+ * <li>their actions are the same</li>
+ * </ul>
+ *
+ * @param obj The reference object with which to compare.
+ * @return true if the two objects are equal.
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (!(obj instanceof DeploymentAdminPermission))
+ return false;
+ DeploymentAdminPermission dap = (DeploymentAdminPermission) obj;
+ return delegate.equals(dap.delegate);
+ }
+
+ /**
+ * Returns hash code for this permission object.
+ *
+ * @return Hash code for this permission object.
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ /**
+ * Returns the String representation of the action list.<p>
+ * The method always gives back the actions in the following (alphabetical) order:
+ * <code>cancel, install, list, metadata, uninstall, uninstall_forced</code>
+ *
+ * @return Action list of this permission instance. This is a comma-separated
+ * list that reflects the action parameter of the constructor.
+ * @see java.security.Permission#getActions()
+ */
+ public String getActions() {
+ return delegate.getActions();
+ }
+
+ /**
+ * Checks if this DeploymentAdminPermission would imply the parameter permission.<p>
+ * Precondition of the implication is that the action set of this permission is the superset
+ * of the action set of the other permission. Further rules of implication are determined
+ * by the {@link org.osgi.framework.Filter} rules and the "OSGi Service Platform, Core
+ * Specification Release 4, Chapter Certificate Matching".<p>
+ *
+ * The allowed attributes are: <code>name</code> (the symbolic name of the deployment
+ * package) and <code>signer</code> (the signer of the deployment package). In both cases
+ * wildcards can be used.<p>
+ *
+ * Examples:
+ *
+ * <pre>
+ * 1. DeploymentAdminPermission("(name=org.osgi.ExampleApp)", "list")
+ * 2. DeploymentAdminPermission("(name=org.osgi.ExampleApp)", "list, install")
+ * 3. DeploymentAdminPermission("(name=org.osgi.*)", "list")
+ * 4. DeploymentAdminPermission("(signer=*, o=ACME, c=US)", "list")
+ * 5. DeploymentAdminPermission("(signer=cn = Bugs Bunny, o = ACME, c = US)", "list")
+ * </pre><p>
+ *
+ * <pre>
+ * 1. implies 1.
+ * 2. implies 1.
+ * 1. doesn't implies 2.
+ * 3. implies 1.
+ * 4. implies 5.
+ * </pre>
+ *
+ * @param permission Permission to check.
+ * @return true if this DeploymentAdminPermission object implies the
+ * specified permission.
+ * @see java.security.Permission#implies(java.security.Permission)
+ * @see org.osgi.framework.Filter
+ */
+ public boolean implies(Permission permission) {
+ if (!(permission instanceof DeploymentAdminPermission))
+ return false;
+
+ DeploymentAdminPermission dap = (DeploymentAdminPermission) permission;
+
+ return delegate.implies(dap.delegate);
+ }
+
+ /**
+ * Returns a new PermissionCollection object for storing DeploymentAdminPermission
+ * objects.
+ *
+ * @return The new PermissionCollection.
+ * @see java.security.Permission#newPermissionCollection()
+ */
+ public PermissionCollection newPermissionCollection() {
+ return delegate.newPermissionCollection();
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentException.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentException.java
new file mode 100644
index 0000000..bf5c6e6
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentException.java
@@ -0,0 +1,266 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/DeploymentException.java,v 1.20 2006/07/12 21:22:10 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.deploymentadmin;
+
+import java.io.InputStream;
+
+/**
+ * Checked exception received when something fails during any deployment
+ * processes. A <code>DeploymentException</code> always contains an error code
+ * (one of the constants specified in this class), and may optionally contain
+ * the textual description of the error condition and a nested cause exception.
+ */
+public class DeploymentException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 916011169146851101L;
+
+ /**
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)},
+ * {@link DeploymentPackage#uninstall()} and {@link DeploymentPackage#uninstallForced()}
+ * methods can throw {@link DeploymentException} with this error code if the
+ * {@link DeploymentAdmin#cancel()} method is called from another thread.
+ */
+ public static final int CODE_CANCELLED = 401;
+
+ /**
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * methods can throw {@link DeploymentException} with this error code if
+ * the got InputStream is not a jar.
+ */
+ public static final int CODE_NOT_A_JAR = 404;
+
+ /**
+ * Order of files in the deployment package is bad. The right order is the
+ * following:<p>
+ *
+ * <ol>
+ * <li>META-INF/MANIFEST.MF</li>
+ * <li>META-INF/*.SF, META-INF/*.DSA, META-INF/*.RS</li>
+ * <li>Localization files</li>
+ * <li>Bundles</li>
+ * <li>Resources</li>
+ * </ol>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_ORDER_ERROR = 450;
+
+ /**
+ * Missing mandatory manifest header.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)} can throw
+ * exception with this error code.
+ */
+ public static final int CODE_MISSING_HEADER = 451;
+
+ /**
+ * Syntax error in any manifest header.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_BAD_HEADER = 452;
+
+ /**
+ * Fix pack version range doesn't fit to the version of the target
+ * deployment package or the target deployment package of the fix pack
+ * doesn't exist.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_MISSING_FIXPACK_TARGET = 453;
+
+ /**
+ * A bundle in the deployment package is marked as DeploymentPackage-Missing
+ * but there is no such bundle in the target deployment package.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_MISSING_BUNDLE = 454;
+
+ /**
+ * A resource in the source deployment package is marked as
+ * DeploymentPackage-Missing but there is no such resource in the target
+ * deployment package.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_MISSING_RESOURCE = 455;
+
+ /**
+ * Bad deployment package signing.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_SIGNING_ERROR = 456;
+
+ /**
+ * Bundle symbolic name is not the same as defined by the deployment package
+ * manifest.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_BUNDLE_NAME_ERROR = 457;
+
+ /**
+ * Matched resource processor service is a customizer from another
+ * deployment package.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_FOREIGN_CUSTOMIZER = 458;
+
+ /**
+ * Bundle with the same symbolic name alerady exists.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_BUNDLE_SHARING_VIOLATION = 460;
+
+ /**
+ * An artifact of any resource already exists.<p>
+ *
+ * This exception is thrown when the called resource processor throws a
+ * <code>ResourceProcessorException</code> with the
+ * {@link org.osgi.service.deploymentadmin.spi.ResourceProcessorException#CODE_RESOURCE_SHARING_VIOLATION}
+ * error code.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)}
+ * throws exception with this error code.
+ */
+ public static final int CODE_RESOURCE_SHARING_VIOLATION = 461;
+
+ /**
+ * Exception with this error code is thrown when one of the Resource Processors
+ * involved in the deployment session threw a <code>ResourceProcessorException</code> with the
+ * {@link org.osgi.service.deploymentadmin.spi.ResourceProcessorException#CODE_PREPARE} error
+ * code.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)} and
+ * {@link DeploymentPackage#uninstall()} methods throw exception with this error code.
+ */
+ public static final int CODE_COMMIT_ERROR = 462;
+
+ /**
+ * Other error condition.<p>
+ *
+ * All Deployment Admin methods which throw <code>DeploymentException</code>
+ * can throw an exception with this error code if the error condition cannot be
+ * categorized.
+ */
+ public static final int CODE_OTHER_ERROR = 463;
+
+ /**
+ * The Resource Processor service with the given PID (see
+ * <code>Resource-Processor</code> manifest header) is not found.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)},
+ * {@link DeploymentPackage#uninstall()} and
+ * {@link DeploymentPackage#uninstallForced()}
+ * throws exception with this error code.
+ */
+ public static final int CODE_PROCESSOR_NOT_FOUND = 464;
+
+ /**
+ * When a client requests a new session with an install or uninstall
+ * operation, it must block that call until the earlier session is
+ * completed. The Deployment Admin service must throw a Deployment Exception
+ * with this error code when the session can not be created after an appropriate
+ * time out period.<p>
+ *
+ * {@link DeploymentAdmin#installDeploymentPackage(InputStream)},
+ * {@link DeploymentPackage#uninstall()} and
+ * {@link DeploymentPackage#uninstallForced()}
+ * throws exception with this error code.
+ */
+ public static final int CODE_TIMEOUT = 465;
+
+ private final int code;
+ private final String message;
+ private final Throwable cause;
+
+ /**
+ * Create an instance of the exception.
+ *
+ * @param code The error code of the failure. Code should be one of the
+ * predefined integer values (<code>CODE_X</code>).
+ * @param message Message associated with the exception
+ * @param cause the originating exception
+ */
+ public DeploymentException(int code, String message, Throwable cause) {
+ this.code = code;
+ this.message = message;
+ this.cause = cause;
+ }
+
+ /**
+ * Create an instance of the exception. Cause exception is implicitly set to
+ * null.
+ *
+ * @param code The error code of the failure. Code should be one of the
+ * predefined integer values (<code>CODE_X</code>).
+ * @param message Message associated with the exception
+ */
+ public DeploymentException(int code, String message) {
+ this(code, message, null);
+ }
+
+ /**
+ * Create an instance of the exception. Cause exception and message are
+ * implicitly set to null.
+ *
+ * @param code The error code of the failure. Code should be one of the
+ * predefined integer values (<code>CODE_X</code>).
+ */
+ public DeploymentException(int code) {
+ this(code, null, null);
+ }
+
+ /**
+ * @return Returns the cause.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * @return Returns the code.
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * @return Returns the message.
+ */
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentPackage.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentPackage.java
new file mode 100644
index 0000000..0155771
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/DeploymentPackage.java
@@ -0,0 +1,240 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/DeploymentPackage.java,v 1.26 2006/07/12 21:22:10 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.deploymentadmin;
+
+import org.osgi.framework.*;
+
+/**
+ * The <code>DeploymentPackage</code> object represents a deployment package (already installed
+ * or being currently processed). A Deployment Package groups resources as a unit
+ * of management. A deployment package is something that can be installed, updated,
+ * and uninstalled as a unit. A deployment package is a reified concept, like a bundle,
+ * in an OSGi Service Platform. It is not known by the OSGi Framework, but it is managed
+ * by the Deployment Admin service. A deployment package is a stream of resources
+ * (including bundles) which, once processed, will result in new artifacts (effects on
+ * the system) being added to the OSGi platform. These new artifacts can include
+ * installed Bundles, new configuration objects added to the Configuration Admin service,
+ * new Wire objects added to the Wire Admin service, or changed system properties, etc. All
+ * the changes caused by the processing of a deployment package are persistently
+ * associated with the deployment package, so that they can be appropriately cleaned
+ * up when the deployment package is uninstalled. There is a strict no overlap rule
+ * imposed on deployment packages. Two deployment packages are not allowed to create or
+ * manipulate the same artifact. Obviously, this means that a bundle cannot be in two
+ * different deployment packagess. Any violation of this no overlap rule is considered
+ * an error and the install or update of the offending deployment package must be aborted.<p>
+ *
+ * The Deployment Admin service should do as much as possible to ensure transactionality.
+ * It means that if a deployment package installation, update or removal (uninstall) fails
+ * all the side effects caused by the process should be disappeared and the system
+ * should be in the state in which it was before the process.<p>
+ *
+ * If a deployment package is being updated the old version is visible through the
+ * <code>DeploymentPackage</code> interface until the update process ends. After the
+ * package is updated the updated version is visible and the old one is not accessible
+ * any more.
+ */
+public interface DeploymentPackage {
+
+ /**
+ * Gives back the state of the deployment package whether it is stale or not).
+ * After uninstall of a deployment package it becomes stale. Any active method calls to a
+ * stale deployment package raise {@link IllegalStateException}.
+ * Active methods are the following:<p>
+ *
+ * <ul>
+ * <li>{@link #getBundle(String)}</li>
+ * <li>{@link #getResourceProcessor(String)}</li>
+ * <li>{@link #uninstall()}</li>
+ * <li>{@link #uninstallForced()}</li>
+ * </ul>
+ *
+ * @return <code>true</code> if the deployment package is stale. <code>false</code>
+ * otherwise
+ * @see #uninstall
+ * @see #uninstallForced
+ */
+ boolean isStale();
+
+ /**
+ * Returns the Deployment Pacakage Symbolic Name of the package.
+ *
+ * @return The name of the deployment package. It cannot be null.
+ */
+ String getName();
+
+ /**
+ * Returns the version of the deployment package.
+ * @return version of the deployment package. It cannot be null.
+ */
+ Version getVersion();
+
+ /**
+ * Returns an array of {@link BundleInfo} objects representing the bundles specified in the manifest
+ * of this deployment package. Its size is equal to the number of the bundles in the deployment package.
+ *
+ * @return array of <code>BundleInfo</code> objects
+ * @throws SecurityException if the caller doesn't have the appropriate {@link DeploymentAdminPermission}
+ * with "metadata" action
+ */
+ BundleInfo[] getBundleInfos();
+
+ /**
+ * Returns the bundle instance, which is part of this deployment package, that corresponds
+ * to the bundle's symbolic name passed in the <code>symbolicName</code> parameter.
+ * This method will return null for request for bundles that are not part
+ * of this deployment package.<p>
+ *
+ * As this instance is transient (i.e. a bundle can be removed at any time because of the
+ * dynamic nature of the OSGi platform), this method may also return null if the bundle
+ * is part of this deployment package, but is not currently defined to the framework.
+ *
+ * @param symbolicName the symbolic name of the requested bundle
+ * @return The <code>Bundle</code> instance for a given bundle symbolic name.
+ * @throws SecurityException if the caller doesn't have the appropriate {@link DeploymentAdminPermission}
+ * with "metadata" action
+ * @throws IllegalStateException if the package is stale
+ */
+ Bundle getBundle(String symbolicName);
+
+ /**
+ * Returns an array of strings representing the resources (including bundles) that
+ * are specified in the manifest of this deployment package. A string element of the
+ * array is the same as the value of the "Name" attribute in the manifest. The array
+ * contains the bundles as well.<p>
+ *
+ * E.g. if the "Name" section of the resource (or individual-section as the
+ * <a href="http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html#Manifest%20Specification">Manifest Specification</a>
+ * calls it) in the manifest is the following
+
+ * <pre>
+ * Name: foo/readme.txt
+ * Resource-Processor: foo.rp
+ * </pre>
+ *
+ * then the corresponding array element is the "foo/readme.txt" string.<p>
+ *
+ * @return The string array corresponding to resources. It cannot be null but its
+ * length can be zero.
+ * @throws SecurityException if the caller doesn't have the appropriate {@link DeploymentAdminPermission}
+ * with "metadata" action
+ */
+ String[] getResources();
+
+ /**
+ * At the time of deployment, resource processor service instances are located to
+ * resources contained in a deployment package.<p>
+ *
+ * This call returns a service reference to the corresponding service instance.
+ * If the resource is not part of the deployment package or this call is made during
+ * deployment, prior to the locating of the service to process a given resource, null will
+ * be returned. Services can be updated after a deployment package has been deployed.
+ * In this event, this call will return a reference to the updated service, not to the
+ * instance that was used at deployment time.
+ *
+ * @param resource the name of the resource (it is the same as the value of the "Name"
+ * attribute in the deployment package's manifest)
+ * @return resource processor for the resource or <code>null</cpde>.
+ * @throws SecurityException if the caller doesn't have the appropriate {@link DeploymentAdminPermission}
+ * with "metadata" action
+ * @throws IllegalStateException if the package is stale
+ */
+ ServiceReference getResourceProcessor(String resource);
+
+ /**
+ * Returns the requested deployment package manifest header from the main section.
+ * Header names are case insensitive. If the header doesn't exist it returns null.<p>
+ *
+ * If the header is localized then the localized value is returned (see OSGi Service Platform,
+ * Mobile Specification Release 4 - Localization related chapters).
+ *
+ * @param header the requested header
+ * @return the value of the header or <code>null</code> if the header does not exist
+ * @throws SecurityException if the caller doesn't have the appropriate {@link DeploymentAdminPermission}
+ * with "metadata" action
+ */
+ String getHeader(String header);
+
+ /**
+ * Returns the requested deployment package manifest header from the name
+ * section determined by the resource parameter. Header names are case insensitive.
+ * If the resource or the header doesn't exist it returns null.<p>
+ *
+ * If the header is localized then the localized value is returned (see OSGi Service Platform,
+ * Mobile Specification Release 4 - Localization related chapters).
+
+ * @param resource the name of the resource (it is the same as the value of the "Name"
+ * attribute in the deployment package's manifest)
+ * @param header the requested header
+ * @return the value of the header or <code>null</code> if the resource or the header doesn't exist
+ * @throws SecurityException if the caller doesn't have the appropriate {@link DeploymentAdminPermission}
+ * with "metadata" action
+ */
+ String getResourceHeader(String resource, String header);
+
+ /**
+ * Uninstalls the deployment package. After uninstallation, the deployment package
+ * object becomes stale. This can be checked by using {@link #isStale()},
+ * which will return <code>true</code> when stale.<p>
+ *
+ * @throws DeploymentException if the deployment package could not be successfully uninstalled.
+ * For detailed error code description see {@link DeploymentException}.
+ * @throws SecurityException if the caller doesn't have the appropriate
+ * {@link DeploymentAdminPermission}("<filter>", "uninstall") permission.
+ * @throws IllegalStateException if the package is stale
+ */
+ void uninstall() throws DeploymentException;
+
+ /**
+ * This method is called to completely uninstall a deployment package, which couldn't be uninstalled
+ * using traditional means ({@link #uninstall()}) due to exceptions. After uninstallation, the deployment
+ * package object becomes stale. This can be checked by using {@link #isStale()},
+ * which will return <code>true</code> when stale.<p>
+ *
+ * The method forces removal of the Deployment Package from the repository maintained by the
+ * Deployment Admin service. This method follows the same steps as {@link #uninstall}. However,
+ * any errors or the absence of Resource Processor services are ignored, they must not cause a roll back.
+ * These errors should be logged.
+ *
+ * @return true if the operation was successful
+ * @throws DeploymentException only {@link DeploymentException#CODE_TIMEOUT} and
+ * {@link DeploymentException#CODE_CANCELLED} can be thrown. For detailed error code description
+ * see {@link DeploymentException}.
+ * @throws SecurityException if the caller doesn't have the appropriate
+ * {@link DeploymentAdminPermission}("<filter>", "uninstall_forced") permission.
+ * @throws IllegalStateException if the package is stale
+ */
+ boolean uninstallForced() throws DeploymentException;
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ * @return a hash code value for this object
+ */
+ int hashCode();
+
+ /**
+ * Indicates whether some other object is "equal to" this one. Two deployment packages
+ * are equal if they have the same deployment package symbolicname and version.
+ *
+ * @param other the reference object with which to compare.
+ * @return true if this object is the same as the obj argument; false otherwise.
+ */
+ boolean equals(Object other);
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/package.html b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/package.html
new file mode 100644
index 0000000..eada510
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/package.html,v 1.4 2006/07/12 21:07:12 hargrave Exp $ -->
+<BODY>
+<p>Deployment Admin Package Version 1.0.
+<p>Bundles wishing to use this package must list the package
+in the <TT>Import-Package</TT> header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.service.deploymentadmin; version=1.0
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentCustomizerPermission.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentCustomizerPermission.java
new file mode 100644
index 0000000..681329e
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentCustomizerPermission.java
@@ -0,0 +1,200 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/spi/DeploymentCustomizerPermission.java,v 1.6 2006/06/21 15:16:13 hargrave Exp $
+ *
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.deploymentadmin.spi;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.security.*;
+
+import org.osgi.service.deploymentadmin.DeploymentAdminPermission;
+
+/**
+ * The <code>DeploymentCustomizerPermission</code> permission gives the right to
+ * Resource Processors to access a bundle's (residing in a Deployment Package) private area.
+ * The bundle and the Resource Processor (customizer) have to be in the same Deployment Package.<p>
+ *
+ * The Resource Processor that has this permission is allowed to access the bundle's
+ * private area by calling the {@link DeploymentSession#getDataFile} method during the session
+ * (see {@link DeploymentSession}). After the session ends the FilePermissions are withdrawn.
+ * The Resource Processor will have <code>FilePermission</code> with "read", "write" and "delete"
+ * actions for the returned {@link java.io.File} that represents the the base directory of the
+ * persistent storage area and for its subdirectories.<p>
+ *
+ * The actions string is converted to lowercase before processing.
+ */
+public class DeploymentCustomizerPermission extends Permission {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constant String to the "privatearea" action.
+ */
+ public static final String PRIVATEAREA = "privatearea";
+
+ private static final String delegateProperty = "org.osgi.vendor.deploymentadmin";
+ private static final Constructor constructor;
+ private final Permission delegate;
+ static {
+ constructor = (Constructor) AccessController.doPrivileged(new PrivilegedAction() {
+ public Object run() {
+ String pckg = System.getProperty(delegateProperty);
+ if (null == pckg)
+ throw new RuntimeException("Property '" + delegateProperty + "' is not set");
+ try {
+ Class c = Class.forName(pckg + ".DeploymentCustomizerPermission");
+ return c.getConstructor(new Class[] {String.class, String.class});
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e.getMessage());
+ }
+ }});
+ }
+
+ /**
+ * Creates a new <code>DeploymentCustomizerPermission</code> object for the given
+ * <code>name</code> and <code>action</code>.<p>
+ *
+ * The name parameter is a filter string. This filter has the same syntax as an OSGi filter
+ * but only the "name" attribute is allowed. The value of the attribute
+ * is a Bundle Symbolic Name that represents a bundle. The only allowed action is the
+ * "privatearea" action. E.g.
+ *
+ * <pre>
+ * Permission perm = new DeploymentCustomizerPermission("(name=com.acme.bundle)", "privatearea");
+ * </pre>
+ *
+ * The Resource Processor that has this permission is allowed to access the bundle's
+ * private area by calling the {@link DeploymentSession#getDataFile} method. The
+ * Resource Processor will have <code>FilePermission</code> with "read", "write" and "delete"
+ * actions for the returned {@link java.io.File} and its subdirectories during the deployment
+ * session.
+ *
+ * @param name Bundle Symbolic Name of the target bundle, must not be <code>null</code>.
+ * @param actions action string (only the "privatearea" or "*" action is valid; "*" means all
+ * the possible actions), must not be <code>null</code>.
+ * @throws IllegalArgumentException if the filter is invalid, the list of actions
+ * contains unknown operations or one of the parameters is <code>null</code>
+ */
+ public DeploymentCustomizerPermission(String name, String actions) {
+ super(name);
+ try {
+ try {
+ delegate = (Permission) constructor.newInstance(new Object[] {name, actions});
+ }
+ catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ catch (Error e) {
+ throw e;
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Throwable e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ /**
+ * Checks two DeploymentCustomizerPermission objects for equality.
+ * Two permission objects are equal if: <p>
+ *
+ * <ul>
+ * <li>their target filters are equal (semantically and not character by
+ * character) and</li>
+ * <li>their actions are the same</li>
+ * </ul>
+ *
+ * @param obj the reference object with which to compare.
+ * @return true if the two objects are equal.
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ public boolean equals(Object obj) {
+ if (obj == this)
+ return true;
+ if (!(obj instanceof DeploymentCustomizerPermission))
+ return false;
+ DeploymentCustomizerPermission dcp = (DeploymentCustomizerPermission) obj;
+ return delegate.equals(dcp.delegate);
+ }
+
+ /**
+ * Returns hash code for this permission object.
+ *
+ * @return Hash code for this permission object.
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ return delegate.hashCode();
+ }
+
+ /**
+ * Returns the String representation of the action list.
+ *
+ * @return Action list of this permission instance. It is always "privatearea".
+ * @see java.security.Permission#getActions()
+ */
+ public String getActions() {
+ return delegate.getActions();
+ }
+
+ /**
+ * Checks if this DeploymentCustomizerPermission would imply the parameter permission.
+ * This permission implies another DeploymentCustomizerPermission permission if:
+ *
+ * <ul>
+ * <li>both of them has the "privatearea" action (other actions are not allowed) and</li>
+ * <li>their filters (only name attribute is allowed in the filters) match similarly to
+ * {@link DeploymentAdminPermission}.</li>
+ * </ul>
+ *
+ * The value of the name attribute means Bundle Symbolic Name and not Deployment Package
+ * Symbolic Name here!<p>
+ *
+ * @param permission Permission to check.
+ * @return true if this DeploymentCustomizerPermission object implies the
+ * specified permission.
+ * @see java.security.Permission#implies(java.security.Permission)
+ */
+ public boolean implies(Permission permission) {
+ if (!(permission instanceof DeploymentCustomizerPermission))
+ return false;
+
+ DeploymentCustomizerPermission dcp = (DeploymentCustomizerPermission) permission;
+
+ return delegate.implies(dcp.delegate);
+ }
+
+ /**
+ * Returns a new PermissionCollection object for storing DeploymentCustomizerPermission
+ * objects.
+ *
+ * @return The new PermissionCollection.
+ * @see java.security.Permission#newPermissionCollection()
+ */
+ public PermissionCollection newPermissionCollection() {
+ return delegate.newPermissionCollection();
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentSession.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentSession.java
new file mode 100644
index 0000000..87a5b1f
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/DeploymentSession.java
@@ -0,0 +1,96 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/spi/DeploymentSession.java,v 1.6 2006/06/16 16:31:39 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.deploymentadmin.spi;
+
+import org.osgi.service.deploymentadmin.DeploymentPackage;
+
+/**
+ * The session interface represents a currently running deployment session
+ * (install/update/uninstall).<p>
+ *
+ * When a deployment package is installed the target package, when uninstalled the
+ * source package is an empty deployment package. The empty deployment package is a virtual
+ * entity it doesn't appear for the outside world. It is only visible on the
+ * DeploymentSession interface used by Resource Processors. Although the empty package
+ * is only visible for Resource Processors it has the following characteristics:<p>
+ *
+ * <ul>
+ * <li>has version 0.0.0</li>
+ * <li>its name is an empty string</li>
+ * <li>it is stale</li>
+ * <li>it has no bundles
+ * (see {@link DeploymentPackage#getBundle(String)})</li>
+ * <li>it has no resources
+ * (see {@link DeploymentPackage#getResources()})</li>
+ * <li>it has no headers except <br/>
+ * <code>DeploymentPackage-SymbolicName</code> and <br/>
+ * <code>DeploymentPackage-Version</code> <br/>
+ * (see {@link DeploymentPackage#getHeader(String)})</li>
+ * <li>it has no resource headers (see
+ * {@link DeploymentPackage#getResourceHeader(String, String)})</li>
+ * <li>{@link DeploymentPackage#uninstall()} throws
+ * {@link java.lang.IllegalStateException}</li>
+ * <li>{@link DeploymentPackage#uninstallForced()} throws
+ * {@link java.lang.IllegalStateException}</li>
+ * </ul>
+ *
+ */
+public interface DeploymentSession {
+
+ /**
+ * If the deployment action is an update or an uninstall, this call returns
+ * the <code>DeploymentPackage</code> instance for the installed deployment package. If the
+ * deployment action is an install, this call returns the empty deploymet package (see
+ * {@link DeploymentPackage}).
+ *
+ * @return the target deployment package
+ * @see DeploymentPackage
+ */
+ DeploymentPackage getTargetDeploymentPackage();
+
+ /**
+ * If the deployment action is an install or an update, this call returns
+ * the <code>DeploymentPackage</code> instance that corresponds to the deployment package
+ * being streamed in for this session. If the deployment action is an uninstall, this call
+ * returns the empty deploymet package (see {@link DeploymentPackage}).
+ *
+ * @return the source deployment package
+ * @see DeploymentPackage
+ */
+ DeploymentPackage getSourceDeploymentPackage();
+
+ /**
+ * Returns the private data area of the specified bundle. The bundle must be part of
+ * either the source or the target deployment packages. The permission set the caller
+ * resource processor needs to manipulate the private area of the bundle is set by the
+ * Deployment Admin on the fly when this method is called. The permissions remain available
+ * during the deployment action only.<p>
+ *
+ * The bundle and the caller Resource Processor have to be in the same Deployment Package.
+ *
+ * @param bundle the bundle the private area belongs to
+ * @return file representing the private area of the bundle. It cannot be null.
+ * @throws SecurityException if the caller doesn't have the appropriate
+ * {@link DeploymentCustomizerPermission}("<filter>", "privatearea") permission.
+ * @see DeploymentPackage
+ * @see DeploymentCustomizerPermission
+ */
+ java.io.File getDataFile(org.osgi.framework.Bundle bundle);
+
+}
+
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessor.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessor.java
new file mode 100644
index 0000000..ec5a5fd
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessor.java
@@ -0,0 +1,139 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/spi/ResourceProcessor.java,v 1.6 2006/07/11 13:19:02 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.deploymentadmin.spi;
+
+import java.io.InputStream;
+
+/**
+ * ResourceProcessor interface is implemented by processors handling resource files
+ * in deployment packages. Resource Processors expose their services as standard OSGi services.
+ * Bundles exporting the service may arrive in the deployment package (customizers) or may be
+ * preregistered (they are installed prevoiusly). Resource processors has to define the
+ * <code>service.pid</code> standard OSGi service property which should be a unique string.<p>
+ *
+ * The order of the method calls on a particular Resource Processor in case of install/update
+ * session is the following:<p>
+ *
+ * <ol>
+ * <li>{@link #begin(DeploymentSession)}</li>
+ * <li>{@link #process(String, InputStream)} calls till there are resources to process
+ * or {@link #rollback()} and the further steps are ignored</li>
+ * <li>{@link #dropped(String)} calls till there are resources to drop
+ * <li>{@link #prepare()}</li>
+ * <li>{@link #commit()} or {@link #rollback()}</li>
+ * </ol>
+ *
+ * The order of the method calls on a particular Resource Processor in case of uninstall
+ * session is the following:<p>
+ *
+ * <ol>
+ * <li>{@link #begin(DeploymentSession)}</li>
+ * <li>{@link #dropAllResources()} or {@link #rollback()} and the further steps are ignored</li>
+ * <li>{@link #prepare()}</li>
+ * <li>{@link #commit()} or {@link #rollback()}</li>
+ * </ol>
+ */
+public interface ResourceProcessor {
+
+ /**
+ * Called when the Deployment Admin starts a new operation on the given deployment package,
+ * and the resource processor is associated a resource within the package. Only one
+ * deployment package can be processed at a time.
+ *
+ * @param session object that represents the current session to the resource processor
+ * @see DeploymentSession
+ */
+ void begin(DeploymentSession session);
+
+ /**
+ * Called when a resource is encountered in the deployment package for which this resource
+ * processor has been selected to handle the processing of that resource.
+ *
+ * @param name The name of the resource relative to the deployment package root directory.
+ * @param stream The stream for the resource.
+ * @throws ResourceProcessorException if the resource cannot be processed. Only
+ * {@link ResourceProcessorException#CODE_RESOURCE_SHARING_VIOLATION} and
+ * {@link ResourceProcessorException#CODE_OTHER_ERROR} error codes are allowed.
+ */
+ void process(String name, InputStream stream) throws ResourceProcessorException;
+
+ /**
+ * Called when a resource, associated with a particular resource processor, had belonged to
+ * an earlier version of a deployment package but is not present in the current version of
+ * the deployment package. This provides an opportunity for the processor to cleanup any
+ * memory and persistent data being maintained for the particular resource.
+ * This method will only be called during "update" deployment sessions.
+ *
+ * @param resource the name of the resource to drop (it is the same as the value of the
+ * "Name" attribute in the deployment package's manifest)
+ * @throws ResourceProcessorException if the resource is not allowed to be dropped. Only the
+ * {@link ResourceProcessorException#CODE_OTHER_ERROR} error code is allowed
+ */
+ void dropped(String resource) throws ResourceProcessorException;
+
+ /**
+ * This method is called during an "uninstall" deployment session.
+ * This method will be called on all resource processors that are associated with resources
+ * in the deployment package being uninstalled. This provides an opportunity for the processor
+ * to cleanup any memory and persistent data being maintained for the deployment package.
+ *
+ * @throws ResourceProcessorException if all resources could not be dropped. Only the
+ * {@link ResourceProcessorException#CODE_OTHER_ERROR} is allowed.
+ */
+ void dropAllResources() throws ResourceProcessorException;
+
+ /**
+ * This method is called on the Resource Processor immediately before calling the
+ * <code>commit</code> method. The Resource Processor has to check whether it is able
+ * to commit the operations since the last <code>begin</code> method call. If it determines
+ * that it is not able to commit the changes, it has to raise a
+ * <code>ResourceProcessorException</code> with the {@link ResourceProcessorException#CODE_PREPARE}
+ * error code.
+ *
+ * @throws ResourceProcessorException if the resource processor is able to determine it is
+ * not able to commit. Only the {@link ResourceProcessorException#CODE_PREPARE} error
+ * code is allowed.
+ */
+ void prepare() throws ResourceProcessorException;
+
+ /**
+ * Called when the processing of the current deployment package is finished.
+ * This method is called if the processing of the current deployment package was successful,
+ * and the changes must be made permanent.
+ */
+ void commit();
+
+
+ /**
+ * Called when the processing of the current deployment package is finished.
+ * This method is called if the processing of the current deployment package was unsuccessful,
+ * and the changes made during the processing of the deployment package should be removed.
+ */
+ void rollback();
+
+ /**
+ * Processing of a resource passed to the resource processor may take long.
+ * The <code>cancel()</code> method notifies the resource processor that it should
+ * interrupt the processing of the current resource. This method is called by the
+ * <code>DeploymentAdmin</code> implementation after the
+ * <code>DeploymentAdmin.cancel()</code> method is called.
+ */
+ void cancel();
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessorException.java b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessorException.java
new file mode 100644
index 0000000..eb9c3dc
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/ResourceProcessorException.java
@@ -0,0 +1,123 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/spi/ResourceProcessorException.java,v 1.7 2006/07/12 21:22:10 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.deploymentadmin.spi;
+
+import java.io.InputStream;
+
+/**
+ * Checked exception received when something fails during a call to a Resource
+ * Processor. A <code>ResourceProcessorException</code> always contains an error
+ * code (one of the constants specified in this class), and may optionally contain
+ * the textual description of the error condition and a nested cause exception.
+ */
+public class ResourceProcessorException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 9135007015668223386L;
+
+ /**
+ * Resource Processors are allowed to raise an exception with this error code
+ * to indicate that the processor is not able to commit the operations it made
+ * since the last call of {@link ResourceProcessor#begin(DeploymentSession)} method.<p>
+ *
+ * Only the {@link ResourceProcessor#prepare()} method is allowed to throw exception
+ * with this error code.
+ */
+ public static final int CODE_PREPARE = 1;
+
+ /**
+ * An artifact of any resource already exists.<p>
+ *
+ * Only the {@link ResourceProcessor#process(String, InputStream)} method
+ * is allowed to throw exception with this error code.
+ */
+ public static final int CODE_RESOURCE_SHARING_VIOLATION = 461;
+
+ /**
+ * Other error condition.<p>
+ *
+ * All Resource Processor methods which throw <code>ResourceProcessorException</code>
+ * is allowed throw an exception with this erro code if the error condition cannot be
+ * categorized.
+ */
+ public static final int CODE_OTHER_ERROR = 463;
+
+ private final int code;
+ private final String message;
+ private final Throwable cause;
+
+ /**
+ * Create an instance of the exception.
+ *
+ * @param code The error code of the failure. Code should be one of the
+ * predefined integer values (<code>CODE_X</code>).
+ * @param message Message associated with the exception
+ * @param cause the originating exception
+ */
+ public ResourceProcessorException(int code, String message, Throwable cause) {
+ this.code = code;
+ this.message = message;
+ this.cause = cause;
+ }
+
+ /**
+ * Create an instance of the exception. Cause exception is implicitly set to
+ * null.
+ *
+ * @param code The error code of the failure. Code should be one of the
+ * predefined integer values (<code>CODE_X</code>).
+ * @param message Message associated with the exception
+ */
+ public ResourceProcessorException(int code, String message) {
+ this(code, message, null);
+ }
+
+ /**
+ * Create an instance of the exception. Cause exception and message are
+ * implicitly set to null.
+ *
+ * @param code The error code of the failure. Code should be one of the
+ * predefined integer values (<code>CODE_X</code>).
+ */
+ public ResourceProcessorException(int code) {
+ this(code, null, null);
+ }
+
+ /**
+ * @return Returns the cause.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * @return Returns the code.
+ */
+ public int getCode() {
+ return code;
+ }
+
+ /**
+ * @return Returns the message.
+ */
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/package.html b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/package.html
new file mode 100644
index 0000000..f5f6c7c
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/package.html
@@ -0,0 +1,12 @@
+<!-- $Header: /cvshome/build/org.osgi.service.deploymentadmin/src/org/osgi/service/deploymentadmin/spi/package.html,v 1.3 2006/07/12 21:07:12 hargrave Exp $ -->
+<BODY>
+<p>Deployment Admin SPI Package Version 1.0.
+The SPI is used by Resource Processors.
+<p>Bundles wishing to use this package must list the package
+in the <TT>Import-Package</TT> header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.service.deploymentadmin.spi; version=1.0
+</pre>
+</BODY>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/deploymentadmin/spi/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/deploymentadmin/src/main/java/org/osgi/service/event/Event.java b/deploymentadmin/src/main/java/org/osgi/service/event/Event.java
new file mode 100644
index 0000000..b0df6ee
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/event/Event.java
@@ -0,0 +1,195 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.event/src/org/osgi/service/event/Event.java,v 1.8 2006/07/12 13:17:04 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.event;
+
+import java.util.*;
+
+import org.osgi.framework.Filter;
+
+/**
+ * An event.
+ *
+ * <code>Event</code> objects are delivered to <code>EventHandler</code>
+ * services which subsrcibe to the topic of the event.
+ *
+ * @version $Revision: 1.8 $
+ */
+public class Event {
+ /**
+ * The topic of this event.
+ */
+ String topic;
+ /**
+ * The properties carried by this event. Keys are strings and values are
+ * objects
+ */
+ Hashtable properties;
+
+ /**
+ * Constructs an event.
+ *
+ * @param topic The topic of the event.
+ * @param properties The event's properties (may be <code>null</code>).
+ *
+ * @throws IllegalArgumentException If topic is not a valid topic name.
+ */
+ public Event(String topic, Dictionary properties) {
+ this.topic = topic;
+ validateTopicName();
+ this.properties = new Hashtable();
+ if (properties != null) {
+ for (Enumeration e = properties.keys(); e.hasMoreElements();) {
+ String key = (String) e.nextElement();
+ Object value = properties.get(key);
+ this.properties.put(key, value);
+ }
+ }
+ this.properties.put(EventConstants.EVENT_TOPIC, topic);
+ }
+
+ /**
+ * Retrieves a property.
+ *
+ * @param name the name of the property to retrieve
+ *
+ * @return The value of the property, or <code>null</code> if not found.
+ */
+ public final Object getProperty(String name) {
+ return properties.get(name);
+ }
+
+ /**
+ * Returns a list of this event's property names.
+ *
+ * @return A non-empty array with one element per property.
+ */
+ public final String[] getPropertyNames() {
+ String[] names = new String[properties.size()];
+ Enumeration keys = properties.keys();
+ for (int i = 0; keys.hasMoreElements(); i++) {
+ names[i] = (String) keys.nextElement();
+ }
+ return names;
+ }
+
+ /**
+ * Returns the topic of this event.
+ *
+ * @return The topic of this event.
+ */
+ public final String getTopic() {
+ return topic;
+ }
+
+ /**
+ * Tests this event's properties against the given filter.
+ *
+ * @param filter The filter to test.
+ *
+ * @return true If this event's properties match the filter, false
+ * otherwise.
+ */
+ public final boolean matches(Filter filter) {
+ return filter.matchCase(properties);
+ }
+
+ /**
+ * Compares this <code>Event</code> object to another object.
+ *
+ * <p>
+ * An event is considered to be <b>equal to </b> another
+ * event if the topic is equal and the properties are equal.
+ *
+ * @param object The <code>Event</code> object to be compared.
+ * @return <code>true</code> if <code>object</code> is a
+ * <code>Event</code> and is equal to this object;
+ * <code>false</code> otherwise.
+ */
+ public boolean equals(Object object) {
+ if (object == this) { // quicktest
+ return true;
+ }
+
+ if (!(object instanceof Event)) {
+ return false;
+ }
+
+ Event event = (Event) object;
+ return topic.equals(event.topic) && properties.equals(event.properties);
+ }
+
+ /**
+ * Returns a hash code value for the object.
+ *
+ * @return An integer which is a hash code value for this object.
+ */
+ public int hashCode() {
+ return topic.hashCode() ^ properties.hashCode();
+ }
+
+ /**
+ * Returns the string representation of this event.
+ *
+ * @return The string representation of this event.
+ */
+ public String toString() {
+ return getClass().getName() + " [topic=" + topic + "]"; //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private static final String SEPARATOR = "/"; //$NON-NLS-1$
+
+ /**
+ * Called by the constructor to validate the topic name.
+ *
+ * @throws IllegalArgumentException If the topic name is invalid.
+ */
+ private void validateTopicName() {
+ try {
+ StringTokenizer st = new StringTokenizer(topic, SEPARATOR, true);
+ validateToken(st.nextToken());
+
+ while (st.hasMoreTokens()) {
+ st.nextToken(); // consume delimiter
+ validateToken(st.nextToken());
+ }
+ }
+ catch (NoSuchElementException e) {
+ throw new IllegalArgumentException("invalid topic"); //$NON-NLS-1$
+ }
+ }
+
+ private static final String tokenAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-"; //$NON-NLS-1$
+
+ /**
+ * Validate a token.
+ *
+ * @throws IllegalArgumentException If the token is invalid.
+ */
+ private void validateToken(String token) {
+ int length = token.length();
+ if (length < 1) { // token must contain at least one character
+ throw new IllegalArgumentException("invalid topic"); //$NON-NLS-1$
+ }
+ for (int i = 0; i < length; i++) { // each character in the token must be from the token alphabet
+ if (tokenAlphabet.indexOf(token.charAt(i)) == -1) { //$NON-NLS-1$
+ throw new IllegalArgumentException("invalid topic"); //$NON-NLS-1$
+ }
+ }
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/event/EventAdmin.java b/deploymentadmin/src/main/java/org/osgi/service/event/EventAdmin.java
new file mode 100644
index 0000000..1683e7b
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/event/EventAdmin.java
@@ -0,0 +1,53 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.event/src/org/osgi/service/event/EventAdmin.java,v 1.6 2006/06/16 16:31:48 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.event;
+
+/**
+ * The Event Admin service. Bundles wishing to publish events must obtain the
+ * Event Admin service and call one of the event delivery methods.
+ *
+ * @version $Revision: 1.6 $
+ */
+public interface EventAdmin {
+ /**
+ * Initiate asynchronous delivery of an event. This method returns to
+ * the caller before delivery of the event is completed.
+ *
+ * @param event The event to send to all listeners which subscribe
+ * to the topic of the event.
+ *
+ * @throws SecurityException If the caller does not have
+ * <code>TopicPermission[topic,PUBLISH]</code> for the topic
+ * specified in the event.
+ */
+ void postEvent(Event event);
+
+ /**
+ * Initiate synchronous delivery of an event. This method does not
+ * return to the caller until delivery of the event is completed.
+ *
+ * @param event The event to send to all listeners which subscribe
+ * to the topic of the event.
+ *
+ * @throws SecurityException If the caller does not have
+ * <code>TopicPermission[topic,PUBLISH]</code> for the topic
+ * specified in the event.
+ */
+ void sendEvent(Event event);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/event/EventConstants.java b/deploymentadmin/src/main/java/org/osgi/service/event/EventConstants.java
new file mode 100644
index 0000000..e6768d0
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/event/EventConstants.java
@@ -0,0 +1,163 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.event/src/org/osgi/service/event/EventConstants.java,v 1.14 2006/07/12 21:06:18 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.event;
+
+import org.osgi.framework.Constants;
+
+/**
+ *
+ * Defines standard names for <code>EventHandler</code> properties.
+ *
+ * @version $Revision: 1.14 $
+ */
+public interface EventConstants {
+
+ /**
+ * Service registration property (named <code>event.topic</code>)
+ * specifying the <code>Event</code> topics of interest to a Event Handler
+ * service.
+ * <p>
+ * Event handlers SHOULD be registered with this property. The value of the
+ * property is an array of strings that describe the topics in which the
+ * handler is interested. An asterisk ('*') may be used as a trailing
+ * wildcard. Event Handlers which do not have a value for this property must
+ * not receive events. More precisely, the value of each entry in the array
+ * must conform to the following grammar:
+ *
+ * <pre>
+ * topic-description := '*' | topic ( '/*' )?
+ * topic := token ( '/' token )*
+ * </pre>
+ *
+ * @see Event
+ */
+ public static final String EVENT_TOPIC = "event.topics";
+
+ /**
+ * Service Registration property (named <code>event.filter</code>)
+ * specifying a filter to further select <code>Event</code> s of interest
+ * to a Event Handler service.
+ * <p>
+ * Event handlers MAY be registered with this property. The value of this
+ * property is a string containing an LDAP-style filter specification. Any
+ * of the event's properties may be used in the filter expression. Each
+ * event handler is notified for any event which belongs to the topics in
+ * which the handler has expressed an interest. If the event handler is also
+ * registered with this service property, then the properties of the event
+ * must also match the filter for the event to be delivered to the event
+ * handler.
+ * <p>
+ * If the filter syntax is invalid, then the Event Handler must be ignored
+ * and a warning should be logged.
+ *
+ * @see Event
+ * @see org.osgi.framework.Filter
+ */
+ public static final String EVENT_FILTER = "event.filter";
+
+ /**
+ * The Distinguished Name of the bundle relevant to the event.
+ */
+ public static final String BUNDLE_SIGNER = "bundle.signer";
+
+ /**
+ * The Bundle Symbolic Name of the bundle relevant to the event.
+ */
+ public static final String BUNDLE_SYMBOLICNAME = "bundle.symbolicName";
+
+ /**
+ * The Bundle id of the bundle relevant to the event.
+ *
+ * @since 1.1
+ */
+ public static final String BUNDLE_ID = "bundle.id";
+
+ /**
+ * The Bundle object of the bundle relevant to the event.
+ *
+ * @since 1.1
+ */
+ public static final String BUNDLE = "bundle";
+
+ /**
+ * The actual event object. Used when rebroadcasting an event that was sent
+ * via some other event mechanism.
+ */
+ public static final String EVENT = "event";
+
+ /**
+ * An exception or error.
+ */
+ public static final String EXCEPTION = "exception";
+
+ /**
+ * Must be equal to the name of the Exception class.
+ *
+ * @since 1.1
+ */
+ public static final String EXCEPTION_CLASS = "exception.class";
+
+ /**
+ * Must be equal to exception.getMessage()
+ */
+ public static final String EXCEPTION_MESSAGE = "exception.message";
+
+ /**
+ * A human-readable message that is usually not localized.
+ */
+ public static final String MESSAGE = "message";
+
+ /**
+ * A service
+ */
+
+ public static final String SERVICE = "service";
+
+ /**
+ * A service's id.
+ */
+ public static final String SERVICE_ID = Constants.SERVICE_ID;
+
+ /**
+ *
+ * A service's objectClass
+ */
+ public static final String SERVICE_OBJECTCLASS = "service.objectClass";
+
+ /**
+ *
+ * A service's persistent identity.
+ */
+ public static final String SERVICE_PID = Constants.SERVICE_PID;
+
+ /**
+ *
+ * The time when the event occurred, as reported by
+ * System.currentTimeMillis()
+ */
+ public static final String TIMESTAMP = "timestamp";
+
+ /**
+ * This constant was released with an incorrect spelling. It has been
+ * replaced by {@link #EXCEPTION_CLASS}
+ *
+ * @deprecated As of 1.1, replaced by EXCEPTION_CLASS
+ */
+ public static final String EXECPTION_CLASS = "exception.class";
+}
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/event/EventHandler.java b/deploymentadmin/src/main/java/org/osgi/service/event/EventHandler.java
new file mode 100644
index 0000000..8073796
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/event/EventHandler.java
@@ -0,0 +1,67 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.event/src/org/osgi/service/event/EventHandler.java,v 1.10 2006/07/11 16:43:59 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.event;
+
+/**
+ * Listener for Events.
+ *
+ * <p>
+ * <code>EventHandler</code> objects are registered with the Framework service
+ * registry and are notified with an <code>Event</code> object when an
+ * event is sent or posted.
+ * <p>
+ * <code>EventHandler</code> objects can inspect the received
+ * <code>Event</code> object to determine its topic and properties.
+ *
+ * <p>
+ * <code>EventHandler</code> objects must be registered with a service
+ * property {@link EventConstants#EVENT_TOPIC} whose value is the list of
+ * topics in which the event handler is interesed.
+ * <p>
+ * For example:
+ *
+ * <pre>
+ * String[] topics = new String[] {EventConstants.EVENT_TOPIC, "com/isv/*"};
+ * Hashtable ht = new Hashtable();
+ * ht.put(EVENT_TOPIC, topics);
+ * context.registerService(EventHandler.class.getName(), this, ht);
+ * </pre>
+ * Event Handler services can also be registered with an {@link EventConstants#EVENT_FILTER}
+ * service propery to further filter the events. If the syntax of this filter is invalid,
+ * then the Event Handler must be ignored by the Event Admin service. The Event Admin
+ * service should log a warning.
+ * <p>
+ * Security Considerations. Bundles wishing to monitor <code>Event</code>
+ * objects will require <code>ServicePermission[EventHandler,REGISTER]</code>
+ * to register an <code>EventHandler</code> service. The bundle must also have
+ * <code>TopicPermission[topic,SUBSCRIBE]</code> for the topic specified in the
+ * event in order to receive the event.
+ *
+ * @see Event
+ *
+ * @version $Revision: 1.10 $
+ */
+public interface EventHandler {
+ /**
+ * Called by the {@link EventAdmin} service to notify the listener of an event.
+ *
+ * @param event The event that occurred.
+ */
+ void handleEvent(Event event);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/event/TopicPermission.java b/deploymentadmin/src/main/java/org/osgi/service/event/TopicPermission.java
new file mode 100644
index 0000000..89591b6
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/event/TopicPermission.java
@@ -0,0 +1,506 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.event/src/org/osgi/service/event/TopicPermission.java,v 1.11 2006/06/16 16:31:48 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.event;
+
+import java.io.IOException;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * A bundle's authority to publish or subscribe to event on a topic.
+ *
+ * <p>
+ * A topic is a slash-separated string that defines a topic.
+ * <p>
+ * For example:
+ *
+ * <pre>
+ * org/osgi/service/foo/FooEvent/ACTION
+ * </pre>
+ *
+ * <p>
+ * <code>TopicPermission</code> has two actions: <code>publish</code> and
+ * <code>subscribe</code>.
+ *
+ * @version $Revision: 1.11 $
+ */
+public final class TopicPermission extends Permission {
+ static final long serialVersionUID = -5855563886961618300L;
+ /**
+ * The action string <code>publish</code>.
+ */
+ public final static String PUBLISH = "publish"; //$NON-NLS-1$
+ /**
+ * The action string <code>subscribe</code>.
+ */
+ public final static String SUBSCRIBE = "subscribe"; //$NON-NLS-1$
+ private final static int ACTION_PUBLISH = 0x00000001;
+ private final static int ACTION_SUBSCRIBE = 0x00000002;
+ private final static int ACTION_ALL = ACTION_PUBLISH
+ | ACTION_SUBSCRIBE;
+ private final static int ACTION_NONE = 0;
+ /**
+ * The actions mask.
+ */
+ private transient int action_mask = ACTION_NONE;
+
+ /**
+ * prefix if the name is wildcarded.
+ */
+ private transient String prefix;
+
+ /**
+ * The actions in canonical form.
+ *
+ * @serial
+ */
+ private String actions = null;
+
+ /**
+ * Defines the authority to publich and/or subscribe to a topic within the
+ * EventAdmin service.
+ * <p>
+ * The name is specified as a slash-separated string. Wildcards may be used.
+ * For example:
+ *
+ * <pre>
+ * org/osgi/service/fooFooEvent/ACTION
+ * com/isv/*
+ * *
+ * </pre>
+ *
+ * <p>
+ * A bundle that needs to publish events on a topic must have the
+ * appropriate <code>TopicPermission</code> for that topic; similarly, a
+ * bundle that needs to subscribe to events on a topic must have the
+ * appropriate <code>TopicPermssion</code> for that topic.
+ * <p>
+ *
+ * @param name Topic name.
+ * @param actions <code>publish</code>,<code>subscribe</code>
+ * (canonical order).
+ */
+ public TopicPermission(String name, String actions) {
+ this(name, getMask(actions));
+ }
+
+ /**
+ * Package private constructor used by TopicPermissionCollection.
+ *
+ * @param name class name
+ * @param mask action mask
+ */
+ TopicPermission(String name, int mask) {
+ super(name);
+ init(name, mask);
+ }
+
+ /**
+ * Called by constructors and when deserialized.
+ *
+ * @param name topic name
+ * @param mask action mask
+ */
+ private void init(String name, int mask) {
+ if ((name == null) || name.length() == 0) {
+ throw new IllegalArgumentException("invalid name"); //$NON-NLS-1$
+ }
+
+ if (name.equals("*")) {
+ prefix = "";
+ }
+ else
+ if (name.endsWith("/*")) {
+ prefix = name.substring(0, name.length() - 1);
+ }
+ else {
+ prefix = null;
+ }
+
+ if ((mask == ACTION_NONE) || ((mask & ACTION_ALL) != mask)) {
+ throw new IllegalArgumentException("invalid action string"); //$NON-NLS-1$
+ }
+ action_mask = mask;
+ }
+
+ /**
+ * Parse action string into action mask.
+ *
+ * @param actions Action string.
+ * @return action mask.
+ */
+ private static int getMask(String actions) {
+ boolean seencomma = false;
+ int mask = ACTION_NONE;
+ if (actions == null) {
+ return mask;
+ }
+ char[] a = actions.toCharArray();
+ int i = a.length - 1;
+ if (i < 0)
+ return mask;
+ while (i != -1) {
+ char c;
+ // skip whitespace
+ while ((i != -1)
+ && ((c = a[i]) == ' ' || c == '\r' || c == '\n'
+ || c == '\f' || c == '\t'))
+ i--;
+ // check for the known strings
+ int matchlen;
+ if (i >= 8 && (a[i - 8] == 's' || a[i - 8] == 'S')
+ && (a[i - 7] == 'u' || a[i - 7] == 'U')
+ && (a[i - 6] == 'b' || a[i - 6] == 'B')
+ && (a[i - 5] == 's' || a[i - 5] == 'S')
+ && (a[i - 4] == 'c' || a[i - 4] == 'C')
+ && (a[i - 3] == 'r' || a[i - 3] == 'R')
+ && (a[i - 2] == 'i' || a[i - 2] == 'I')
+ && (a[i - 1] == 'b' || a[i - 1] == 'B')
+ && (a[i] == 'e' || a[i] == 'E')) {
+ matchlen = 9;
+ mask |= ACTION_SUBSCRIBE;
+ }
+ else
+ if (i >= 6 && (a[i - 6] == 'p' || a[i - 6] == 'P')
+ && (a[i - 5] == 'u' || a[i - 5] == 'U')
+ && (a[i - 4] == 'b' || a[i - 4] == 'B')
+ && (a[i - 3] == 'l' || a[i - 3] == 'L')
+ && (a[i - 2] == 'i' || a[i - 2] == 'I')
+ && (a[i - 1] == 's' || a[i - 1] == 'S')
+ && (a[i] == 'h' || a[i] == 'H')) {
+ matchlen = 7;
+ mask |= ACTION_PUBLISH;
+ }
+ else {
+ // parse error
+ throw new IllegalArgumentException("invalid permission: " //$NON-NLS-1$
+ + actions);
+ }
+ // make sure we didn't just match the tail of a word
+ // like "ackbarfpublish". Also, skip to the comma.
+ seencomma = false;
+ while (i >= matchlen && !seencomma) {
+ switch (a[i - matchlen]) {
+ case ',' :
+ seencomma = true;
+ /* FALLTHROUGH */
+ case ' ' :
+ case '\r' :
+ case '\n' :
+ case '\f' :
+ case '\t' :
+ break;
+ default :
+ throw new IllegalArgumentException(
+ "invalid permission: " + actions); //$NON-NLS-1$
+ }
+ i--;
+ }
+ // point i at the location of the comma minus one (or -1).
+ i -= matchlen;
+ }
+ if (seencomma) {
+ throw new IllegalArgumentException("invalid permission: " + actions); //$NON-NLS-1$
+ }
+ return mask;
+ }
+
+ /**
+ * Determines if the specified permission is implied by this object.
+ *
+ * <p>
+ * This method checks that the topic name of the target is implied by the
+ * topic name of this object. The list of <code>TopicPermission</code>
+ * actions must either match or allow for the list of the target object to
+ * imply the target <code>TopicPermission</code> action.
+ *
+ * <pre>
+ * x/y/*,"publish" -> x/y/z,"publish" is true
+ * *,"subscribe" -> x/y,"subscribe" is true
+ * *,"publish" -> x/y,"subscribe" is false
+ * x/y,"publish" -> x/y/z,"publish" is false
+ * </pre>
+ *
+ * @param p The target permission to interrogate.
+ * @return <code>true</code> if the specified <code>TopicPermission</code>
+ * action is implied by this object; <code>false</code> otherwise.
+ */
+ public boolean implies(Permission p) {
+ if (p instanceof TopicPermission) {
+ TopicPermission target = (TopicPermission) p;
+ if ((action_mask & target.action_mask) == target.action_mask) {
+ if (prefix != null) {
+ return target.getName().startsWith(prefix);
+ }
+
+ return target.getName().equals(getName());
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the canonical string representation of the
+ * <code>TopicPermission</code> actions.
+ *
+ * <p>
+ * Always returns present <code>TopicPermission</code> actions in the
+ * following order: <code>publish</code>,<code>subscribe</code>.
+ *
+ * @return Canonical string representation of the
+ * <code>TopicPermission</code> actions.
+ */
+ public String getActions() {
+ if (actions == null) {
+ StringBuffer sb = new StringBuffer();
+ boolean comma = false;
+ if ((action_mask & ACTION_PUBLISH) == ACTION_PUBLISH) {
+ sb.append(PUBLISH);
+ comma = true;
+ }
+ if ((action_mask & ACTION_SUBSCRIBE) == ACTION_SUBSCRIBE) {
+ if (comma)
+ sb.append(',');
+ sb.append(SUBSCRIBE);
+ }
+ actions = sb.toString();
+ }
+ return actions;
+ }
+
+ /**
+ * Returns a new <code>PermissionCollection</code> object suitable for
+ * storing <code>TopicPermission</code> objects.
+ *
+ * @return A new <code>PermissionCollection</code> object.
+ */
+ public PermissionCollection newPermissionCollection() {
+ return new TopicPermissionCollection();
+ }
+
+ /**
+ * Determines the equality of two <code>TopicPermission</code> objects.
+ *
+ * This method checks that specified <code>TopicPermission</code> has the same topic name and
+ * actions as this
+ * <code>TopicPermission</code> object.
+ *
+ * @param obj The object to test for equality with this
+ * <code>TopicPermission</code> object.
+ * @return <code>true</code> if <code>obj</code> is a
+ * <code>TopicPermission</code>, and has the same topic name and
+ * actions as this <code>TopicPermission</code> object;
+ * <code>false</code> otherwise.
+ */
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof TopicPermission)) {
+ return false;
+ }
+ TopicPermission p = (TopicPermission) obj;
+ return (action_mask == p.action_mask) && getName().equals(p.getName());
+ }
+
+ /**
+ * Returns the hash code value for this object.
+ *
+ * @return A hash code value for this object.
+ */
+ public int hashCode() {
+ return getName().hashCode() ^ getActions().hashCode();
+ }
+
+ /**
+ * Returns the current action mask.
+ * <p>
+ * Used by the TopicPermissionCollection class.
+ *
+ * @return Current action mask.
+ */
+ int getMask() {
+ return action_mask;
+ }
+
+ /**
+ * WriteObject is called to save the state of this permission object to a
+ * stream. The actions are serialized, and the superclass takes care of the
+ * name.
+ */
+ private synchronized void writeObject(java.io.ObjectOutputStream s)
+ throws IOException {
+ // Write out the actions. The superclass takes care of the name
+ // call getActions to make sure actions field is initialized
+ if (actions == null)
+ getActions();
+ s.defaultWriteObject();
+ }
+
+ /**
+ * readObject is called to restore the state of this permission from a
+ * stream.
+ */
+ private synchronized void readObject(java.io.ObjectInputStream s)
+ throws IOException, ClassNotFoundException {
+ // Read in the action, then initialize the rest
+ s.defaultReadObject();
+ init(getName(), getMask(actions));
+ }
+}
+
+/**
+ * Stores a set of <code>TopicPermission</code> permissions.
+ *
+ * @see java.security.Permission
+ * @see java.security.Permissions
+ * @see java.security.PermissionCollection
+ */
+final class TopicPermissionCollection extends PermissionCollection {
+ static final long serialVersionUID = -614647783533924048L;
+ /**
+ * Table of permissions.
+ *
+ * @serial
+ */
+ private Hashtable permissions;
+ /**
+ * Boolean saying if "*" is in the collection.
+ *
+ * @serial
+ */
+ private boolean all_allowed;
+
+ /**
+ * Create an empty TopicPermissions object.
+ *
+ */
+ public TopicPermissionCollection() {
+ permissions = new Hashtable();
+ all_allowed = false;
+ }
+
+ /**
+ * Adds a permission to the <code>TopicPermission</code> objects. The key
+ * for the hash is the name.
+ *
+ * @param permission The <code>TopicPermission</code> object to add.
+ *
+ * @throws IllegalArgumentException If the permission is not a
+ * <code>TopicPermission</code> instance.
+ *
+ * @throws SecurityException If this
+ * <code>TopicPermissionCollection</code> object has been
+ * marked read-only.
+ */
+ public void add(Permission permission) {
+ if (!(permission instanceof TopicPermission))
+ throw new IllegalArgumentException("invalid permission: " //$NON-NLS-1$
+ + permission);
+ if (isReadOnly())
+ throw new SecurityException("attempt to add a Permission to a " //$NON-NLS-1$
+ + "readonly PermissionCollection"); //$NON-NLS-1$
+ TopicPermission pp = (TopicPermission) permission;
+ String name = pp.getName();
+ TopicPermission existing = (TopicPermission) permissions.get(name);
+ if (existing != null) {
+ int oldMask = existing.getMask();
+ int newMask = pp.getMask();
+ if (oldMask != newMask) {
+ permissions.put(name, new TopicPermission(name, oldMask
+ | newMask));
+ }
+ }
+ else {
+ permissions.put(name, permission);
+ }
+ if (!all_allowed) {
+ if (name.equals("*")) //$NON-NLS-1$
+ all_allowed = true;
+ }
+ }
+
+ /**
+ * Determines if the specified permissions implies the permissions expressed
+ * in <code>permission</code>.
+ *
+ * @param permission The Permission object to compare with this
+ * <code>TopicPermission</code> object.
+ *
+ * @return <code>true</code> if <code>permission</code> is a proper
+ * subset of a permission in the set; <code>false</code>
+ * otherwise.
+ */
+ public boolean implies(Permission permission) {
+ if (!(permission instanceof TopicPermission))
+ return false;
+ TopicPermission pp = (TopicPermission) permission;
+ TopicPermission x;
+ int desired = pp.getMask();
+ int effective = 0;
+ // short circuit if the "*" Permission was added
+ if (all_allowed) {
+ x = (TopicPermission) permissions.get("*"); //$NON-NLS-1$
+ if (x != null) {
+ effective |= x.getMask();
+ if ((effective & desired) == desired)
+ return true;
+ }
+ }
+ // strategy:
+ // Check for full match first. Then work our way up the
+ // name looking for matches on a/b/*
+ String name = pp.getName();
+ x = (TopicPermission) permissions.get(name);
+ if (x != null) {
+ // we have a direct hit!
+ effective |= x.getMask();
+ if ((effective & desired) == desired)
+ return true;
+ }
+ // work our way up the tree...
+ int last, offset;
+ offset = name.length() - 1;
+ while ((last = name.lastIndexOf("/", offset)) != -1) { //$NON-NLS-1$
+ name = name.substring(0, last + 1) + "*"; //$NON-NLS-1$
+ x = (TopicPermission) permissions.get(name);
+ if (x != null) {
+ effective |= x.getMask();
+ if ((effective & desired) == desired)
+ return true;
+ }
+ offset = last - 1;
+ }
+ // we don't have to check for "*" as it was already checked
+ // at the top (all_allowed), so we just return false
+ return false;
+ }
+
+ /**
+ * Returns an enumeration of all <code>TopicPermission</code> objects in
+ * the container.
+ *
+ * @return Enumeration of all <code>TopicPermission</code> objects.
+ */
+ public Enumeration elements() {
+ return permissions.elements();
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/event/package.html b/deploymentadmin/src/main/java/org/osgi/service/event/package.html
new file mode 100644
index 0000000..ac36fcf
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/event/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.service.event/src/org/osgi/service/event/package.html,v 1.8 2006/07/12 21:07:18 hargrave Exp $ -->
+<BODY>
+<p>Event Admin Package Version 1.1.
+<p>Bundles wishing to use this package must list the package
+in the <TT>Import-Package</TT> header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.service.event; version=1.1
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/service/event/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/event/packageinfo
new file mode 100644
index 0000000..3987f9c
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/event/packageinfo
@@ -0,0 +1 @@
+version 1.1
diff --git a/deploymentadmin/src/main/java/org/osgi/service/io/ConnectionFactory.java b/deploymentadmin/src/main/java/org/osgi/service/io/ConnectionFactory.java
new file mode 100644
index 0000000..03f812e
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/io/ConnectionFactory.java
@@ -0,0 +1,62 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.io/src/org/osgi/service/io/ConnectionFactory.java,v 1.9 2006/07/12 21:22:12 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2002, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.io;
+
+import java.io.IOException;
+
+import javax.microedition.io.Connection;
+
+/**
+ * A Connection Factory service is called by the implementation of the Connector
+ * Service to create <code>javax.microedition.io.Connection</code> objects which
+ * implement the scheme named by <code>IO_SCHEME</code>.
+ *
+ * When a <code>ConnectorService.open</code> method is called, the implementation
+ * of the Connector Service will examine the specified name for a scheme. The
+ * Connector Service will then look for a Connection Factory service which is
+ * registered with the service property <code>IO_SCHEME</code> which matches the
+ * scheme. The {@link #createConnection} method of the selected Connection
+ * Factory will then be called to create the actual <code>Connection</code>
+ * object.
+ *
+ * @version $Revision: 1.9 $
+ */
+public interface ConnectionFactory {
+ /**
+ * Service property containing the scheme(s) for which this Connection
+ * Factory can create <code>Connection</code> objects. This property is of
+ * type <code>String[]</code>.
+ */
+ public static final String IO_SCHEME = "io.scheme";
+
+ /**
+ * Create a new <code>Connection</code> object for the specified URI.
+ *
+ * @param name The full URI passed to the <code>ConnectorService.open</code>
+ * method
+ * @param mode The mode parameter passed to the
+ * <code>ConnectorService.open</code> method
+ * @param timeouts The timeouts parameter passed to the
+ * <code>ConnectorService.open</code> method
+ * @return A new <code>javax.microedition.io.Connection</code> object.
+ * @throws IOException If a <code>javax.microedition.io.Connection</code>
+ * object can not not be created.
+ */
+ public Connection createConnection(String name, int mode, boolean timeouts)
+ throws IOException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/io/ConnectorService.java b/deploymentadmin/src/main/java/org/osgi/service/io/ConnectorService.java
new file mode 100644
index 0000000..45d1f87
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/io/ConnectorService.java
@@ -0,0 +1,167 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.io/src/org/osgi/service/io/ConnectorService.java,v 1.9 2006/07/12 21:22:12 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2002, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.io;
+
+import java.io.*;
+
+import javax.microedition.io.Connection;
+import javax.microedition.io.Connector;
+
+/**
+ * The Connector Service should be called to create and open
+ * <code>javax.microedition.io.Connection</code> objects.
+ *
+ * When an <code>open*</code> method is called, the implementation of the
+ * Connector Service will examine the specified name for a scheme. The Connector
+ * Service will then look for a Connection Factory service which is registered
+ * with the service property <code>IO_SCHEME</code> which matches the scheme. The
+ * <code>createConnection</code> method of the selected Connection Factory will
+ * then be called to create the actual <code>Connection</code> object.
+ *
+ * <p>
+ * If more than one Connection Factory service is registered for a particular
+ * scheme, the service with the highest ranking (as specified in its
+ * <code>service.ranking</code> property) is called. If there is a tie in ranking,
+ * the service with the lowest service ID (as specified in its
+ * <code>service.id</code> property), that is the service that was registered
+ * first, is called. This is the same algorithm used by
+ * <code>BundleContext.getServiceReference</code>.
+ *
+ * @version $Revision: 1.9 $
+ */
+public interface ConnectorService {
+ /**
+ * Read access mode.
+ *
+ * @see "javax.microedition.io.Connector.READ"
+ */
+ public static final int READ = Connector.READ;
+ /**
+ * Write access mode.
+ *
+ * @see "javax.microedition.io.Connector.WRITE"
+ */
+ public static final int WRITE = Connector.WRITE;
+ /**
+ * Read/Write access mode.
+ *
+ * @see "javax.microedition.io.Connector.READ_WRITE"
+ */
+ public static final int READ_WRITE = Connector.READ_WRITE;
+
+ /**
+ * Create and open a <code>Connection</code> object for the specified name.
+ *
+ * @param name The URI for the connection.
+ * @return A new <code>javax.microedition.io.Connection</code> object.
+ * @throws IllegalArgumentException If a parameter is invalid.
+ * @throws javax.microedition.io.ConnectionNotFoundException If the
+ * connection cannot be found.
+ * @throws IOException If some other kind of I/O error occurs.
+ * @see "javax.microedition.io.Connector.open(String name)"
+ */
+ public Connection open(String name) throws IOException;
+
+ /**
+ * Create and open a <code>Connection</code> object for the specified name and
+ * access mode.
+ *
+ * @param name The URI for the connection.
+ * @param mode The access mode.
+ * @return A new <code>javax.microedition.io.Connection</code> object.
+ * @throws IllegalArgumentException If a parameter is invalid.
+ * @throws javax.microedition.io.ConnectionNotFoundException If the
+ * connection cannot be found.
+ * @throws IOException If some other kind of I/O error occurs.
+ * @see "javax.microedition.io.Connector.open(String name, int mode)"
+ */
+ public Connection open(String name, int mode) throws IOException;
+
+ /**
+ * Create and open a <code>Connection</code> object for the specified name,
+ * access mode and timeouts.
+ *
+ * @param name The URI for the connection.
+ * @param mode The access mode.
+ * @param timeouts A flag to indicate that the caller wants timeout
+ * exceptions.
+ * @return A new <code>javax.microedition.io.Connection</code> object.
+ * @throws IllegalArgumentException If a parameter is invalid.
+ * @throws javax.microedition.io.ConnectionNotFoundException If the
+ * connection cannot be found.
+ * @throws IOException If some other kind of I/O error occurs.
+ * @see "<code>javax.microedition.io.Connector.open</code>"
+ */
+ public Connection open(String name, int mode, boolean timeouts)
+ throws IOException;
+
+ /**
+ * Create and open an <code>InputStream</code> object for the specified name.
+ *
+ * @param name The URI for the connection.
+ * @return An <code>InputStream</code> object.
+ * @throws IllegalArgumentException If a parameter is invalid.
+ * @throws javax.microedition.io.ConnectionNotFoundException If the
+ * connection cannot be found.
+ * @throws IOException If some other kind of I/O error occurs.
+ * @see "javax.microedition.io.Connector.openInputStream(String name)"
+ */
+ public InputStream openInputStream(String name) throws IOException;
+
+ /**
+ * Create and open a <code>DataInputStream</code> object for the specified
+ * name.
+ *
+ * @param name The URI for the connection.
+ * @return A <code>DataInputStream</code> object.
+ * @throws IllegalArgumentException If a parameter is invalid.
+ * @throws javax.microedition.io.ConnectionNotFoundException If the
+ * connection cannot be found.
+ * @throws IOException If some other kind of I/O error occurs.
+ * @see "javax.microedition.io.Connector.openDataInputStream(String name)"
+ */
+ public DataInputStream openDataInputStream(String name) throws IOException;
+
+ /**
+ * Create and open an <code>OutputStream</code> object for the specified name.
+ *
+ * @param name The URI for the connection.
+ * @return An <code>OutputStream</code> object.
+ * @throws IllegalArgumentException If a parameter is invalid.
+ * @throws javax.microedition.io.ConnectionNotFoundException If the
+ * connection cannot be found.
+ * @throws IOException If some other kind of I/O error occurs.
+ * @see "javax.microedition.io.Connector.openOutputStream(String name)"
+ */
+ public OutputStream openOutputStream(String name) throws IOException;
+
+ /**
+ * Create and open a <code>DataOutputStream</code> object for the specified
+ * name.
+ *
+ * @param name The URI for the connection.
+ * @return A <code>DataOutputStream</code> object.
+ * @throws IllegalArgumentException If a parameter is invalid.
+ * @throws javax.microedition.io.ConnectionNotFoundException If the
+ * connection cannot be found.
+ * @throws IOException If some other kind of I/O error occurs.
+ * @see "javax.microedition.io.Connector.openDataOutputStream(String name)"
+ */
+ public DataOutputStream openDataOutputStream(String name)
+ throws IOException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/io/package.html b/deploymentadmin/src/main/java/org/osgi/service/io/package.html
new file mode 100644
index 0000000..4d4adc4
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/io/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.service.io/src/org/osgi/service/io/package.html,v 1.3 2006/07/12 21:07:06 hargrave Exp $ -->
+<BODY>
+<p>IO Connector Package Version 1.0.
+<p>Bundles wishing to use this package must list the package
+in the <TT>Import-Package</TT> header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.service.io; version=1.0, javax.microedition.io
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/service/io/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/io/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/io/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/deploymentadmin/src/main/java/org/osgi/service/log/LogEntry.java b/deploymentadmin/src/main/java/org/osgi/service/log/LogEntry.java
new file mode 100644
index 0000000..12e6c8d
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/log/LogEntry.java
@@ -0,0 +1,109 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.log/src/org/osgi/service/log/LogEntry.java,v 1.9 2006/06/16 16:31:49 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.log;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Provides methods to access the information contained in an individual Log
+ * Service log entry.
+ *
+ * <p>
+ * A <code>LogEntry</code> object may be acquired from the
+ * <code>LogReaderService.getLog</code> method or by registering a
+ * <code>LogListener</code> object.
+ *
+ * @version $Revision: 1.9 $
+ * @see LogReaderService#getLog
+ * @see LogListener
+ */
+public interface LogEntry {
+ /**
+ * Returns the bundle that created this <code>LogEntry</code> object.
+ *
+ * @return The bundle that created this <code>LogEntry</code> object;
+ * <code>null</code> if no bundle is associated with this
+ * <code>LogEntry</code> object.
+ */
+ public Bundle getBundle();
+
+ /**
+ * Returns the <code>ServiceReference</code> object for the service associated
+ * with this <code>LogEntry</code> object.
+ *
+ * @return <code>ServiceReference</code> object for the service associated
+ * with this <code>LogEntry</code> object; <code>null</code> if no
+ * <code>ServiceReference</code> object was provided.
+ */
+ public ServiceReference getServiceReference();
+
+ /**
+ * Returns the severity level of this <code>LogEntry</code> object.
+ *
+ * <p>
+ * This is one of the severity levels defined by the <code>LogService</code>
+ * interface.
+ *
+ * @return Severity level of this <code>LogEntry</code> object.
+ *
+ * @see LogService#LOG_ERROR
+ * @see LogService#LOG_WARNING
+ * @see LogService#LOG_INFO
+ * @see LogService#LOG_DEBUG
+ */
+ public int getLevel();
+
+ /**
+ * Returns the human readable message associated with this <code>LogEntry</code>
+ * object.
+ *
+ * @return <code>String</code> containing the message associated with this
+ * <code>LogEntry</code> object.
+ */
+ public String getMessage();
+
+ /**
+ * Returns the exception object associated with this <code>LogEntry</code>
+ * object.
+ *
+ * <p>
+ * In some implementations, the returned exception may not be the original
+ * exception. To avoid references to a bundle defined exception class, thus
+ * preventing an uninstalled bundle from being garbage collected, the Log
+ * Service may return an exception object of an implementation defined
+ * Throwable subclass. The returned object will attempt to provide as much
+ * information as possible from the original exception object such as the
+ * message and stack trace.
+ *
+ * @return <code>Throwable</code> object of the exception associated with this
+ * <code>LogEntry</code>;<code>null</code> if no exception is
+ * associated with this <code>LogEntry</code> object.
+ */
+ public Throwable getException();
+
+ /**
+ * Returns the value of <code>currentTimeMillis()</code> at the time this
+ * <code>LogEntry</code> object was created.
+ *
+ * @return The system time in milliseconds when this <code>LogEntry</code>
+ * object was created.
+ * @see "System.currentTimeMillis()"
+ */
+ public long getTime();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/log/LogListener.java b/deploymentadmin/src/main/java/org/osgi/service/log/LogListener.java
new file mode 100644
index 0000000..3731c28
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/log/LogListener.java
@@ -0,0 +1,51 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.log/src/org/osgi/service/log/LogListener.java,v 1.9 2006/06/16 16:31:49 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.log;
+
+import java.util.EventListener;
+
+/**
+ * Subscribes to <code>LogEntry</code> objects from the <code>LogReaderService</code>.
+ *
+ * <p>
+ * A <code>LogListener</code> object may be registered with the Log Reader Service
+ * using the <code>LogReaderService.addLogListener</code> method. After the
+ * listener is registered, the <code>logged</code> method will be called for each
+ * <code>LogEntry</code> object created. The <code>LogListener</code> object may be
+ * unregistered by calling the <code>LogReaderService.removeLogListener</code>
+ * method.
+ *
+ * @version $Revision: 1.9 $
+ * @see LogReaderService
+ * @see LogEntry
+ * @see LogReaderService#addLogListener(LogListener)
+ * @see LogReaderService#removeLogListener(LogListener)
+ */
+public interface LogListener extends EventListener {
+ /**
+ * Listener method called for each LogEntry object created.
+ *
+ * <p>
+ * As with all event listeners, this method should return to its caller as
+ * soon as possible.
+ *
+ * @param entry A <code>LogEntry</code> object containing log information.
+ * @see LogEntry
+ */
+ public void logged(LogEntry entry);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/log/LogReaderService.java b/deploymentadmin/src/main/java/org/osgi/service/log/LogReaderService.java
new file mode 100644
index 0000000..4176ebc
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/log/LogReaderService.java
@@ -0,0 +1,98 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.log/src/org/osgi/service/log/LogReaderService.java,v 1.10 2006/06/16 16:31:49 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.log;
+
+import java.util.Enumeration;
+
+/**
+ * Provides methods to retrieve <code>LogEntry</code> objects from the log.
+ * <p>
+ * There are two ways to retrieve <code>LogEntry</code> objects:
+ * <ul>
+ * <li>The primary way to retrieve <code>LogEntry</code> objects is to register a
+ * <code>LogListener</code> object whose <code>LogListener.logged</code> method will
+ * be called for each entry added to the log.
+ * <li>To retrieve past <code>LogEntry</code> objects, the <code>getLog</code>
+ * method can be called which will return an <code>Enumeration</code> of all
+ * <code>LogEntry</code> objects in the log.
+ *
+ * @version $Revision: 1.10 $
+ * @see LogEntry
+ * @see LogListener
+ * @see LogListener#logged(LogEntry)
+ */
+public interface LogReaderService {
+ /**
+ * Subscribes to <code>LogEntry</code> objects.
+ *
+ * <p>
+ * This method registers a <code>LogListener</code> object with the Log Reader
+ * Service. The <code>LogListener.logged(LogEntry)</code> method will be
+ * called for each <code>LogEntry</code> object placed into the log.
+ *
+ * <p>
+ * When a bundle which registers a <code>LogListener</code> object is stopped
+ * or otherwise releases the Log Reader Service, the Log Reader Service must
+ * remove all of the bundle's listeners.
+ *
+ * <p>
+ * If this Log Reader Service's list of listeners already contains a
+ * listener <code>l</code> such that <code>(l==listener)</code>, this method
+ * does nothing.
+ *
+ * @param listener A <code>LogListener</code> object to register; the
+ * <code>LogListener</code> object is used to receive <code>LogEntry</code>
+ * objects.
+ * @see LogListener
+ * @see LogEntry
+ * @see LogListener#logged(LogEntry)
+ */
+ public void addLogListener(LogListener listener);
+
+ /**
+ * Unsubscribes to <code>LogEntry</code> objects.
+ *
+ * <p>
+ * This method unregisters a <code>LogListener</code> object from the Log
+ * Reader Service.
+ *
+ * <p>
+ * If <code>listener</code> is not contained in this Log Reader Service's list
+ * of listeners, this method does nothing.
+ *
+ * @param listener A <code>LogListener</code> object to unregister.
+ * @see LogListener
+ */
+ public void removeLogListener(LogListener listener);
+
+ /**
+ * Returns an <code>Enumeration</code> of all <code>LogEntry</code> objects in
+ * the log.
+ *
+ * <p>
+ * Each element of the enumeration is a <code>LogEntry</code> object, ordered
+ * with the most recent entry first. Whether the enumeration is of all
+ * <code>LogEntry</code> objects since the Log Service was started or some
+ * recent past is implementation-specific. Also implementation-specific is
+ * whether informational and debug <code>LogEntry</code> objects are included
+ * in the enumeration.
+ * @return An <code>Enumeration</code> of all <code>LogEntry</code> objects in
+ * the log.
+ */
+ public Enumeration getLog();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/log/LogService.java b/deploymentadmin/src/main/java/org/osgi/service/log/LogService.java
new file mode 100644
index 0000000..4858c58
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/log/LogService.java
@@ -0,0 +1,156 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.log/src/org/osgi/service/log/LogService.java,v 1.9 2006/06/16 16:31:49 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.log;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Provides methods for bundles to write messages to the log.
+ *
+ * <p>
+ * <code>LogService</code> methods are provided to log messages; optionally with a
+ * <code>ServiceReference</code> object or an exception.
+ *
+ * <p>
+ * Bundles must log messages in the OSGi environment with a severity level
+ * according to the following hierarchy:
+ * <ol>
+ * <li>{@link #LOG_ERROR}
+ * <li>{@link #LOG_WARNING}
+ * <li>{@link #LOG_INFO}
+ * <li>{@link #LOG_DEBUG}
+ * </ol>
+ *
+ * @version $Revision: 1.9 $
+ */
+public interface LogService {
+ /**
+ * An error message (Value 1).
+ *
+ * <p>
+ * This log entry indicates the bundle or service may not be functional.
+ */
+ public static final int LOG_ERROR = 1;
+ /**
+ * A warning message (Value 2).
+ *
+ * <p>
+ * This log entry indicates a bundle or service is still functioning but may
+ * experience problems in the future because of the warning condition.
+ */
+ public static final int LOG_WARNING = 2;
+ /**
+ * An informational message (Value 3).
+ *
+ * <p>
+ * This log entry may be the result of any change in the bundle or service
+ * and does not indicate a problem.
+ */
+ public static final int LOG_INFO = 3;
+ /**
+ * A debugging message (Value 4).
+ *
+ * <p>
+ * This log entry is used for problem determination and may be irrelevant to
+ * anyone but the bundle developer.
+ */
+ public static final int LOG_DEBUG = 4;
+
+ /**
+ * Logs a message.
+ *
+ * <p>
+ * The <code>ServiceReference</code> field and the <code>Throwable</code> field
+ * of the <code>LogEntry</code> object will be set to <code>null</code>.
+ *
+ * @param level The severity of the message. This should be one of the
+ * defined log levels but may be any integer that is interpreted in a
+ * user defined way.
+ * @param message Human readable string describing the condition or
+ * <code>null</code>.
+ * @see #LOG_ERROR
+ * @see #LOG_WARNING
+ * @see #LOG_INFO
+ * @see #LOG_DEBUG
+ */
+ public void log(int level, String message);
+
+ /**
+ * Logs a message with an exception.
+ *
+ * <p>
+ * The <code>ServiceReference</code> field of the <code>LogEntry</code> object
+ * will be set to <code>null</code>.
+ *
+ * @param level The severity of the message. This should be one of the
+ * defined log levels but may be any integer that is interpreted in a
+ * user defined way.
+ * @param message The human readable string describing the condition or
+ * <code>null</code>.
+ * @param exception The exception that reflects the condition or
+ * <code>null</code>.
+ * @see #LOG_ERROR
+ * @see #LOG_WARNING
+ * @see #LOG_INFO
+ * @see #LOG_DEBUG
+ */
+ public void log(int level, String message, Throwable exception);
+
+ /**
+ * Logs a message associated with a specific <code>ServiceReference</code>
+ * object.
+ *
+ * <p>
+ * The <code>Throwable</code> field of the <code>LogEntry</code> will be set to
+ * <code>null</code>.
+ *
+ * @param sr The <code>ServiceReference</code> object of the service that this
+ * message is associated with or <code>null</code>.
+ * @param level The severity of the message. This should be one of the
+ * defined log levels but may be any integer that is interpreted in a
+ * user defined way.
+ * @param message Human readable string describing the condition or
+ * <code>null</code>.
+ * @see #LOG_ERROR
+ * @see #LOG_WARNING
+ * @see #LOG_INFO
+ * @see #LOG_DEBUG
+ */
+ public void log(ServiceReference sr, int level, String message);
+
+ /**
+ * Logs a message with an exception associated and a
+ * <code>ServiceReference</code> object.
+ *
+ * @param sr The <code>ServiceReference</code> object of the service that this
+ * message is associated with.
+ * @param level The severity of the message. This should be one of the
+ * defined log levels but may be any integer that is interpreted in a
+ * user defined way.
+ * @param message Human readable string describing the condition or
+ * <code>null</code>.
+ * @param exception The exception that reflects the condition or
+ * <code>null</code>.
+ * @see #LOG_ERROR
+ * @see #LOG_WARNING
+ * @see #LOG_INFO
+ * @see #LOG_DEBUG
+ */
+ public void log(ServiceReference sr, int level, String message,
+ Throwable exception);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/log/package.html b/deploymentadmin/src/main/java/org/osgi/service/log/package.html
new file mode 100644
index 0000000..a300196
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/log/package.html
@@ -0,0 +1 @@
+<!-- $Header: /cvshome/build/org.osgi.service.log/src/org/osgi/service/log/package.html,v 1.4 2006/07/12 21:07:15 hargrave Exp $ -->
<BODY>
<p>Log Service Package Version 1.3.
<p>Bundles wishing to use this package must list the package
in the Import-Package header of the bundle's manifest.
For example:
<pre>
Import-Package: org.osgi.service.log; version=1.3
</pre>
</BODY>
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/log/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/log/packageinfo
new file mode 100644
index 0000000..0117a56
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/log/packageinfo
@@ -0,0 +1 @@
+version 1.3
diff --git a/deploymentadmin/src/main/java/org/osgi/service/metatype/AttributeDefinition.java b/deploymentadmin/src/main/java/org/osgi/service/metatype/AttributeDefinition.java
new file mode 100644
index 0000000..3f3f053
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/metatype/AttributeDefinition.java
@@ -0,0 +1,279 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.metatype/src/org/osgi/service/metatype/AttributeDefinition.java,v 1.13 2006/06/16 16:31:23 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.metatype;
+
+/**
+ * An interface to describe an attribute.
+ *
+ * <p>
+ * An <code>AttributeDefinition</code> object defines a description of the data
+ * type of a property/attribute.
+ *
+ * @version $Revision: 1.13 $
+ */
+public interface AttributeDefinition {
+ /**
+ * The <code>STRING</code> (1) type.
+ *
+ * <p>
+ * Attributes of this type should be stored as <code>String</code>,
+ * <code>Vector</code> with <code>String</code> or <code>String[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int STRING = 1;
+ /**
+ * The <code>LONG</code> (2) type.
+ *
+ * Attributes of this type should be stored as <code>Long</code>,
+ * <code>Vector</code> with <code>Long</code> or <code>long[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int LONG = 2;
+ /**
+ * The <code>INTEGER</code> (3) type.
+ *
+ * Attributes of this type should be stored as <code>Integer</code>,
+ * <code>Vector</code> with <code>Integer</code> or <code>int[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int INTEGER = 3;
+ /**
+ * The <code>SHORT</code> (4) type.
+ *
+ * Attributes of this type should be stored as <code>Short</code>,
+ * <code>Vector</code> with <code>Short</code> or <code>short[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int SHORT = 4;
+ /**
+ * The <code>CHARACTER</code> (5) type.
+ *
+ * Attributes of this type should be stored as <code>Character</code>,
+ * <code>Vector</code> with <code>Character</code> or <code>char[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int CHARACTER = 5;
+ /**
+ * The <code>BYTE</code> (6) type.
+ *
+ * Attributes of this type should be stored as <code>Byte</code>,
+ * <code>Vector</code> with <code>Byte</code> or <code>byte[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int BYTE = 6;
+ /**
+ * The <code>DOUBLE</code> (7) type.
+ *
+ * Attributes of this type should be stored as <code>Double</code>,
+ * <code>Vector</code> with <code>Double</code> or <code>double[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int DOUBLE = 7;
+ /**
+ * The <code>FLOAT</code> (8) type.
+ *
+ * Attributes of this type should be stored as <code>Float</code>,
+ * <code>Vector</code> with <code>Float</code> or <code>float[]</code> objects,
+ * depending on the <code>getCardinality()</code> value.
+ */
+ public static final int FLOAT = 8;
+ /**
+ * The <code>BIGINTEGER</code> (9) type.
+ *
+ * Attributes of this type should be stored as <code>BigInteger</code>,
+ * <code>Vector</code> with <code>BigInteger</code> or <code>BigInteger[]</code>
+ * objects, depending on the <code>getCardinality()</code> value.
+ *
+ * @deprecated As of 1.1.
+ */
+ public static final int BIGINTEGER = 9;
+ /**
+ * The <code>BIGDECIMAL</code> (10) type.
+ *
+ * Attributes of this type should be stored as <code>BigDecimal</code>,
+ * <code>Vector</code> with <code>BigDecimal</code> or <code>BigDecimal[]</code>
+ * objects depending on <code>getCardinality()</code>.
+ *
+ * @deprecated As of 1.1.
+ */
+ public static final int BIGDECIMAL = 10;
+ /**
+ * The <code>BOOLEAN</code> (11) type.
+ *
+ * Attributes of this type should be stored as <code>Boolean</code>,
+ * <code>Vector</code> with <code>Boolean</code> or <code>boolean[]</code> objects
+ * depending on <code>getCardinality()</code>.
+ */
+ public static final int BOOLEAN = 11;
+
+ /**
+ * Get the name of the attribute. This name may be localized.
+ *
+ * @return The localized name of the definition.
+ */
+ public String getName();
+
+ /**
+ * Unique identity for this attribute.
+ *
+ * Attributes share a global namespace in the registry. E.g. an attribute
+ * <code>cn</code> or <code>commonName</code> must always be a <code>String</code>
+ * and the semantics are always a name of some object. They share this
+ * aspect with LDAP/X.500 attributes. In these standards the OSI Object
+ * Identifier (OID) is used to uniquely identify an attribute. If such an
+ * OID exists, (which can be requested at several standard organisations and
+ * many companies already have a node in the tree) it can be returned here.
+ * Otherwise, a unique id should be returned which can be a Java class name
+ * (reverse domain name) or generated with a GUID algorithm. Note that all
+ * LDAP defined attributes already have an OID. It is strongly advised to
+ * define the attributes from existing LDAP schemes which will give the OID.
+ * Many such schemes exist ranging from postal addresses to DHCP parameters.
+ *
+ * @return The id or oid
+ */
+ public String getID();
+
+ /**
+ * Return a description of this attribute.
+ *
+ * The description may be localized and must describe the semantics of this
+ * type and any constraints.
+ *
+ * @return The localized description of the definition.
+ */
+ public String getDescription();
+
+ /**
+ * Return the cardinality of this attribute.
+ *
+ * The OSGi environment handles multi valued attributes in arrays ([]) or in
+ * <code>Vector</code> objects. The return value is defined as follows:
+ *
+ * <pre>
+ *
+ * x = Integer.MIN_VALUE no limit, but use Vector
+ * x < 0 -x = max occurrences, store in Vector
+ * x > 0 x = max occurrences, store in array []
+ * x = Integer.MAX_VALUE no limit, but use array []
+ * x = 0 1 occurrence required
+ *
+ * </pre>
+ *
+ * @return The cardinality of this attribute.
+ */
+ public int getCardinality();
+
+ /**
+ * Return the type for this attribute.
+ *
+ * <p>
+ * Defined in the following constants which map to the appropriate Java
+ * type. <code>STRING</code>,<code>LONG</code>,<code>INTEGER</code>,
+ * <code>CHAR</code>,<code>BYTE</code>,<code>DOUBLE</code>,<code>FLOAT</code>,
+ * <code>BOOLEAN</code>.
+ *
+ * @return The type for this attribute.
+ */
+ public int getType();
+
+ /**
+ * Return a list of option values that this attribute can take.
+ *
+ * <p>
+ * If the function returns <code>null</code>, there are no option values
+ * available.
+ *
+ * <p>
+ * Each value must be acceptable to validate() (return "") and must be a
+ * <code>String</code> object that can be converted to the data type defined
+ * by getType() for this attribute.
+ *
+ * <p>
+ * This list must be in the same sequence as <code>getOptionLabels()</code>.
+ * I.e. for each index i in <code>getOptionValues</code>, i in
+ * <code>getOptionLabels()</code> should be the label.
+ *
+ * <p>
+ * For example, if an attribute can have the value male, female, unknown,
+ * this list can return
+ * <code>new String[] { "male", "female", "unknown" }</code>.
+ *
+ * @return A list values
+ */
+ public String[] getOptionValues();
+
+ /**
+ * Return a list of labels of option values.
+ *
+ * <p>
+ * The purpose of this method is to allow menus with localized labels. It is
+ * associated with <code>getOptionValues</code>. The labels returned here are
+ * ordered in the same way as the values in that method.
+ *
+ * <p>
+ * If the function returns <code>null</code>, there are no option labels
+ * available.
+ * <p>
+ * This list must be in the same sequence as the <code>getOptionValues()</code>
+ * method. I.e. for each index i in <code>getOptionLabels</code>, i in
+ * <code>getOptionValues()</code> should be the associated value.
+ *
+ * <p>
+ * For example, if an attribute can have the value male, female, unknown,
+ * this list can return (for dutch)
+ * <code>new String[] { "Man", "Vrouw", "Onbekend" }</code>.
+ *
+ * @return A list values
+ */
+ public String[] getOptionLabels();
+
+ /**
+ * Validate an attribute in <code>String</code> form.
+ *
+ * An attribute might be further constrained in value. This method will
+ * attempt to validate the attribute according to these constraints. It can
+ * return three different values:
+ *
+ * <pre>
+ * null No validation present
+ * "" No problems detected
+ * "..." A localized description of why the value is wrong
+ * </pre>
+ *
+ * @param value The value before turning it into the basic data type
+ * @return <code>null</code>, "", or another string
+ */
+ public String validate(String value);
+
+ /**
+ * Return a default for this attribute.
+ *
+ * The object must be of the appropriate type as defined by the cardinality
+ * and <code>getType()</code>. The return type is a list of <code>String</code>
+ * objects that can be converted to the appropriate type. The cardinality of
+ * the return array must follow the absolute cardinality of this type. E.g.
+ * if the cardinality = 0, the array must contain 1 element. If the
+ * cardinality is 1, it must contain 0 or 1 elements. If it is -5, it must
+ * contain from 0 to max 5 elements. Note that the special case of a 0
+ * cardinality, meaning a single value, does not allow arrays or vectors of
+ * 0 elements.
+ *
+ * @return Return a default value or <code>null</code> if no default exists.
+ */
+ public String[] getDefaultValue();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeInformation.java b/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeInformation.java
new file mode 100644
index 0000000..ce544e3
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeInformation.java
@@ -0,0 +1,52 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.metatype/src/org/osgi/service/metatype/MetaTypeInformation.java,v 1.8 2006/06/16 16:31:23 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.metatype;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * A MetaType Information object is created by the MetaTypeService to return
+ * meta type information for a specific bundle.
+ *
+ * @version $Revision: 1.8 $
+ * @since 1.1
+ */
+public interface MetaTypeInformation extends MetaTypeProvider {
+ /**
+ * Return the PIDs (for ManagedServices) for which ObjectClassDefinition
+ * information is available.
+ *
+ * @return Array of PIDs.
+ */
+ public String[] getPids();
+
+ /**
+ * Return the Factory PIDs (for ManagedServiceFactories) for which
+ * ObjectClassDefinition information is available.
+ *
+ * @return Array of Factory PIDs.
+ */
+ public String[] getFactoryPids();
+
+ /**
+ * Return the bundle for which this object provides meta type information.
+ *
+ * @return Bundle for which this object provides meta type information.
+ */
+ public Bundle getBundle();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeProvider.java b/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeProvider.java
new file mode 100644
index 0000000..a21f617
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeProvider.java
@@ -0,0 +1,57 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.metatype/src/org/osgi/service/metatype/MetaTypeProvider.java,v 1.11 2006/06/16 16:31:23 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.metatype;
+
+/**
+ * Provides access to metatypes.
+ *
+ * @version $Revision: 1.11 $
+ */
+public interface MetaTypeProvider {
+ /**
+ * Returns an object class definition for the specified id localized to the
+ * specified locale.
+ *
+ * <p>
+ * The locale parameter must be a name that consists of <code>language</code>[
+ * "_" <code>country</code>[ "_" <code>variation</code>] ] as is customary in
+ * the <code>Locale</code> class. This <code>Locale</code> class is not used
+ * because certain profiles do not contain it.
+ *
+ * @param id The ID of the requested object class. This can be a pid or
+ * factory pid returned by getPids or getFactoryPids.
+ * @param locale The locale of the definition or <code>null</code> for default
+ * locale.
+ * @return A <code>ObjectClassDefinition</code> object.
+ * @throws IllegalArgumentException If the id or locale arguments are not
+ * valid
+ */
+ public ObjectClassDefinition getObjectClassDefinition(String id, String locale);
+
+ /**
+ * Return a list of available locales.
+ *
+ * The results must be names that consists of language [ _ country [ _
+ * variation ]] as is customary in the <code>Locale</code> class.
+ *
+ * @return An array of locale strings or <code>null</code> if there is no
+ * locale specific localization can be found.
+ *
+ */
+ public String[] getLocales();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeService.java b/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeService.java
new file mode 100644
index 0000000..ea1b1b2
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/metatype/MetaTypeService.java
@@ -0,0 +1,53 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.metatype/src/org/osgi/service/metatype/MetaTypeService.java,v 1.10 2006/06/16 16:31:23 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.metatype;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * The MetaType Service can be used to obtain meta type information for a
+ * bundle. The MetaType Service will examine the specified bundle for meta type
+ * documents to create the returned <code>MetaTypeInformation</code> object.
+ *
+ * <p>
+ * If the specified bundle does not contain any meta type documents, then a
+ * <code>MetaTypeInformation</code> object will be returned that wrappers any
+ * <code>ManagedService</code> or <code>ManagedServiceFactory</code>
+ * services registered by the specified bundle that implement
+ * <code>MetaTypeProvider</code>. Thus the MetaType Service can be used to
+ * retrieve meta type information for bundles which contain a meta type
+ * documents or which provide their own <code>MetaTypeProvider</code> objects.
+ *
+ * @version $Revision: 1.10 $
+ * @since 1.1
+ */
+public interface MetaTypeService {
+ /**
+ * Return the MetaType information for the specified bundle.
+ *
+ * @param bundle The bundle for which meta type information is requested.
+ * @return A MetaTypeInformation object for the specified bundle.
+ */
+ public MetaTypeInformation getMetaTypeInformation(Bundle bundle);
+
+ /**
+ * Location of meta type documents. The MetaType Service will process each
+ * entry in the meta type documents directory.
+ */
+ public final static String METATYPE_DOCUMENTS_LOCATION = "OSGI-INF/metatype";
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/metatype/ObjectClassDefinition.java b/deploymentadmin/src/main/java/org/osgi/service/metatype/ObjectClassDefinition.java
new file mode 100644
index 0000000..754b636
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/metatype/ObjectClassDefinition.java
@@ -0,0 +1,121 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.metatype/src/org/osgi/service/metatype/ObjectClassDefinition.java,v 1.11 2006/06/16 16:31:23 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.metatype;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Description for the data type information of an objectclass.
+ *
+ * @version $Revision: 1.11 $
+ */
+public interface ObjectClassDefinition {
+ /**
+ * Argument for <code>getAttributeDefinitions(int)</code>.
+ * <p>
+ * <code>REQUIRED</code> indicates that only the required definitions are
+ * returned. The value is 1.
+ */
+ public static final int REQUIRED = 1;
+ /**
+ * Argument for <code>getAttributeDefinitions(int)</code>.
+ * <p>
+ * <code>OPTIONAL</code> indicates that only the optional definitions are
+ * returned. The value is 2.
+ */
+ public static final int OPTIONAL = 2;
+ /**
+ * Argument for <code>getAttributeDefinitions(int)</code>.
+ * <p>
+ * <code>ALL</code> indicates that all the definitions are returned. The value
+ * is -1.
+ */
+ public static final int ALL = 0xFFFFFFFF;
+
+ /**
+ * Return the name of this object class.
+ *
+ * The name may be localized.
+ *
+ * @return The name of this object class.
+ */
+ public String getName();
+
+ /**
+ * Return the id of this object class.
+ *
+ * <p>
+ * <code>ObjectDefintion</code> objects share a global namespace in the
+ * registry. They share this aspect with LDAP/X.500 attributes. In these
+ * standards the OSI Object Identifier (OID) is used to uniquely identify
+ * object classes. If such an OID exists, (which can be requested at several
+ * standard organisations and many companies already have a node in the
+ * tree) it can be returned here. Otherwise, a unique id should be returned
+ * which can be a java class name (reverse domain name) or generated with a
+ * GUID algorithm. Note that all LDAP defined object classes already have an
+ * OID associated. It is strongly advised to define the object classes from
+ * existing LDAP schemes which will give the OID for free. Many such schemes
+ * exist ranging from postal addresses to DHCP parameters.
+ *
+ * @return The id of this object class.
+ */
+ public String getID();
+
+ /**
+ * Return a description of this object class.
+ *
+ * The description may be localized.
+ *
+ * @return The description of this object class.
+ */
+ public String getDescription();
+
+ /**
+ * Return the attribute definitions for this object class.
+ *
+ * <p>
+ * Return a set of attributes. The filter parameter can distinguish between
+ * <code>ALL</code>,<code>REQUIRED</code> or the <code>OPTIONAL</code>
+ * attributes.
+ *
+ * @param filter <code>ALL</code>,<code>REQUIRED</code>,<code>OPTIONAL</code>
+ * @return An array of attribute definitions or <code>null</code> if no
+ * attributes are selected
+ */
+ public AttributeDefinition[] getAttributeDefinitions(int filter);
+
+ /**
+ * Return an <code>InputStream</code> object that can be used to create an
+ * icon from.
+ *
+ * <p>
+ * Indicate the size and return an <code>InputStream</code> object containing
+ * an icon. The returned icon maybe larger or smaller than the indicated
+ * size.
+ *
+ * <p>
+ * The icon may depend on the localization.
+ *
+ * @param size Requested size of an icon, e.g. a 16x16 pixels icon then size =
+ * 16
+ * @return An InputStream representing an icon or <code>null</code>
+ * @throws IOException If the <code>InputStream</code> cannot be returned.
+ */
+ public InputStream getIcon(int size) throws IOException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/metatype/package.html b/deploymentadmin/src/main/java/org/osgi/service/metatype/package.html
new file mode 100644
index 0000000..4b074c9
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/metatype/package.html
@@ -0,0 +1 @@
+<!-- $Header: /cvshome/build/org.osgi.service.metatype/src/org/osgi/service/metatype/package.html,v 1.5 2006/07/12 21:07:16 hargrave Exp $ -->
<BODY>
<p>Metatype Package Version 1.1.
<p>Bundles wishing to use this package must list the package
in the <TT>Import-Package</TT> header of the bundle's manifest.
For example:
<pre>
Import-Package: org.osgi.service.metatype; version=1.1
</pre>
</BODY>
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/metatype/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/metatype/packageinfo
new file mode 100644
index 0000000..3987f9c
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/metatype/packageinfo
@@ -0,0 +1 @@
+version 1.1
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorAdmin.java b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorAdmin.java
new file mode 100644
index 0000000..c9377c8
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorAdmin.java
@@ -0,0 +1,359 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.monitor/src/org/osgi/service/monitor/MonitorAdmin.java,v 1.25 2006/06/16 16:31:25 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.monitor;
+
+/**
+ * The <code>MonitorAdmin</code> service is a singleton service that handles
+ * <code>StatusVariable</code> query requests and measurement job control
+ * requests.
+ * <p>
+ * Note that an alternative but not recommended way of obtaining
+ * <code>StatusVariable</code>s is that applications having the required
+ * <code>ServicePermissions</code> can query the list of
+ * <code>Monitorable</code> services from the service registry and then query
+ * the list of <code>StatusVariable</code> names from the
+ * <code>Monitorable</code> services. This way all services which publish
+ * <code>StatusVariable</code>s will be returned regardless of whether they
+ * do or do not hold the necessary <code>MonitorPermission</code> for
+ * publishing <code>StatusVariable</code>s. By using the
+ * <code>MonitorAdmin</code> to obtain the <code>StatusVariable</code>s it
+ * is guaranteed that only those <code>Monitorable</code> services will be
+ * accessed who are authorized to publish <code>StatusVariable</code>s. It is
+ * the responsibility of the <code>MonitorAdmin</code> implementation to check
+ * the required permissions and show only those variables which pass this check.
+ * <p>
+ * The events posted by <code>MonitorAdmin</code> contain the following
+ * properties:
+ * <ul>
+ * <li><code>mon.monitorable.pid</code>: The identifier of the
+ * <code>Monitorable</code>
+ * <li><code>mon.statusvariable.name</code>: The identifier of the
+ * <code>StatusVariable</code> within the given <code>Monitorable</code>
+ * <li><code>mon.statusvariable.value</code>: The value of the
+ * <code>StatusVariable</code>, represented as a <code>String</code>
+ * <li><code>mon.listener.id</code>: The identifier of the initiator of the
+ * monitoring job (only present if the event was generated due to a monitoring
+ * job)
+ * </ul>
+ * <p>
+ * Most of the methods require either a Monitorable ID or a Status Variable path
+ * parameter, the latter in [Monitorable_ID]/[StatusVariable_ID] format. These
+ * parameters must not be <code>null</code>, and the IDs they contain must
+ * conform to their respective definitions in {@link Monitorable} and
+ * {@link StatusVariable}. If any of the restrictions are violated, the method
+ * must throw an <code>IllegalArgumentException</code>.
+ */
+public interface MonitorAdmin {
+
+ /**
+ * Returns a <code>StatusVariable</code> addressed by its full path.
+ * The entity which queries a <code>StatusVariable</code> needs to hold
+ * <code>MonitorPermission</code> for the given target with the
+ * <code>read</code> action present.
+ *
+ * @param path the full path of the <code>StatusVariable</code> in
+ * [Monitorable_ID]/[StatusVariable_ID] format
+ * @return the <code>StatusVariable</code> object
+ * @throws java.lang.IllegalArgumentException if <code>path</code> is
+ * <code>null</code> or otherwise invalid, or points to a
+ * non-existing <code>StatusVariable</code>
+ * @throws java.lang.SecurityException if the caller does not hold a
+ * <code>MonitorPermission</code> for the
+ * <code>StatusVariable</code> specified by <code>path</code>
+ * with the <code>read</code> action present
+ */
+ public StatusVariable getStatusVariable(String path)
+ throws IllegalArgumentException, SecurityException;
+
+ /**
+ * Returns the names of the <code>Monitorable</code> services that are
+ * currently registered. The <code>Monitorable</code> instances are not
+ * accessible through the <code>MonitorAdmin</code>, so that requests to
+ * individual status variables can be filtered with respect to the
+ * publishing rights of the <code>Monitorable</code> and the reading
+ * rights of the caller.
+ * <p>
+ * The returned array contains the names in alphabetical order. It cannot be
+ * <code>null</code>, an empty array is returned if no
+ * <code>Monitorable</code> services are registered.
+ *
+ * @return the array of <code>Monitorable</code> names
+ */
+ public String[] getMonitorableNames();
+
+ /**
+ * Returns the <code>StatusVariable</code> objects published by a
+ * <code>Monitorable</code> instance. The <code>StatusVariables</code>
+ * will hold the values taken at the time of this method call. Only those
+ * status variables are returned where the following two conditions are met:
+ * <ul>
+ * <li>the specified <code>Monitorable</code> holds a
+ * <code>MonitorPermission</code> for the status variable with the
+ * <code>publish</code> action present
+ * <li>the caller holds a <code>MonitorPermission</code> for the status
+ * variable with the <code>read</code> action present
+ * </ul>
+ * All other status variables are silently ignored, they are omitted from
+ * the result.
+ * <p>
+ * The elements in the returned array are in no particular order. The return
+ * value cannot be <code>null</code>, an empty array is returned if no
+ * (authorized and readable) Status Variables are provided by the given
+ * <code>Monitorable</code>.
+ *
+ * @param monitorableId the identifier of a <code>Monitorable</code>
+ * instance
+ * @return a list of <code>StatusVariable</code> objects published
+ * by the specified <code>Monitorable</code>
+ * @throws java.lang.IllegalArgumentException if <code>monitorableId</code>
+ * is <code>null</code> or otherwise invalid, or points to a
+ * non-existing <code>Monitorable</code>
+ */
+ public StatusVariable[] getStatusVariables(String monitorableId)
+ throws IllegalArgumentException;
+
+ /**
+ * Returns the list of <code>StatusVariable</code> names published by a
+ * <code>Monitorable</code> instance. Only those status variables are
+ * listed where the following two conditions are met:
+ * <ul>
+ * <li>the specified <code>Monitorable</code> holds a
+ * <code>MonitorPermission</code> for the status variable with the
+ * <code>publish</code> action present
+ * <li>the caller holds a <code>MonitorPermission</code> for
+ * the status variable with the <code>read</code> action present
+ * </ul>
+ * All other status variables are silently ignored, their names are omitted
+ * from the list.
+ * <p>
+ * The returned array does not contain duplicates, and the elements are in
+ * alphabetical order. It cannot be <code>null</code>, an empty array is
+ * returned if no (authorized and readable) Status Variables are provided
+ * by the given <code>Monitorable</code>.
+ *
+ * @param monitorableId the identifier of a <code>Monitorable</code>
+ * instance
+ * @return a list of <code>StatusVariable</code> objects names
+ * published by the specified <code>Monitorable</code>
+ * @throws java.lang.IllegalArgumentException if <code>monitorableId</code>
+ * is <code>null</code> or otherwise invalid, or points to a
+ * non-existing <code>Monitorable</code>
+ */
+ public String[] getStatusVariableNames(String monitorableId)
+ throws IllegalArgumentException;
+
+ /**
+ * Switches event sending on or off for the specified
+ * <code>StatusVariable</code>s. When the <code>MonitorAdmin</code> is
+ * notified about a <code>StatusVariable</code> being updated it sends an
+ * event unless this feature is switched off. Note that events within a
+ * monitoring job can not be switched off. The event sending state of the
+ * <code>StatusVariables</code> must not be persistently stored. When a
+ * <code>StatusVariable</code> is registered for the first time in a
+ * framework session, its event sending state is set to ON by default.
+ * <p>
+ * Usage of the "*" wildcard is allowed in the path argument of this method
+ * as a convenience feature. The wildcard can be used in either or both path
+ * fragments, but only at the end of the fragments. The semantics of the
+ * wildcard is that it stands for any matching <code>StatusVariable</code>
+ * at the time of the method call, it does not affect the event sending
+ * status of <code>StatusVariable</code>s which are not yet registered. As
+ * an example, when the <code>switchEvents("MyMonitorable/*", false)</code>
+ * method is executed, event sending from all <code>StatusVariables</code>
+ * of the MyMonitorable service are switched off. However, if the
+ * MyMonitorable service starts to publish a new <code>StatusVariable</code>
+ * later, it's event sending status is on by default.
+ *
+ * @param path the identifier of the <code>StatusVariable</code>(s) in
+ * [Monitorable_id]/[StatusVariable_id] format, possibly with the
+ * "*" wildcard at the end of either path fragment
+ * @param on <code>false</code> if event sending should be switched off,
+ * <code>true</code> if it should be switched on for the given path
+ * @throws java.lang.SecurityException if the caller does not hold
+ * <code>MonitorPermission</code> with the
+ * <code>switchevents</code> action or if there is any
+ * <code>StatusVariable</code> in the <code>path</code> field for
+ * which it is not allowed to switch event sending on or off as per
+ * the target field of the permission
+ * @throws java.lang.IllegalArgumentException if <code>path</code> is
+ * <code>null</code> or otherwise invalid, or points to a
+ * non-existing <code>StatusVariable</code>
+ */
+ public void switchEvents(String path, boolean on)
+ throws IllegalArgumentException, SecurityException;
+
+ /**
+ * Issues a request to reset a given <code>StatusVariable</code>.
+ * Depending on the semantics of the <code>StatusVariable</code> this call
+ * may or may not succeed: it makes sense to reset a counter to its starting
+ * value, but e.g. a <code>StatusVariable</code> of type String might not
+ * have a meaningful default value. Note that for numeric
+ * <code>StatusVariable</code>s the starting value may not necessarily be
+ * 0. Resetting a <code>StatusVariable</code> triggers a monitor event if
+ * the <code>StatusVariable</code> supports update notifications.
+ * <p>
+ * The entity that wants to reset the <code>StatusVariable</code> needs to
+ * hold <code>MonitorPermission</code> with the <code>reset</code>
+ * action present. The target field of the permission must match the
+ * <code>StatusVariable</code> name to be reset.
+ *
+ * @param path the identifier of the <code>StatusVariable</code> in
+ * [Monitorable_id]/[StatusVariable_id] format
+ * @return <code>true</code> if the <code>Monitorable</code> could
+ * successfully reset the given <code>StatusVariable</code>,
+ * <code>false</code> otherwise
+ * @throws java.lang.IllegalArgumentException if <code>path</code> is
+ * <code>null</code> or otherwise invalid, or points to a
+ * non-existing <code>StatusVariable</code>
+ * @throws java.lang.SecurityException if the caller does not hold
+ * <code>MonitorPermission</code> with the <code>reset</code>
+ * action or if the specified <code>StatusVariable</code> is not
+ * allowed to be reset as per the target field of the permission
+ */
+ public boolean resetStatusVariable(String path)
+ throws IllegalArgumentException, SecurityException;
+
+ /**
+ * Returns a human readable description of the given
+ * <code>StatusVariable</code>. The <code>null</code> value may be returned
+ * if there is no description for the given <code>StatusVariable</code>.
+ * <p>
+ * The entity that queries a <code>StatusVariable</code> needs to hold
+ * <code>MonitorPermission</code> for the given target with the
+ * <code>read</code> action present.
+ *
+ * @param path the full path of the <code>StatusVariable</code> in
+ * [Monitorable_ID]/[StatusVariable_ID] format
+ * @return the human readable description of this
+ * <code>StatusVariable</code> or <code>null</code> if it is not
+ * set
+ * @throws java.lang.IllegalArgumentException if <code>path</code> is
+ * <code>null</code> or otherwise invalid, or points to a
+ * non-existing <code>StatusVariable</code>
+ * @throws java.lang.SecurityException if the caller does not hold a
+ * <code>MonitorPermission</code> for the
+ * <code>StatusVariable</code> specified by <code>path</code>
+ * with the <code>read</code> action present
+ */
+ public String getDescription(String path)
+ throws IllegalArgumentException, SecurityException;
+
+ /**
+ * Starts a time based <code>MonitoringJob</code> with the parameters
+ * provided. Monitoring events will be sent according to the specified
+ * schedule. All specified <code>StatusVariable</code>s must exist when the
+ * job is started. The initiator string is used in the
+ * <code>mon.listener.id</code> field of all events triggered by the job,
+ * to allow filtering the events based on the initiator.
+ * <p>
+ * The <code>schedule</code> parameter specifies the time in seconds
+ * between two measurements, it must be greater than 0. The first
+ * measurement will be taken when the timer expires for the first time, not
+ * when this method is called.
+ * <p>
+ * The <code>count</code> parameter defines the number of measurements to be
+ * taken, and must either be a positive integer, or 0 if the measurement is
+ * to run until explicitely stopped.
+ * <p>
+ * The entity which initiates a <code>MonitoringJob</code> needs to hold
+ * <code>MonitorPermission</code> for all the specified target
+ * <code>StatusVariable</code>s with the <code>startjob</code> action
+ * present. If the permission's action string specifies a minimal sampling
+ * interval then the <code>schedule</code> parameter should be at least as
+ * great as the value in the action string.
+ *
+ * @param initiator the identifier of the entity that initiated the job
+ * @param statusVariables the list of <code>StatusVariable</code>s to be
+ * monitored, with each <code>StatusVariable</code> name given in
+ * [Monitorable_PID]/[StatusVariable_ID] format
+ * @param schedule the time in seconds between two measurements
+ * @param count the number of measurements to be taken, or 0 for the
+ * measurement to run until explicitely stopped
+ * @return the successfully started job object, cannot be <code>null</code>
+ * @throws java.lang.IllegalArgumentException if the list of
+ * <code>StatusVariable</code> names contains an invalid or
+ * non-existing <code>StatusVariable</code>; if
+ * <code>initiator</code> is <code>null</code> or empty; or if the
+ * <code>schedule</code> or <code>count</code> parameters are
+ * invalid
+ * @throws java.lang.SecurityException if the caller does not hold
+ * <code>MonitorPermission</code> for all the specified
+ * <code>StatusVariable</code>s, with the <code>startjob</code>
+ * action present, or if the permission does not allow starting the
+ * job with the given frequency
+ */
+ public MonitoringJob startScheduledJob(String initiator,
+ String[] statusVariables, int schedule, int count)
+ throws IllegalArgumentException, SecurityException;
+
+ /**
+ * Starts a change based <code>MonitoringJob</code> with the parameters
+ * provided. Monitoring events will be sent when the
+ * <code>StatusVariable</code>s of this job are updated. All specified
+ * <code>StatusVariable</code>s must exist when the job is started, and
+ * all must support update notifications. The initiator string is used in
+ * the <code>mon.listener.id</code> field of all events triggered by the
+ * job, to allow filtering the events based on the initiator.
+ * <p>
+ * The <code>count</code> parameter specifies the number of changes that
+ * must happen to a <code>StatusVariable</code> before a new notification is
+ * sent, this must be a positive integer.
+ * <p>
+ * The entity which initiates a <code>MonitoringJob</code> needs to hold
+ * <code>MonitorPermission</code> for all the specified target
+ * <code>StatusVariable</code>s with the <code>startjob</code> action
+ * present.
+ *
+ * @param initiator the identifier of the entity that initiated the job
+ * @param statusVariables the list of <code>StatusVariable</code>s to be
+ * monitored, with each <code>StatusVariable</code> name given in
+ * [Monitorable_PID]/[StatusVariable_ID] format
+ * @param count the number of changes that must happen to a
+ * <code>StatusVariable</code> before a new notification is sent
+ * @return the successfully started job object, cannot be <code>null</code>
+ * @throws java.lang.IllegalArgumentException if the list of
+ * <code>StatusVariable</code> names contains an invalid or
+ * non-existing <code>StatusVariable</code>, or one that does not
+ * support notifications; if the <code>initiator</code> is
+ * <code>null</code> or empty; or if <code>count</code> is invalid
+ * @throws java.lang.SecurityException if the caller does not hold
+ * <code>MonitorPermission</code> for all the specified
+ * <code>StatusVariable</code>s, with the <code>startjob</code>
+ * action present
+ */
+ public MonitoringJob startJob(String initiator, String[] statusVariables,
+ int count) throws IllegalArgumentException, SecurityException;
+
+ /**
+ * Returns the list of currently running <code>MonitoringJob</code>s.
+ * Jobs are only visible to callers that have the necessary permissions: to
+ * receive a Monitoring Job in the returned list, the caller must hold all
+ * permissions required for starting the job. This means that if the caller
+ * does not have <code>MonitorPermission</code> with the proper
+ * <code>startjob</code> action for all the Status Variables monitored by a
+ * job, then that job will be silently omitted from the results.
+ * <p>
+ * The returned array cannot be <code>null</code>, an empty array is
+ * returned if there are no running jobs visible to the caller at the time
+ * of the call.
+ *
+ * @return the list of running jobs visible to the caller
+ */
+ public MonitoringJob[] getRunningJobs();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorListener.java b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorListener.java
new file mode 100644
index 0000000..44fa845
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorListener.java
@@ -0,0 +1,42 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.monitor/src/org/osgi/service/monitor/MonitorListener.java,v 1.11 2006/06/16 16:31:25 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.monitor;
+
+/**
+ * The <code>MonitorListener</code> is used by <code>Monitorable</code>
+ * services to send notifications when a <code>StatusVariable</code> value is
+ * changed. The <code>MonitorListener</code> should register itself as a
+ * service at the OSGi Service Registry. This interface must (only) be
+ * implemented by the Monitor Admin component.
+ */
+public interface MonitorListener {
+ /**
+ * Callback for notification of a <code>StatusVariable</code> change.
+ *
+ * @param monitorableId the identifier of the <code>Monitorable</code>
+ * instance reporting the change
+ * @param statusVariable the <code>StatusVariable</code> that has changed
+ * @throws java.lang.IllegalArgumentException if the specified monitorable
+ * ID is invalid (<code>null</code>, empty, or contains illegal
+ * characters) or points to a non-existing <code>Monitorable</code>,
+ * or if <code>statusVariable</code> is <code>null</code>
+ */
+ public void updated(String monitorableId, StatusVariable statusVariable)
+ throws IllegalArgumentException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorPermission.java b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorPermission.java
new file mode 100644
index 0000000..b0ca12d
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitorPermission.java
@@ -0,0 +1,366 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.monitor/src/org/osgi/service/monitor/MonitorPermission.java,v 1.17 2006/06/21 15:17:16 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2005, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.monitor;
+
+import java.io.UnsupportedEncodingException;
+import java.security.Permission;
+import java.util.StringTokenizer;
+
+/**
+ * Indicates the callers authority to publish, read or reset
+ * <code>StatusVariable</code>s, to switch event sending on or off or to
+ * start monitoring jobs. The target of the permission is the identifier of the
+ * <code>StatusVariable</code>, the action can be <code>read</code>,
+ * <code>publish</code>, <code>reset</code>, <code>startjob</code>,
+ * <code>switchevents</code>, or the combination of these separated by
+ * commas. Action names are interpreted case-insensitively, but the canonical
+ * action string returned by {@link #getActions} uses the forms defined by the
+ * action constants.
+ * <p>
+ * If the wildcard <code>*</code> appears in the actions field, all legal
+ * monitoring commands are allowed on the designated target(s) by the owner of
+ * the permission.
+ */
+public class MonitorPermission extends Permission {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -9084425194463274314L;
+
+ /**
+ * Holders of <code>MonitorPermission</code> with the <code>read</code>
+ * action present are allowed to read the value of the
+ * <code>StatusVariable</code>s specified in the permission's target field.
+ */
+ public static final String READ = "read";
+
+ /**
+ * Holders of <code>MonitorPermission</code> with the <code>reset</code>
+ * action present are allowed to reset the value of the
+ * <code>StatusVariable</code>s specified in the permission's target field.
+ */
+ public static final String RESET = "reset";
+
+ /**
+ * Holders of <code>MonitorPermission</code> with the <code>publish</code>
+ * action present are <code>Monitorable</code> services that are allowed
+ * to publish the <code>StatusVariable</code>s specified in the
+ * permission's target field. Note, that this permission cannot be enforced
+ * when a <code>Monitorable</code> registers to the framework, because the
+ * Service Registry does not know about this permission. Instead, any
+ * <code>StatusVariable</code>s published by a <code>Monitorable</code>
+ * without the corresponding <code>publish</code> permission are silently
+ * ignored by <code>MonitorAdmin</code>, and are therefore invisible to the
+ * users of the monitoring service.
+ */
+ public static final String PUBLISH = "publish";
+
+ /**
+ * Holders of <code>MonitorPermission</code> with the <code>startjob</code>
+ * action present are allowed to initiate monitoring jobs involving the
+ * <code>StatusVariable</code>s specified in the permission's target field.
+ * <p>
+ * A minimal sampling interval can be optionally defined in the following
+ * form: <code>startjob:n</code>. This allows the holder of the permission
+ * to initiate time based jobs with a measurement interval of at least
+ * <code>n</code> seconds. If <code>n</code> is not specified or 0 then the
+ * holder of this permission is allowed to start monitoring jobs specifying
+ * any frequency.
+ */
+ public static final String STARTJOB = "startjob";
+
+ /**
+ * Holders of <code>MonitorPermission</code> with the
+ * <code>switchevents</code> action present are allowed to switch event
+ * sending on or off for the value of the <code>StatusVariable</code>s
+ * specified in the permission's target field.
+ */
+ public static final String SWITCHEVENTS = "switchevents";
+
+ private static final int READ_FLAG = 0x1;
+ private static final int RESET_FLAG = 0x2;
+ private static final int PUBLISH_FLAG = 0x4;
+ private static final int STARTJOB_FLAG = 0x8;
+ private static final int SWITCHEVENTS_FLAG = 0x10;
+
+ private static final int ALL_FLAGS = READ_FLAG | RESET_FLAG |
+ PUBLISH_FLAG | STARTJOB_FLAG | SWITCHEVENTS_FLAG;
+
+ private String monId;
+ private String varId;
+ private boolean prefixMonId;
+ private boolean prefixVarId;
+ private int mask;
+ private int minJobInterval;
+
+ /**
+ * Create a <code>MonitorPermission</code> object, specifying the target
+ * and actions.
+ * <p>
+ * The <code>statusVariable</code> parameter is the target of the
+ * permission, defining one or more status variable names to which the
+ * specified actions apply. Multiple status variable names can be selected
+ * by using the wildcard <code>*</code> in the target string. The wildcard
+ * is allowed in both fragments, but only at the end of the fragments.
+ * <p>
+ * For example, the following targets are valid:
+ * <code>com.mycomp.myapp/queue_length</code>,
+ * <code>com.mycomp.myapp/*</code>, <code>com.mycomp.*/*</code>,
+ * <code>*/*</code>, <code>*/queue_length</code>,
+ * <code>*/queue*</code>.
+ * <p>
+ * The following targets are invalid:
+ * <code>*.myapp/queue_length</code>, <code>com.*.myapp/*</code>,
+ * <code>*</code>.
+ * <p>
+ * The <code>actions</code> parameter specifies the allowed action(s):
+ * <code>read</code>, <code>publish</code>, <code>startjob</code>,
+ * <code>reset</code>, <code>switchevents</code>, or the combination of
+ * these separated by commas. String constants are defined in this class for
+ * each valid action. Passing <code>"*"</code> as the action
+ * string is equivalent to listing all actions.
+ *
+ * @param statusVariable the identifier of the <code>StatusVariable</code>
+ * in [Monitorable_id]/[StatusVariable_id] format
+ * @param actions the list of allowed actions separated by commas, or
+ * <code>*</code> for all actions
+ * @throws java.lang.IllegalArgumentException if either parameter is
+ * <code>null</code>, or invalid with regard to the constraints
+ * defined above and in the documentation of the used actions
+ */
+ public MonitorPermission(String statusVariable, String actions)
+ throws IllegalArgumentException {
+ super(statusVariable);
+
+ if(statusVariable == null)
+ throw new IllegalArgumentException(
+ "Invalid StatusVariable path 'null'.");
+
+ if(actions == null)
+ throw new IllegalArgumentException(
+ "Invalid actions string 'null'.");
+
+ int sep = statusVariable.indexOf('/');
+ int len = statusVariable.length();
+
+ if (sep == -1)
+ throw new IllegalArgumentException(
+ "Invalid StatusVariable path: should contain '/' separator.");
+ if (sep == 0 || sep == statusVariable.length() - 1)
+ throw new IllegalArgumentException(
+ "Invalid StatusVariable path: empty monitorable ID or StatusVariable name.");
+
+ prefixMonId = statusVariable.charAt(sep - 1) == '*';
+ prefixVarId = statusVariable.charAt(len - 1) == '*';
+
+ monId = statusVariable.substring(0, prefixMonId ? sep - 1 : sep);
+ varId = statusVariable.substring(sep + 1, prefixVarId ? len - 1 : len);
+
+ checkId(monId, "Monitorable ID part of the target");
+ checkId(varId, "Status Variable ID part of the target");
+
+ minJobInterval = 0;
+
+ if(actions.equals("*"))
+ mask = ALL_FLAGS;
+ else {
+ mask = 0;
+ StringTokenizer st = new StringTokenizer(actions, ",");
+ while (st.hasMoreTokens()) {
+ String action = st.nextToken();
+ if (action.equalsIgnoreCase(READ)) {
+ addToMask(READ_FLAG, READ);
+ } else if (action.equalsIgnoreCase(RESET)) {
+ addToMask(RESET_FLAG, RESET);
+ } else if (action.equalsIgnoreCase(PUBLISH)) {
+ addToMask(PUBLISH_FLAG, PUBLISH);
+ } else if (action.equalsIgnoreCase(SWITCHEVENTS)) {
+ addToMask(SWITCHEVENTS_FLAG, SWITCHEVENTS);
+ } else if (action.toLowerCase().startsWith(STARTJOB)) {
+ minJobInterval = 0;
+
+ int slen = STARTJOB.length();
+ if (action.length() != slen) {
+ if (action.charAt(slen) != ':')
+ throw new IllegalArgumentException(
+ "Invalid action '" + action + "'.");
+
+ try {
+ minJobInterval = Integer.parseInt(action
+ .substring(slen + 1));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(
+ "Invalid parameter in startjob action '"
+ + action + "'.");
+ }
+ }
+ addToMask(STARTJOB_FLAG, STARTJOB);
+ } else
+ throw new IllegalArgumentException("Invalid action '" +
+ action + "'");
+ }
+ }
+ }
+
+ private void addToMask(int action, String actionString) {
+ if((mask & action) != 0)
+ throw new IllegalArgumentException("Invalid action string: " +
+ actionString + " appears multiple times.");
+
+ mask |= action;
+ }
+
+ private void checkId(String id, String idName)
+ throws IllegalArgumentException {
+
+ byte[] nameBytes;
+ try {
+ nameBytes = id.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // never happens, "UTF-8" must always be supported
+ throw new IllegalStateException(e.getMessage());
+ }
+ if(nameBytes.length > StatusVariable.MAX_ID_LENGTH)
+ throw new IllegalArgumentException(idName + " is too long (over " +
+ StatusVariable.MAX_ID_LENGTH + " bytes in UTF-8 encoding).");
+
+
+ if (id.equals(".") || id.equals(".."))
+ throw new IllegalArgumentException(idName + " is invalid.");
+
+ char[] chars = id.toCharArray();
+ for (int i = 0; i < chars.length; i++)
+ if (StatusVariable.SYMBOLIC_NAME_CHARACTERS.indexOf(chars[i]) == -1)
+ throw new IllegalArgumentException(idName +
+ " contains invalid characters.");
+ }
+
+ /**
+ * Create an integer hash of the object. The hash codes of
+ * <code>MonitorPermission</code>s <code>p1</code> and <code>p2</code> are
+ * the same if <code>p1.equals(p2)</code>.
+ *
+ * @return the hash of the object
+ */
+ public int hashCode() {
+ return new Integer(mask).hashCode()
+ ^ new Integer(minJobInterval).hashCode() ^ monId.hashCode()
+ ^ new Boolean(prefixMonId).hashCode()
+ ^ varId.hashCode()
+ ^ new Boolean(prefixVarId).hashCode();
+ }
+
+ /**
+ * Determines the equality of two <code>MonitorPermission</code> objects.
+ * Two <code>MonitorPermission</code> objects are equal if their target
+ * strings are equal and the same set of actions are listed in their action
+ * strings.
+ *
+ * @param o the object being compared for equality with this object
+ * @return <code>true</code> if the two permissions are equal
+ */
+ public boolean equals(Object o) {
+ if (!(o instanceof MonitorPermission))
+ return false;
+
+ MonitorPermission other = (MonitorPermission) o;
+
+ return mask == other.mask && minJobInterval == other.minJobInterval
+ && monId.equals(other.monId)
+ && prefixMonId == other.prefixMonId
+ && varId.equals(other.varId)
+ && prefixVarId == other.prefixVarId;
+ }
+
+ /**
+ * Get the action string associated with this permission. The actions are
+ * returned in the following order: <code>read</code>, <code>reset</code>,
+ * <code>publish</code>, <code>startjob</code>, <code>switchevents</code>.
+ *
+ * @return the allowed actions separated by commas, cannot be
+ * <code>null</code>
+ */
+ public String getActions() {
+ StringBuffer sb = new StringBuffer();
+
+ appendAction(sb, READ_FLAG, READ);
+ appendAction(sb, RESET_FLAG, RESET);
+ appendAction(sb, PUBLISH_FLAG, PUBLISH);
+ appendAction(sb, STARTJOB_FLAG, STARTJOB);
+ appendAction(sb, SWITCHEVENTS_FLAG, SWITCHEVENTS);
+
+ return sb.toString();
+ }
+
+ private void appendAction(StringBuffer sb, int flag, String actionName) {
+ if ((mask & flag) != 0) {
+ if(sb.length() != 0)
+ sb.append(',');
+ sb.append(actionName);
+
+ if(flag == STARTJOB_FLAG && minJobInterval != 0)
+ sb.append(':').append(minJobInterval);
+ }
+ }
+
+ /**
+ * Determines if the specified permission is implied by this permission.
+ * <p>
+ * This method returns <code>false</code> if and only if at least one of the
+ * following conditions are fulfilled for the specified permission:
+ * <ul>
+ * <li>it is not a <code>MonitorPermission</code>
+ * <li>it has a broader set of actions allowed than this one
+ * <li>it allows initiating time based monitoring jobs with a lower minimal
+ * sampling interval
+ * <li>the target set of <code>Monitorable</code>s is not the same nor a
+ * subset of the target set of <code>Monitorable</code>s of this permission
+ * <li>the target set of <code>StatusVariable</code>s is not the same
+ * nor a subset of the target set of <code>StatusVariable</code>s of this
+ * permission
+ * </ul>
+ *
+ * @param p the permission to be checked
+ * @return <code>true</code> if the given permission is implied by this
+ * permission
+ */
+ public boolean implies(Permission p) {
+ if (!(p instanceof MonitorPermission))
+ return false;
+
+ MonitorPermission other = (MonitorPermission) p;
+
+ if ((mask & other.mask) != other.mask)
+ return false;
+
+ if ((other.mask & STARTJOB_FLAG) != 0
+ && minJobInterval > other.minJobInterval)
+ return false;
+
+ return implies(monId, prefixMonId, other.monId, other.prefixMonId)
+ && implies(varId, prefixVarId, other.varId, other.prefixVarId);
+ }
+
+ private boolean implies(String id, boolean prefix, String oid,
+ boolean oprefix) {
+
+ return prefix ? oid.startsWith(id) : !oprefix && id.equals(oid);
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/Monitorable.java b/deploymentadmin/src/main/java/org/osgi/service/monitor/Monitorable.java
new file mode 100644
index 0000000..8203ddd
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/Monitorable.java
@@ -0,0 +1,140 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.monitor/src/org/osgi/service/monitor/Monitorable.java,v 1.17 2006/06/16 16:31:25 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.monitor;
+
+/**
+ * A <code>Monitorable</code> can provide information about itself in the form
+ * of <code>StatusVariables</code>. Instances of this interface should
+ * register themselves at the OSGi Service Registry. The
+ * <code>MonitorAdmin</code> listens to the registration of
+ * <code>Monitorable</code> services, and makes the information they provide
+ * available also through the Device Management Tree (DMT) for remote access.
+ * <p>
+ * The monitorable service is identified by its PID string which must be a non-
+ * <code>null</code>, non-empty string that conforms to the "symbolic-name"
+ * definition in the OSGi core specification. This means that only the
+ * characters [-_.a-zA-Z0-9] may be used. The length of the PID must not exceed
+ * 20 characters.
+ * <p>
+ * A <code>Monitorable</code> may optionally support sending notifications
+ * when the status of its <code>StatusVariables</code> change. Support for
+ * change notifications can be defined per <code>StatusVariable</code>.
+ * <p>
+ * Publishing <code>StatusVariables</code> requires the presence of the
+ * <code>MonitorPermission</code> with the <code>publish</code> action
+ * string. This permission, however, is not checked during registration of the
+ * <code>Monitorable</code> service. Instead, the <code>MonitorAdmin</code>
+ * implemenatation must make sure that when a <code>StatusVariable</code> is
+ * queried, it is shown only if the <code>Monitorable</code> is authorized to
+ * publish the given <code>StatusVariable</code>.
+ */
+public interface Monitorable {
+ /**
+ * Returns the list of <code>StatusVariable</code> identifiers published
+ * by this <code>Monitorable</code>. A <code>StatusVariable</code> name
+ * is unique within the scope of a <code>Monitorable</code>. The array
+ * contains the elements in no particular order. The returned value must not
+ * be <code>null</code>.
+ *
+ * @return the <code>StatusVariable<code> identifiers published by this
+ * object, or an empty array if none are published
+ */
+ public String[] getStatusVariableNames();
+
+ /**
+ * Returns the <code>StatusVariable</code> object addressed by its
+ * identifier. The <code>StatusVariable</code> will hold the value taken
+ * at the time of this method call.
+ * <p>
+ * The given identifier does not contain the Monitorable PID, i.e. it
+ * specifies the name and not the path of the Status Variable.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>, cannot be
+ * <code>null</code>
+ * @return the <code>StatusVariable</code> object
+ * @throws java.lang.IllegalArgumentException if <code>id</code> points to a
+ * non-existing <code>StatusVariable</code>
+ */
+ public StatusVariable getStatusVariable(String id)
+ throws IllegalArgumentException;
+
+ /**
+ * Tells whether the <code>StatusVariable</code> provider is able to send
+ * instant notifications when the given <code>StatusVariable</code>
+ * changes. If the <code>Monitorable</code> supports sending change
+ * updates it must notify the <code>MonitorListener</code> when the value
+ * of the <code>StatusVariable</code> changes. The
+ * <code>Monitorable</code> finds the <code>MonitorListener</code>
+ * service through the Service Registry.
+ * <p>
+ * The given identifier does not contain the Monitorable PID, i.e. it
+ * specifies the name and not the path of the Status Variable.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>, cannot be
+ * <code>null</code>
+ * @return <code>true</code> if the <code>Monitorable</code> can send
+ * notification when the given <code>StatusVariable</code>
+ * changes, <code>false</code> otherwise
+ * @throws java.lang.IllegalArgumentException if <code>id</code> points to a
+ * non-existing <code>StatusVariable</code>
+ */
+ public boolean notifiesOnChange(String id) throws IllegalArgumentException;
+
+ /**
+ * Issues a request to reset a given <code>StatusVariable</code>.
+ * Depending on the semantics of the actual Status Variable this call may or
+ * may not succeed: it makes sense to reset a counter to its starting value,
+ * but for example a <code>StatusVariable</code> of type <code>String</code>
+ * might not have a meaningful default value. Note that for numeric
+ * <code>StatusVariables</code> the starting value may not necessarily be
+ * 0. Resetting a <code>StatusVariable</code> must trigger a monitor event.
+ * <p>
+ * The given identifier does not contain the Monitorable PID, i.e. it
+ * specifies the name and not the path of the Status Variable.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>, cannot be
+ * <code>null</code>
+ * @return <code>true</code> if the <code>Monitorable</code> could
+ * successfully reset the given <code>StatusVariable</code>,
+ * <code>false</code> otherwise
+ * @throws java.lang.IllegalArgumentException if <code>id</code> points to a
+ * non-existing <code>StatusVariable</code>
+ */
+ public boolean resetStatusVariable(String id)
+ throws IllegalArgumentException;
+
+ /**
+ * Returns a human readable description of a <code>StatusVariable</code>.
+ * This can be used by management systems on their GUI. The
+ * <code>null</code> return value is allowed if there is no description for
+ * the specified Status Variable.
+ * <p>
+ * The given identifier does not contain the Monitorable PID, i.e. it
+ * specifies the name and not the path of the Status Variable.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>, cannot be
+ * <code>null</code>
+ * @return the human readable description of this
+ * <code>StatusVariable</code> or <code>null</code> if it is not
+ * set
+ * @throws java.lang.IllegalArgumentException if <code>id</code> points to a
+ * non-existing <code>StatusVariable</code>
+ */
+ public String getDescription(String id) throws IllegalArgumentException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitoringJob.java b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitoringJob.java
new file mode 100644
index 0000000..75b9d2e
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/MonitoringJob.java
@@ -0,0 +1,126 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.monitor/src/org/osgi/service/monitor/MonitoringJob.java,v 1.14 2006/06/16 16:31:25 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.monitor;
+
+/**
+ * A Monitoring Job is a request for scheduled or event based notifications on
+ * update of a set of <code>StatusVariable</code>s. The job is a data
+ * structure that holds a non-empty list of <code>StatusVariable</code> names,
+ * an identification of the initiator of the job, and the sampling parameters.
+ * There are two kinds of monitoring jobs: time based and change based. Time
+ * based jobs take samples of all <code>StatusVariable</code>s with a
+ * specified frequency. The number of samples to be taken before the job
+ * finishes may be specified. Change based jobs are only interested in the
+ * changes of the monitored <code>StatusVariable</code>s. In this case, the
+ * number of changes that must take place between two notifications can be
+ * specified.
+ * <p>
+ * The job can be started on the <code>MonitorAdmin</code> interface. Running
+ * the job (querying the <code>StatusVariable</code>s, listening to changes,
+ * and sending out notifications on updates) is the task of the
+ * <code>MonitorAdmin</code> implementation.
+ * <p>
+ * Whether a monitoring job keeps track dynamically of the
+ * <code>StatusVariable</code>s it monitors is not specified. This means that
+ * if we monitor a <code>StatusVariable</code> of a <code>Monitorable</code>
+ * service which disappears and later reappears then it is implementation
+ * specific whether we still receive updates of the <code>StatusVariable</code>
+ * changes or not.
+ */
+public interface MonitoringJob {
+ /**
+ * Stops a Monitoring Job. Note that a time based job can also stop
+ * automatically if the specified number of samples have been taken.
+ */
+ public void stop();
+
+ /**
+ * Returns the identitifier of the principal who initiated the job. This is
+ * set at the time when
+ * {@link MonitorAdmin#startJob MonitorAdmin.startJob()} method is called.
+ * This string holds the ServerID if the operation was initiated from a
+ * remote manager, or an arbitrary ID of the initiator entity in the local
+ * case (used for addressing notification events).
+ *
+ * @return the ID of the initiator, cannot be <code>null</code>
+ */
+ public String getInitiator();
+
+ /**
+ * Returns the list of <code>StatusVariable</code> names that are the
+ * targets of this measurement job. For time based jobs, the
+ * <code>MonitorAdmin</code> will iterate through this list and query all
+ * <code>StatusVariable</code>s when its timer set by the job's frequency
+ * rate expires.
+ *
+ * @return the target list of the measurement job in
+ * [Monitorable_ID]/[StatusVariable_ID] format, cannot be
+ * <code>null</code>
+ */
+ public String[] getStatusVariableNames();
+
+ /**
+ * Returns the delay (in seconds) between two samples. If this call returns
+ * N (greater than 0) then the <code>MonitorAdmin</code> queries each
+ * <code>StatusVariable</code> that belongs to this job every N seconds.
+ * The value 0 means that the job is not scheduled but event based: in this
+ * case instant notification on changes is requested (at every nth change of
+ * the value, as specified by the report count parameter).
+ *
+ * @return the delay (in seconds) between samples, or 0 for change based
+ * jobs
+ */
+ public int getSchedule();
+
+ /**
+ * Returns the number of times <code>MonitorAdmin</code> will query the
+ * <code>StatusVariable</code>s (for time based jobs), or the number of
+ * changes of a <code>StatusVariable</code> between notifications (for
+ * change based jobs). Time based jobs with non-zero report count will take
+ * <code>getReportCount()</code>*<code>getSchedule()</code> time to
+ * finish. Time based jobs with 0 report count and change based jobs do not
+ * stop automatically, but all jobs can be stopped with the {@link #stop}
+ * method.
+ *
+ * @return the number of measurements to be taken, or the number of changes
+ * between notifications
+ */
+ public int getReportCount();
+
+ /**
+ * Returns whether the job was started locally or remotely. Jobs started by
+ * the clients of this API are always local, remote jobs can only be started
+ * using the Device Management Tree.
+ *
+ * @return <code>true</code> if the job was started from the local device,
+ * <code>false</code> if the job was initiated from a management
+ * server through the device management tree
+ */
+ public boolean isLocal();
+
+ /**
+ * Returns whether the job is running. A job is running until it is
+ * explicitely stopped, or, in case of time based jobs with a finite report
+ * count, until the given number of measurements have been made.
+ *
+ * @return <code>true</code> if the job is still running, <code>false</code>
+ * if it has finished
+ */
+ public boolean isRunning();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/StatusVariable.java b/deploymentadmin/src/main/java/org/osgi/service/monitor/StatusVariable.java
new file mode 100644
index 0000000..74ff9df
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/StatusVariable.java
@@ -0,0 +1,433 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.monitor/src/org/osgi/service/monitor/StatusVariable.java,v 1.14 2006/06/16 16:31:25 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.service.monitor;
+
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+
+/**
+ * A <code>StatusVariable</code> object represents the value of a status
+ * variable taken with a certain collection method at a certain point of time.
+ * The type of the <code>StatusVariable</code> can be <code>int</code>,
+ * <code>float</code>, <code>boolean</code> or <code>String</code>.
+ * <p>
+ * A <code>StatusVariable</code> is identified by an ID string that is unique
+ * within the scope of a <code>Monitorable</code>. The ID must be a non-
+ * <code>null</code>, non-empty string that conforms to the "symbolic-name"
+ * definition in the OSGi core specification. This means that only the
+ * characters [-_.a-zA-Z0-9] may be used. The length of the ID must not exceed
+ * 32 bytes when UTF-8 encoded.
+ */
+public final class StatusVariable {
+ //----- Public constants -----//
+ /**
+ * Constant for identifying <code>int</code> data type.
+ */
+ public static final int TYPE_INTEGER = 0;
+
+ /**
+ * Constant for identifying <code>float</code> data type.
+ */
+ public static final int TYPE_FLOAT = 1;
+
+ /**
+ * Constant for identifying <code>String</code> data type.
+ */
+ public static final int TYPE_STRING = 2;
+
+ /**
+ * Constant for identifying <code>boolean</code> data type.
+ */
+ public static final int TYPE_BOOLEAN = 3;
+
+ /**
+ * Constant for identifying 'Cumulative Counter' data collection method.
+ */
+ public static final int CM_CC = 0;
+
+ /**
+ * Constant for identifying 'Discrete Event Registration' data collection
+ * method.
+ */
+ public static final int CM_DER = 1;
+
+ /**
+ * Constant for identifying 'Gauge' data collection method.
+ */
+ public static final int CM_GAUGE = 2;
+
+ /**
+ * Constant for identifying 'Status Inspection' data collection method.
+ */
+ public static final int CM_SI = 3;
+
+ //----- Package private constants -----//
+
+ static final String SYMBOLIC_NAME_CHARACTERS =
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789" +
+ "-_."; // a subset of the characters allowed in DMT URIs
+
+ static final int MAX_ID_LENGTH = 32;
+
+ //----- Private fields -----//
+ private String id;
+ private Date timeStamp;
+ private int cm;
+ private int type;
+
+ private int intData;
+ private float floatData;
+ private String stringData;
+ private boolean booleanData;
+
+
+ //----- Constructors -----//
+ /**
+ * Constructor for a <code>StatusVariable</code> of <code>int</code>
+ * type.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>
+ * @param cm the collection method, one of the <code>CM_</code> constants
+ * @param data the <code>int</code> value of the
+ * <code>StatusVariable</code>
+ * @throws java.lang.IllegalArgumentException if the given <code>id</code>
+ * is not a valid <code>StatusVariable</code> name, or if
+ * <code>cm</code> is not one of the collection method constants
+ * @throws java.lang.NullPointerException if the <code>id</code>
+ * parameter is <code>null</code>
+ */
+ public StatusVariable(String id, int cm, int data) {
+ setCommon(id, cm);
+ type = TYPE_INTEGER;
+ intData = data;
+ }
+
+ /**
+ * Constructor for a <code>StatusVariable</code> of <code>float</code>
+ * type.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>
+ * @param cm the collection method, one of the <code>CM_</code> constants
+ * @param data the <code>float</code> value of the
+ * <code>StatusVariable</code>
+ * @throws java.lang.IllegalArgumentException if the given <code>id</code>
+ * is not a valid <code>StatusVariable</code> name, or if
+ * <code>cm</code> is not one of the collection method constants
+ * @throws java.lang.NullPointerException if the <code>id</code> parameter
+ * is <code>null</code>
+ */
+ public StatusVariable(String id, int cm, float data) {
+ setCommon(id, cm);
+ type = TYPE_FLOAT;
+ floatData = data;
+ }
+
+ /**
+ * Constructor for a <code>StatusVariable</code> of <code>boolean</code>
+ * type.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>
+ * @param cm the collection method, one of the <code>CM_</code> constants
+ * @param data the <code>boolean</code> value of the
+ * <code>StatusVariable</code>
+ * @throws java.lang.IllegalArgumentException if the given <code>id</code>
+ * is not a valid <code>StatusVariable</code> name, or if
+ * <code>cm</code> is not one of the collection method constants
+ * @throws java.lang.NullPointerException if the <code>id</code> parameter
+ * is <code>null</code>
+ */
+ public StatusVariable(String id, int cm, boolean data) {
+ setCommon(id, cm);
+ type = TYPE_BOOLEAN;
+ booleanData = data;
+ }
+
+ /**
+ * Constructor for a <code>StatusVariable</code> of <code>String</code>
+ * type.
+ *
+ * @param id the identifier of the <code>StatusVariable</code>
+ * @param cm the collection method, one of the <code>CM_</code> constants
+ * @param data the <code>String</code> value of the
+ * <code>StatusVariable</code>, can be <code>null</code>
+ * @throws java.lang.IllegalArgumentException if the given <code>id</code>
+ * is not a valid <code>StatusVariable</code> name, or if
+ * <code>cm</code> is not one of the collection method constants
+ * @throws java.lang.NullPointerException if the <code>id</code> parameter
+ * is <code>null</code>
+ */
+ public StatusVariable(String id, int cm, String data) {
+ setCommon(id, cm);
+ type = TYPE_STRING;
+ stringData = data;
+ }
+
+
+ // ----- Public methods -----//
+ /**
+ * Returns the ID of this <code>StatusVariable</code>. The ID is unique
+ * within the scope of a <code>Monitorable</code>.
+ *
+ * @return the ID of this <code>StatusVariable</code>
+ */
+ public String getID() {
+ return id;
+ }
+
+ /**
+ * Returns information on the data type of this <code>StatusVariable</code>.
+ *
+ * @return one of the <code>TYPE_</code> constants indicating the type of
+ * this <code>StatusVariable</code>
+ */
+ public int getType() {
+ return type;
+ }
+
+ /**
+ * Returns the timestamp associated with the <code>StatusVariable</code>.
+ * The timestamp is stored when the <code>StatusVariable</code> instance is
+ * created, generally during the {@link Monitorable#getStatusVariable}
+ * method call.
+ *
+ * @return the time when the <code>StatusVariable</code> value was
+ * queried, cannot be <code>null</code>
+ *
+ */
+ public Date getTimeStamp() {
+ return timeStamp;
+ }
+
+ /**
+ * Returns the <code>StatusVariable</code> value if its type is
+ * <code>String</code>.
+ *
+ * @return the <code>StatusVariable</code> value as a <code>String</code>
+ * @throws java.lang.IllegalStateException if the type of the
+ * <code>StatusVariable</code> is not <code>String</code>
+ */
+ public String getString() throws IllegalStateException {
+ if (type != TYPE_STRING)
+ throw new IllegalStateException(
+ "This StatusVariable does not contain a String value.");
+ return stringData;
+ }
+
+ /**
+ * Returns the <code>StatusVariable</code> value if its type is
+ * <code>int</code>.
+ *
+ * @return the <code>StatusVariable</code> value as an <code>int</code>
+ * @throws java.lang.IllegalStateException if the type of this
+ * <code>StatusVariable</code> is not <code>int</code>
+ */
+ public int getInteger() throws IllegalStateException {
+ if (type != TYPE_INTEGER)
+ throw new IllegalStateException(
+ "This StatusVariable does not contain an integer value.");
+ return intData;
+ }
+
+ /**
+ * Returns the <code>StatusVariable</code> value if its type is
+ * <code>float</code>.
+ *
+ * @return the <code>StatusVariable</code> value as a <code>float</code>
+ * @throws java.lang.IllegalStateException if the type of this
+ * <code>StatusVariable</code> is not <code>float</code>
+ */
+ public float getFloat() throws IllegalStateException {
+ if (type != TYPE_FLOAT)
+ throw new IllegalStateException(
+ "This StatusVariable does not contain a float value.");
+ return floatData;
+ }
+
+ /**
+ * Returns the <code>StatusVariable</code> value if its type is
+ * <code>boolean</code>.
+ *
+ * @return the <code>StatusVariable</code> value as a <code>boolean</code>
+ * @throws java.lang.IllegalStateException if the type of this
+ * <code>StatusVariable</code> is not <code>boolean</code>
+ */
+ public boolean getBoolean() throws IllegalStateException {
+ if (type != TYPE_BOOLEAN)
+ throw new IllegalStateException(
+ "This StatusVariable does not contain a boolean value.");
+ return booleanData;
+ }
+
+ /**
+ * Returns the collection method of this <code>StatusVariable</code>. See
+ * section 3.3 b) in [ETSI TS 132 403]
+ *
+ * @return one of the <code>CM_</code> constants
+ */
+ public int getCollectionMethod() {
+ return cm;
+ }
+
+ /**
+ * Compares the specified object with this <code>StatusVariable</code>.
+ * Two <code>StatusVariable</code> objects are considered equal if their
+ * full path, collection method and type are identical, and the data
+ * (selected by their type) is equal.
+ *
+ * @param obj the object to compare with this <code>StatusVariable</code>
+ * @return <code>true</code> if the argument represents the same
+ * <code>StatusVariable</code> as this object
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof StatusVariable))
+ return false;
+
+ StatusVariable other = (StatusVariable) obj;
+
+ if (!equals(id, other.id) || cm != other.cm || type != other.type)
+ return false;
+
+ switch (type) {
+ case TYPE_INTEGER: return intData == other.intData;
+ case TYPE_FLOAT: return floatData == other.floatData;
+ case TYPE_STRING: return equals(stringData, other.stringData);
+ case TYPE_BOOLEAN: return booleanData == other.booleanData;
+ }
+
+ return false; // never reached
+ }
+
+ /**
+ * Returns the hash code value for this <code>StatusVariable</code>. The
+ * hash code is calculated based on the full path, collection method and
+ * value of the <code>StatusVariable</code>.
+ *
+ * @return the hash code of this object
+ */
+ public int hashCode() {
+ int hash = hashCode(id) ^ cm;
+
+ switch (type) {
+ case TYPE_INTEGER: return hash ^ intData;
+ case TYPE_FLOAT: return hash ^ hashCode(new Float(floatData));
+ case TYPE_BOOLEAN: return hash ^ hashCode(new Boolean(booleanData));
+ case TYPE_STRING: return hash ^ hashCode(stringData);
+ }
+
+ return 0; // never reached
+ }
+
+ // String representation: StatusVariable(path, cm, time, type, value)
+ /**
+ * Returns a <code>String</code> representation of this
+ * <code>StatusVariable</code>. The returned <code>String</code>
+ * contains the full path, collection method, timestamp, type and value
+ * parameters of the <code>StatusVariable</code> in the following format:
+ * <pre>StatusVariable(<path>, <cm>, <timestamp>, <type>, <value>)</pre>
+ * The collection method identifiers used in the string representation are
+ * "CC", "DER", "GAUGE" and "SI" (without the quotes). The format of the
+ * timestamp is defined by the <code>Date.toString</code> method, while the
+ * type is identified by one of the strings "INTEGER", "FLOAT", "STRING" and
+ * "BOOLEAN". The final field contains the string representation of the
+ * value of the status variable.
+ *
+ * @return the <code>String</code> representation of this
+ * <code>StatusVariable</code>
+ */
+ public String toString() {
+ String cmName = null;
+ switch (cm) {
+ case CM_CC: cmName = "CC"; break;
+ case CM_DER: cmName = "DER"; break;
+ case CM_GAUGE: cmName = "GAUGE"; break;
+ case CM_SI: cmName = "SI"; break;
+ }
+
+ String beg = "StatusVariable(" + id + ", " + cmName + ", "
+ + timeStamp + ", ";
+
+ switch (type) {
+ case TYPE_INTEGER: return beg + "INTEGER, " + intData + ")";
+ case TYPE_FLOAT: return beg + "FLOAT, " + floatData + ")";
+ case TYPE_STRING: return beg + "STRING, " + stringData + ")";
+ case TYPE_BOOLEAN: return beg + "BOOLEAN, " + booleanData + ")";
+ }
+
+ return null; // never reached
+ }
+
+ //----- Private methods -----//
+
+ private void setCommon(String id, int cm)
+ throws IllegalArgumentException, NullPointerException {
+ checkId(id, "StatusVariable ID");
+
+ if (cm != CM_CC && cm != CM_DER && cm != CM_GAUGE && cm != CM_SI)
+ throw new IllegalArgumentException(
+ "Unknown data collection method constant '" + cm + "'.");
+
+ this.id = id;
+ this.cm = cm;
+ timeStamp = new Date();
+ }
+
+
+ private boolean equals(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+
+ private int hashCode(Object o) {
+ return o == null ? 0 : o.hashCode();
+ }
+
+ private static void checkId(String id, String idName)
+ throws IllegalArgumentException, NullPointerException {
+ if (id == null)
+ throw new NullPointerException(idName + " is null.");
+ if(id.length() == 0)
+ throw new IllegalArgumentException(idName + " is empty.");
+
+ byte[] nameBytes;
+ try {
+ nameBytes = id.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // never happens, "UTF-8" must always be supported
+ throw new IllegalStateException(e.getMessage());
+ }
+ if(nameBytes.length > MAX_ID_LENGTH)
+ throw new IllegalArgumentException(idName + " is too long " +
+ "(over " + MAX_ID_LENGTH + " bytes in UTF-8 encoding).");
+
+ if(id.equals(".") || id.equals(".."))
+ throw new IllegalArgumentException(idName + " is invalid.");
+
+ if(!containsValidChars(id))
+ throw new IllegalArgumentException(idName +
+ " contains invalid characters.");
+ }
+
+ private static boolean containsValidChars(String name) {
+ char[] chars = name.toCharArray();
+ for(int i = 0; i < chars.length; i++)
+ if(SYMBOLIC_NAME_CHARACTERS.indexOf(chars[i]) == -1)
+ return false;
+
+ return true;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/package.html b/deploymentadmin/src/main/java/org/osgi/service/monitor/package.html
new file mode 100644
index 0000000..565f431
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.service.monitor/src/org/osgi/service/monitor/package.html,v 1.4 2006/07/12 21:07:13 hargrave Exp $ -->
+<BODY>
+<p>Monitor Admin Package Version 1.0.
+<p>Bundles wishing to use this package must list the package
+in the <TT>Import-Package</TT> header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.service.monitor; version=1.0
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/service/monitor/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/monitor/packageinfo
new file mode 100644
index 0000000..7c8de03
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/monitor/packageinfo
@@ -0,0 +1 @@
+version 1.0
diff --git a/deploymentadmin/src/main/java/org/osgi/service/prefs/BackingStoreException.java b/deploymentadmin/src/main/java/org/osgi/service/prefs/BackingStoreException.java
new file mode 100644
index 0000000..7a182d7
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/prefs/BackingStoreException.java
@@ -0,0 +1,83 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.prefs/src/org/osgi/service/prefs/BackingStoreException.java,v 1.12 2006/07/11 13:15:55 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.prefs;
+
+/**
+ * Thrown to indicate that a preferences operation could not complete because of
+ * a failure in the backing store, or a failure to contact the backing store.
+ *
+ * @version $Revision: 1.12 $
+ */
+public class BackingStoreException extends Exception {
+ static final long serialVersionUID = -1415637364122829574L;
+ /**
+ * Nested exception.
+ */
+ private final Throwable cause;
+
+ /**
+ * Constructs a <code>BackingStoreException</code> with the specified detail
+ * message.
+ *
+ * @param s The detail message.
+ */
+ public BackingStoreException(String s) {
+ super(s);
+ this.cause = null;
+ }
+
+ /**
+ * Constructs a <code>BackingStoreException</code> with the specified detail
+ * message.
+ *
+ * @param s The detail message.
+ * @param cause The cause of the exception. May be <code>null</code>.
+ * @since 1.1
+ */
+ public BackingStoreException(String s, Throwable cause) {
+ super(s);
+ this.cause = cause;
+ }
+
+ /**
+ * Returns the cause of this exception or <code>null</code> if no cause was
+ * specified when this exception was created.
+ *
+ * @return The cause of this exception or <code>null</code> if no cause was
+ * specified.
+ * @since 1.1
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * The cause of this exception can only be set when constructed.
+ *
+ * @param cause Cause of the exception.
+ * @return This object.
+ * @throws java.lang.IllegalStateException This method will always throw an
+ * <code>IllegalStateException</code> since the cause of this
+ * exception can only be set when constructed.
+ * @since 1.1
+ */
+ public Throwable initCause(Throwable cause) {
+ throw new IllegalStateException();
+ }
+
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/prefs/Preferences.java b/deploymentadmin/src/main/java/org/osgi/service/prefs/Preferences.java
new file mode 100644
index 0000000..c3a6cd0
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/prefs/Preferences.java
@@ -0,0 +1,702 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.prefs/src/org/osgi/service/prefs/Preferences.java,v 1.11 2006/07/11 00:54:04 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.prefs;
+
+/**
+ * A node in a hierarchical collection of preference data.
+ *
+ * <p>
+ * This interface allows applications to store and retrieve user and system
+ * preference data. This data is stored persistently in an
+ * implementation-dependent backing store. Typical implementations include flat
+ * files, OS-specific registries, directory servers and SQL databases.
+ *
+ * <p>
+ * For each bundle, there is a separate tree of nodes for each user, and one for
+ * system preferences. The precise description of "user" and "system" will vary
+ * from one bundle to another. Typical information stored in the user preference
+ * tree might include font choice, and color choice for a bundle which interacts
+ * with the user via a servlet. Typical information stored in the system
+ * preference tree might include installation data, or things like high score
+ * information for a game program.
+ *
+ * <p>
+ * Nodes in a preference tree are named in a similar fashion to directories in a
+ * hierarchical file system. Every node in a preference tree has a <i>node name
+ * </i> (which is not necessarily unique), a unique <i>absolute path name </i>,
+ * and a path name <i>relative </i> to each ancestor including itself.
+ *
+ * <p>
+ * The root node has a node name of the empty <code>String</code> object ("").
+ * Every other node has an arbitrary node name, specified at the time it is
+ * created. The only restrictions on this name are that it cannot be the empty
+ * string, and it cannot contain the slash character ('/').
+ *
+ * <p>
+ * The root node has an absolute path name of <code>"/"</code>. Children of the
+ * root node have absolute path names of <code>"/" + </code> <i><node name>
+ * </i>. All other nodes have absolute path names of <i><parent's absolute
+ * path name> </i> <code> + "/" + </code> <i><node name> </i>. Note that
+ * all absolute path names begin with the slash character.
+ *
+ * <p>
+ * A node <i>n </i>'s path name relative to its ancestor <i>a </i> is simply the
+ * string that must be appended to <i>a </i>'s absolute path name in order to
+ * form <i>n </i>'s absolute path name, with the initial slash character (if
+ * present) removed. Note that:
+ * <ul>
+ * <li>No relative path names begin with the slash character.
+ * <li>Every node's path name relative to itself is the empty string.
+ * <li>Every node's path name relative to its parent is its node name (except
+ * for the root node, which does not have a parent).
+ * <li>Every node's path name relative to the root is its absolute path name
+ * with the initial slash character removed.
+ * </ul>
+ *
+ * <p>
+ * Note finally that:
+ * <ul>
+ * <li>No path name contains multiple consecutive slash characters.
+ * <li>No path name with the exception of the root's absolute path name end in
+ * the slash character.
+ * <li>Any string that conforms to these two rules is a valid path name.
+ * </ul>
+ *
+ * <p>
+ * Each <code>Preference</code> node has zero or more properties associated with
+ * it, where a property consists of a name and a value. The bundle writer is
+ * free to choose any appropriate names for properties. Their values can be of
+ * type <code>String</code>,<code>long</code>,<code>int</code>,<code>boolean</code>,
+ * <code>byte[]</code>,<code>float</code>, or <code>double</code> but they can
+ * always be accessed as if they were <code>String</code> objects.
+ *
+ * <p>
+ * All node name and property name comparisons are case-sensitive.
+ *
+ * <p>
+ * All of the methods that modify preference data are permitted to operate
+ * asynchronously; they may return immediately, and changes will eventually
+ * propagate to the persistent backing store, with an implementation-dependent
+ * delay. The <code>flush</code> method may be used to synchronously force updates
+ * to the backing store.
+ *
+ * <p>
+ * Implementations must automatically attempt to flush to the backing store any
+ * pending updates for a bundle's preferences when the bundle is stopped or
+ * otherwise ungets the Preferences Service.
+ *
+ * <p>
+ * The methods in this class may be invoked concurrently by multiple threads in
+ * a single Java Virtual Machine (JVM) without the need for external
+ * synchronization, and the results will be equivalent to some serial execution.
+ * If this class is used concurrently <i>by multiple JVMs </i> that store their
+ * preference data in the same backing store, the data store will not be
+ * corrupted, but no other guarantees are made concerning the consistency of the
+ * preference data.
+ *
+ *
+ * @version $Revision: 1.11 $
+ */
+public interface Preferences {
+ /**
+ * Associates the specified value with the specified key in this node.
+ *
+ * @param key key with which the specified value is to be associated.
+ * @param value value to be associated with the specified key.
+ * @throws NullPointerException if <code>key</code> or <code>value</code> is
+ * <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ */
+ public void put(String key, String value);
+
+ /**
+ * Returns the value associated with the specified <code>key</code> in this
+ * node. Returns the specified default if there is no value associated with
+ * the <code>key</code>, or the backing store is inaccessible.
+ *
+ * @param key key whose associated value is to be returned.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the backing store is
+ * inaccessible.
+ * @return the value associated with <code>key</code>, or <code>def</code> if
+ * no value is associated with <code>key</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>. (A
+ * <code>null</code> default <i>is </i> permitted.)
+ */
+ public String get(String key, String def);
+
+ /**
+ * Removes the value associated with the specified <code>key</code> in this
+ * node, if any.
+ *
+ * @param key key whose mapping is to be removed from this node.
+ * @see #get(String,String)
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ */
+ public void remove(String key);
+
+ /**
+ * Removes all of the properties (key-value associations) in this node. This
+ * call has no effect on any descendants of this node.
+ *
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #remove(String)
+ */
+ public void clear() throws BackingStoreException;
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>int</code> value with the specified <code>key</code> in this node. The
+ * associated string is the one that would be returned if the <code>int</code>
+ * value were passed to <code>Integer.toString(int)</code>. This method is
+ * intended for use in conjunction with {@link #getInt} method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the property value
+ * be represented by a <code>String</code> object in the backing store. If the
+ * backing store supports integer values, it is not unreasonable to use
+ * them. This implementation detail is not visible through the
+ * <code>Preferences</code> API, which allows the value to be read as an
+ * <code>int</code> (with <code>getInt</code> or a <code>String</code> (with
+ * <code>get</code>) type.
+ *
+ * @param key key with which the string form of value is to be associated.
+ * @param value <code>value</code> whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #getInt(String,int)
+ */
+ public void putInt(String key, int value);
+
+ /**
+ * Returns the <code>int</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to an <code>int</code> as by
+ * <code>Integer.parseInt(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Integer.parseInt(String)</code> would throw a
+ * <code>NumberFormatException</code> if the associated <code>value</code> were
+ * passed. This method is intended for use in conjunction with the
+ * {@link #putInt} method.
+ *
+ * @param key key whose associated value is to be returned as an
+ * <code>int</code>.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as an <code>int</code> or the backing store is
+ * inaccessible.
+ * @return the <code>int</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as an <code>int</code> type.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #putInt(String,int)
+ * @see #get(String,String)
+ */
+ public int getInt(String key, int def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>long</code> value with the specified <code>key</code> in this node. The
+ * associated <code>String</code> object is the one that would be returned if
+ * the <code>long</code> value were passed to <code>Long.toString(long)</code>.
+ * This method is intended for use in conjunction with the {@link #getLong}
+ * method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the <code>value</code>
+ * be represented by a <code>String</code> type in the backing store. If the
+ * backing store supports <code>long</code> values, it is not unreasonable to
+ * use them. This implementation detail is not visible through the <code>
+ * Preferences</code> API, which allows the value to be read as a
+ * <code>long</code> (with <code>getLong</code> or a <code>String</code> (with
+ * <code>get</code>) type.
+ *
+ * @param key <code>key</code> with which the string form of <code>value</code>
+ * is to be associated.
+ * @param value <code>value</code> whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #getLong(String,long)
+ */
+ public void putLong(String key, long value);
+
+ /**
+ * Returns the <code>long</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to a <code>long</code> as by
+ * <code>Long.parseLong(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Long.parseLong(String)</code> would throw a
+ * <code>NumberFormatException</code> if the associated <code>value</code> were
+ * passed. This method is intended for use in conjunction with the
+ * {@link #putLong} method.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>long</code> value.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>long</code> type or the backing
+ * store is inaccessible.
+ * @return the <code>long</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>long</code> type.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #putLong(String,long)
+ * @see #get(String,String)
+ */
+ public long getLong(String key, long def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>boolean</code> value with the specified key in this node. The
+ * associated string is "true" if the value is <code>true</code>, and "false"
+ * if it is <code>false</code>. This method is intended for use in
+ * conjunction with the {@link #getBoolean} method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a string in the backing store. If the backing store
+ * supports <code>boolean</code> values, it is not unreasonable to use them.
+ * This implementation detail is not visible through the <code>Preferences
+ * </code> API, which allows the value to be read as a <code>boolean</code>
+ * (with <code>getBoolean</code>) or a <code>String</code> (with <code>get</code>)
+ * type.
+ *
+ * @param key <code>key</code> with which the string form of value is to be
+ * associated.
+ * @param value value whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #getBoolean(String,boolean)
+ * @see #get(String,String)
+ */
+ public void putBoolean(String key, boolean value);
+
+ /**
+ * Returns the <code>boolean</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. Valid
+ * strings are "true", which represents <code>true</code>, and "false", which
+ * represents <code>false</code>. Case is ignored, so, for example, "TRUE"
+ * and "False" are also valid. This method is intended for use in
+ * conjunction with the {@link #putBoolean} method.
+ *
+ * <p>
+ * Returns the specified default if there is no value associated with the
+ * <code>key</code>, the backing store is inaccessible, or if the associated
+ * value is something other than "true" or "false", ignoring case.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>boolean</code>.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>boolean</code> or the backing store
+ * is inaccessible.
+ * @return the <code>boolean</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>null</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>boolean</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #get(String,String)
+ * @see #putBoolean(String,boolean)
+ */
+ public boolean getBoolean(String key, boolean def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>float</code> value with the specified <code>key</code> in this node.
+ * The associated <code>String</code> object is the one that would be returned
+ * if the <code>float</code> value were passed to
+ * <code>Float.toString(float)</code>. This method is intended for use in
+ * conjunction with the {@link #getFloat} method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a string in the backing store. If the backing store
+ * supports <code>float</code> values, it is not unreasonable to use them.
+ * This implementation detail is not visible through the <code>Preferences
+ * </code> API, which allows the value to be read as a <code>float</code> (with
+ * <code>getFloat</code>) or a <code>String</code> (with <code>get</code>) type.
+ *
+ * @param key <code>key</code> with which the string form of value is to be
+ * associated.
+ * @param value value whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #getFloat(String,float)
+ */
+ public void putFloat(String key, float value);
+
+ /**
+ * Returns the float <code>value</code> represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to a <code>float</code> value as by
+ * <code>Float.parseFloat(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Float.parseFloat(String)</code> would throw a
+ * <code>NumberFormatException</code> if the associated value were passed.
+ * This method is intended for use in conjunction with the {@link #putFloat}
+ * method.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>float</code> value.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>float</code> type or the backing
+ * store is inaccessible.
+ * @return the <code>float</code> value represented by the string associated
+ * with <code>key</code> in this node, or <code>def</code> if the
+ * associated value does not exist or cannot be interpreted as a
+ * <code>float</code> type.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @see #putFloat(String,float)
+ * @see #get(String,String)
+ */
+ public float getFloat(String key, float def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>double</code> value with the specified <code>key</code> in this node.
+ * The associated <code>String</code> object is the one that would be returned
+ * if the <code>double</code> value were passed to
+ * <code>Double.toString(double)</code>. This method is intended for use in
+ * conjunction with the {@link #getDouble} method
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a string in the backing store. If the backing store
+ * supports <code>double</code> values, it is not unreasonable to use them.
+ * This implementation detail is not visible through the <code>Preferences
+ * </code> API, which allows the value to be read as a <code>double</code> (with
+ * <code>getDouble</code>) or a <code>String</code> (with <code>get</code>)
+ * type.
+ *
+ * @param key <code>key</code> with which the string form of value is to be
+ * associated.
+ * @param value value whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #getDouble(String,double)
+ */
+ public void putDouble(String key, double value);
+
+ /**
+ * Returns the <code>double</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. The
+ * <code>String</code> object is converted to a <code>double</code> value as by
+ * <code>Double.parseDouble(String)</code>. Returns the specified default if
+ * there is no value associated with the <code>key</code>, the backing store
+ * is inaccessible, or if <code>Double.parseDouble(String)</code> would throw
+ * a <code>NumberFormatException</code> if the associated value were passed.
+ * This method is intended for use in conjunction with the
+ * {@link #putDouble} method.
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>double</code> value.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>double</code> type or the backing
+ * store is inaccessible.
+ * @return the <code>double</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>double</code> type.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the the {@link #removeNode()} method.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>.
+ * @see #putDouble(String,double)
+ * @see #get(String,String)
+ */
+ public double getDouble(String key, double def);
+
+ /**
+ * Associates a <code>String</code> object representing the specified
+ * <code>byte[]</code> with the specified <code>key</code> in this node. The
+ * associated <code>String</code> object the <i>Base64 </i> encoding of the
+ * <code>byte[]</code>, as defined in <a
+ * href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 </a>, Section 6.8,
+ * with one minor change: the string will consist solely of characters from
+ * the <i>Base64 Alphabet </i>; it will not contain any newline characters.
+ * This method is intended for use in conjunction with the
+ * {@link #getByteArray} method.
+ *
+ * <p>
+ * Implementor's note: it is <i>not </i> necessary that the value be
+ * represented by a <code>String</code> type in the backing store. If the
+ * backing store supports <code>byte[]</code> values, it is not unreasonable
+ * to use them. This implementation detail is not visible through the <code>
+ * Preferences</code> API, which allows the value to be read as an a
+ * <code>byte[]</code> object (with <code>getByteArray</code>) or a
+ * <code>String</code> object (with <code>get</code>).
+ *
+ * @param key <code>key</code> with which the string form of <code>value</code>
+ * is to be associated.
+ * @param value <code>value</code> whose string form is to be associated with
+ * <code>key</code>.
+ * @throws NullPointerException if <code>key</code> or <code>value</code> is
+ * <code>null</code>.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #getByteArray(String,byte[])
+ * @see #get(String,String)
+ */
+ public void putByteArray(String key, byte[] value);
+
+ /**
+ * Returns the <code>byte[]</code> value represented by the <code>String</code>
+ * object associated with the specified <code>key</code> in this node. Valid
+ * <code>String</code> objects are <i>Base64 </i> encoded binary data, as
+ * defined in <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 </a>,
+ * Section 6.8, with one minor change: the string must consist solely of
+ * characters from the <i>Base64 Alphabet </i>; no newline characters or
+ * extraneous characters are permitted. This method is intended for use in
+ * conjunction with the {@link #putByteArray} method.
+ *
+ * <p>
+ * Returns the specified default if there is no value associated with the
+ * <code>key</code>, the backing store is inaccessible, or if the associated
+ * value is not a valid Base64 encoded byte array (as defined above).
+ *
+ * @param key <code>key</code> whose associated value is to be returned as a
+ * <code>byte[]</code> object.
+ * @param def the value to be returned in the event that this node has no
+ * value associated with <code>key</code> or the associated value
+ * cannot be interpreted as a <code>byte[]</code> type, or the backing
+ * store is inaccessible.
+ * @return the <code>byte[]</code> value represented by the <code>String</code>
+ * object associated with <code>key</code> in this node, or
+ * <code>def</code> if the associated value does not exist or cannot
+ * be interpreted as a <code>byte[]</code>.
+ * @throws NullPointerException if <code>key</code> is <code>null</code>. (A
+ * <code>null</code> value for <code>def</code> <i>is </i> permitted.)
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #get(String,String)
+ * @see #putByteArray(String,byte[])
+ */
+ public byte[] getByteArray(String key, byte[] def);
+
+ /**
+ * Returns all of the keys that have an associated value in this node. (The
+ * returned array will be of size zero if this node has no preferences and
+ * not <code>null</code>!)
+ *
+ * @return an array of the keys that have an associated value in this node.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ */
+ public String[] keys() throws BackingStoreException;
+
+ /**
+ * Returns the names of the children of this node. (The returned array will
+ * be of size zero if this node has no children and not <code>null</code>!)
+ *
+ * @return the names of the children of this node.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ */
+ public String[] childrenNames() throws BackingStoreException;
+
+ /**
+ * Returns the parent of this node, or <code>null</code> if this is the root.
+ *
+ * @return the parent of this node.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ */
+ public Preferences parent();
+
+ /**
+ * Returns a named <code>Preferences</code> object (node), creating it and any
+ * of its ancestors if they do not already exist. Accepts a relative or
+ * absolute pathname. Absolute pathnames (which begin with <code>'/'</code>)
+ * are interpreted relative to the root of this node. Relative pathnames
+ * (which begin with any character other than <code>'/'</code>) are
+ * interpreted relative to this node itself. The empty string (<code>""</code>)
+ * is a valid relative pathname, referring to this node itself.
+ *
+ * <p>
+ * If the returned node did not exist prior to this call, this node and any
+ * ancestors that were created by this call are not guaranteed to become
+ * persistent until the <code>flush</code> method is called on the returned
+ * node (or one of its descendants).
+ *
+ * @param pathName the path name of the <code>Preferences</code> object to
+ * return.
+ * @return the specified <code>Preferences</code> object.
+ * @throws IllegalArgumentException if the path name is invalid.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @throws NullPointerException if path name is <code>null</code>.
+ * @see #flush()
+ */
+ public Preferences node(String pathName);
+
+ /**
+ * Returns true if the named node exists. Accepts a relative or absolute
+ * pathname. Absolute pathnames (which begin with <code>'/'</code>) are
+ * interpreted relative to the root of this node. Relative pathnames (which
+ * begin with any character other than <code>'/'</code>) are interpreted
+ * relative to this node itself. The pathname <code>""</code> is valid, and
+ * refers to this node itself.
+ *
+ * <p>
+ * If this node (or an ancestor) has already been removed with the
+ * {@link #removeNode()} method, it <i>is </i> legal to invoke this method,
+ * but only with the pathname <code>""</code>; the invocation will return
+ * <code>false</code>. Thus, the idiom <code>p.nodeExists("")</code> may be
+ * used to test whether <code>p</code> has been removed.
+ *
+ * @param pathName the path name of the node whose existence is to be
+ * checked.
+ * @return true if the specified node exists.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method and
+ * <code>pathname</code> is not the empty string (<code>""</code>).
+ * @throws IllegalArgumentException if the path name is invalid (i.e., it
+ * contains multiple consecutive slash characters, or ends with a
+ * slash character and is more than one character long).
+ */
+ public boolean nodeExists(String pathName)
+ throws BackingStoreException;
+
+ /**
+ * Removes this node and all of its descendants, invalidating any properties
+ * contained in the removed nodes. Once a node has been removed, attempting
+ * any method other than <code>name()</code>,<code>absolutePath()</code> or
+ * <code>nodeExists("")</code> on the corresponding <code>Preferences</code>
+ * instance will fail with an <code>IllegalStateException</code>. (The
+ * methods defined on <code>Object</code> can still be invoked on a node after
+ * it has been removed; they will not throw <code>IllegalStateException</code>.)
+ *
+ * <p>
+ * The removal is not guaranteed to be persistent until the <code>flush</code>
+ * method is called on the parent of this node.
+ *
+ * @throws IllegalStateException if this node (or an ancestor) has already
+ * been removed with the {@link #removeNode()} method.
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @see #flush()
+ */
+ public void removeNode() throws BackingStoreException;
+
+ /**
+ * Returns this node's name, relative to its parent.
+ *
+ * @return this node's name, relative to its parent.
+ */
+ public String name();
+
+ /**
+ * Returns this node's absolute path name. Note that:
+ * <ul>
+ * <li>Root node - The path name of the root node is <code>"/"</code>.
+ * <li>Slash at end - Path names other than that of the root node may not
+ * end in slash (<code>'/'</code>).
+ * <li>Unusual names -<code>"."</code> and <code>".."</code> have <i>no </i>
+ * special significance in path names.
+ * <li>Illegal names - The only illegal path names are those that contain
+ * multiple consecutive slashes, or that end in slash and are not the root.
+ * </ul>
+ *
+ * @return this node's absolute path name.
+ */
+ public String absolutePath();
+
+ /**
+ * Forces any changes in the contents of this node and its descendants to
+ * the persistent store.
+ *
+ * <p>
+ * Once this method returns successfully, it is safe to assume that all
+ * changes made in the subtree rooted at this node prior to the method
+ * invocation have become permanent.
+ *
+ * <p>
+ * Implementations are free to flush changes into the persistent store at
+ * any time. They do not need to wait for this method to be called.
+ *
+ * <p>
+ * When a flush occurs on a newly created node, it is made persistent, as
+ * are any ancestors (and descendants) that have yet to be made persistent.
+ * Note however that any properties value changes in ancestors are <i>not
+ * </i> guaranteed to be made persistent.
+ *
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #sync()
+ */
+ public void flush() throws BackingStoreException;
+
+ /**
+ * Ensures that future reads from this node and its descendants reflect any
+ * changes that were committed to the persistent store (from any VM) prior
+ * to the <code>sync</code> invocation. As a side-effect, forces any changes
+ * in the contents of this node and its descendants to the persistent store,
+ * as if the <code>flush</code> method had been invoked on this node.
+ *
+ * @throws BackingStoreException if this operation cannot be completed due
+ * to a failure in the backing store, or inability to communicate
+ * with it.
+ * @throws IllegalStateException if this node (or an ancestor) has been
+ * removed with the {@link #removeNode()} method.
+ * @see #flush()
+ */
+ public void sync() throws BackingStoreException;
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/prefs/PreferencesService.java b/deploymentadmin/src/main/java/org/osgi/service/prefs/PreferencesService.java
new file mode 100644
index 0000000..19c1e34
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/prefs/PreferencesService.java
@@ -0,0 +1,56 @@
+/*
+ * $Header: /cvshome/build/org.osgi.service.prefs/src/org/osgi/service/prefs/PreferencesService.java,v 1.10 2006/06/16 16:31:30 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2001, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.service.prefs;
+
+/**
+ * The Preferences Service.
+ *
+ * <p>
+ * Each bundle using this service has its own set of preference trees: one for
+ * system preferences, and one for each user.
+ *
+ * <p>
+ * A <code>PreferencesService</code> object is specific to the bundle which
+ * obtained it from the service registry. If a bundle wishes to allow another
+ * bundle to access its preferences, it should pass its
+ * <code>PreferencesService</code> object to that bundle.
+ *
+ */
+public interface PreferencesService {
+ /**
+ * Returns the root system node for the calling bundle.
+ *
+ * @return The root system node for the calling bundle.
+ */
+ public Preferences getSystemPreferences();
+
+ /**
+ * Returns the root node for the specified user and the calling bundle.
+ *
+ * @param name The user for which to return the preference root node.
+ * @return The root node for the specified user and the calling bundle.
+ */
+ public Preferences getUserPreferences(String name);
+
+ /**
+ * Returns the names of users for which node trees exist.
+ *
+ * @return The names of users for which node trees exist.
+ */
+ public String[] getUsers();
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/service/prefs/package.html b/deploymentadmin/src/main/java/org/osgi/service/prefs/package.html
new file mode 100644
index 0000000..b67e70a
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/prefs/package.html
@@ -0,0 +1 @@
+<!-- $Header: /cvshome/build/org.osgi.service.prefs/src/org/osgi/service/prefs/package.html,v 1.4 2006/07/12 21:06:58 hargrave Exp $ -->
<BODY>
<p>Preferences Service Package Version 1.1.
<p>Bundles wishing to use this package must list the package
in the Import-Package header of the bundle's manifest.
For example:
<pre>
Import-Package: org.osgi.service.prefs; version=1.1
</pre>
</BODY>
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/service/prefs/packageinfo b/deploymentadmin/src/main/java/org/osgi/service/prefs/packageinfo
new file mode 100644
index 0000000..3987f9c
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/service/prefs/packageinfo
@@ -0,0 +1 @@
+version 1.1
diff --git a/deploymentadmin/src/main/java/org/osgi/util/gsm/IMEICondition.java b/deploymentadmin/src/main/java/org/osgi/util/gsm/IMEICondition.java
new file mode 100644
index 0000000..b2f1955
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/gsm/IMEICondition.java
@@ -0,0 +1,85 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.gsm/src/org/osgi/util/gsm/IMEICondition.java,v 1.20 2006/07/12 21:21:32 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.util.gsm;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.Condition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+
+/**
+ * Class representing an IMEI condition. Instances of this class contain a
+ * string value that is matched against the IMEI of the device.
+ */
+public class IMEICondition {
+ private static final String ORG_OSGI_UTIL_GSM_IMEI = "org.osgi.util.gsm.imei";
+ private static final String imei ;
+
+ static {
+ imei = (String)
+ AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ return System.getProperty(ORG_OSGI_UTIL_GSM_IMEI);
+ }
+ }
+ );
+ }
+
+ private IMEICondition() {
+ }
+
+ /**
+ * Creates an IMEICondition object.
+ *
+ * @param bundle ignored, as the IMEI number is the property of the mobile device,
+ * and thus the same for all bundles.
+ * @param conditionInfo contains the IMEI value to match the device's IMEI against. Its
+ * {@link ConditionInfo#getArgs()} method should return a String array with one value, the
+ * IMEI string. The IMEI is 15 digits without hypens. Limited pattern matching is allowed,
+ * then the string is 0 to 14 digits, followed by an asterisk(<code>*</code>).
+ * @return An IMEICondition object, that can tell whether its IMEI number matches that of the device.
+ * If the number contains an asterisk(<code>*</code>), then the beginning
+ * of the imei is compared to the pattern.
+ * @throws NullPointerException if one of the parameters is <code>null</code>.
+ * @throws IllegalArgumentException if the IMEI is not a string of 15 digits, or
+ * 0 to 14 digits with an <code>*</code> at the end.
+ */
+ public static Condition getCondition(Bundle bundle, ConditionInfo conditionInfo) {
+ if (bundle==null) throw new NullPointerException("bundle");
+ String imei = conditionInfo.getArgs()[0];
+ if (imei.length()>15) throw new IllegalArgumentException("imei too long: "+imei);
+ if (imei.endsWith("*")) {
+ imei = imei.substring(0,imei.length()-1);
+ } else {
+ if (imei.length()!=15) throw new IllegalArgumentException("not a valid imei: "+imei);
+ }
+ for(int i=0;i<imei.length();i++) {
+ int c = imei.charAt(i);
+ if (c<'0'||c>'9') throw new IllegalArgumentException("not a valid imei: "+imei);
+ }
+ if (IMEICondition.imei==null) {
+ System.err.println("The OSGI Reference Implementation of org.osgi.util.gsm.IMEICondition ");
+ System.err.println("needs the system property "+ORG_OSGI_UTIL_GSM_IMEI+" set.");
+ return Condition.FALSE;
+ }
+ return IMEICondition.imei.startsWith(imei)?Condition.TRUE:Condition.FALSE;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/util/gsm/IMSICondition.java b/deploymentadmin/src/main/java/org/osgi/util/gsm/IMSICondition.java
new file mode 100644
index 0000000..42de781
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/gsm/IMSICondition.java
@@ -0,0 +1,84 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.gsm/src/org/osgi/util/gsm/IMSICondition.java,v 1.22 2006/07/12 21:21:32 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.util.gsm;
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.Condition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+
+/**
+ * Class representing an IMSI condition. Instances of this class contain a
+ * string value that is matched against the IMSI of the subscriber.
+ */
+public class IMSICondition {
+ private static final String ORG_OSGI_UTIL_GSM_IMSI = "org.osgi.util.gsm.imsi";
+ private static final String imsi;
+
+ static {
+ imsi = (String)
+ AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Object run() {
+ return System.getProperty(ORG_OSGI_UTIL_GSM_IMSI);
+ }
+ }
+ );
+ }
+
+ private IMSICondition() {}
+
+ /**
+ * Creates an IMSI condition object.
+ *
+ * @param bundle ignored, as the IMSI number is the same for all bundles.
+ * @param conditionInfo contains the IMSI value to match the device's IMSI against. Its
+ * {@link ConditionInfo#getArgs()} method should return a String array with one value, the
+ * IMSI string. The IMSI is 15 digits without hypens. Limited pattern matching is allowed,
+ * then the string is 0 to 14 digits, followed by an asterisk(<code>*</code>).
+ * @return An IMSICondition object, that can tell whether its IMSI number matches that of the device.
+ * If the number contains an asterisk(<code>*</code>), then the beginning
+ * of the IMSI is compared to the pattern.
+ * @throws NullPointerException if one of the parameters is <code>null</code>.
+ * @throws IllegalArgumentException if the IMSI is not a string of 15 digits, or
+ * 0 to 14 digits with an <code>*</code> at the end.
+ */
+ public static Condition getCondition(Bundle bundle, ConditionInfo conditionInfo) {
+ if (bundle==null) throw new NullPointerException("bundle");
+ if (conditionInfo==null) throw new NullPointerException("conditionInfo");
+ String imsi = conditionInfo.getArgs()[0];
+ if (imsi.length()>15) throw new IllegalArgumentException("imsi too long: "+imsi);
+ if (imsi.endsWith("*")) {
+ imsi = imsi.substring(0,imsi.length()-1);
+ } else {
+ if (imsi.length()!=15) throw new IllegalArgumentException("not a valid imei: "+imsi);
+ }
+ for(int i=0;i<imsi.length();i++) {
+ int c = imsi.charAt(i);
+ if (c<'0'||c>'9') throw new IllegalArgumentException("not a valid imei: "+imsi);
+ }
+ if (IMSICondition.imsi==null) {
+ System.err.println("The OSGI Reference Implementation of org.osgi.util.gsm.IMSICondition ");
+ System.err.println("needs the system property "+ORG_OSGI_UTIL_GSM_IMSI+" set.");
+ return Condition.FALSE;
+ }
+ return (IMSICondition.imsi.startsWith(imsi))?Condition.TRUE:Condition.FALSE;
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/util/gsm/package.html b/deploymentadmin/src/main/java/org/osgi/util/gsm/package.html
new file mode 100644
index 0000000..9f0a7c7
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/gsm/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.util.gsm/src/org/osgi/util/gsm/package.html,v 1.2 2006/07/12 21:06:54 hargrave Exp $ -->
+<BODY>
+<p>Mobile GSM Conditions Package Version 1.0.
+<p>Bundles wishing to use this package must list the package
+in the Import-Package header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.util.gsm; version=1.0
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/util/gsm/packageinfo b/deploymentadmin/src/main/java/org/osgi/util/gsm/packageinfo
new file mode 100644
index 0000000..a4f1546
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/gsm/packageinfo
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/util/mobile/UserPromptCondition.java b/deploymentadmin/src/main/java/org/osgi/util/mobile/UserPromptCondition.java
new file mode 100644
index 0000000..423ba1a
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/mobile/UserPromptCondition.java
@@ -0,0 +1,256 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.mobile/src/org/osgi/util/mobile/UserPromptCondition.java,v 1.26 2006/07/10 08:18:30 pnagy Exp $
+ *
+ * Copyright (c) OSGi Alliance (2004, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.osgi.util.mobile;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Dictionary;
+
+import org.osgi.framework.Bundle;
+import org.osgi.service.condpermadmin.Condition;
+import org.osgi.service.condpermadmin.ConditionInfo;
+
+/**
+ * Class representing a user prompt condition. Instances of this class hold two
+ * values: a prompt string that is to be displayed to the user and the
+ * permission level string according to MIDP2.0 (oneshot, session, blanket).
+ *
+ */
+public class UserPromptCondition implements Condition {
+
+ /*
+ * NOTE: An implementor may also choose to replace this class in
+ * their distribution with a class that directly interfaces with the
+ * policy implementation. This replacement class MUST NOT alter the
+ * public/protected signature of this class.
+ */
+ // this will need to be set by the implementation class
+ static Method factory = null;
+ Condition realUserPromptCondition;
+
+ private final Bundle bundle;
+ private final String levels;
+ private final String defaultLevel;
+ private final String catalogName;
+ private final String message;
+
+ /**
+ * Returns a UserPromptCondition object with the given prompt string and permission
+ * level. The user should be given choice as to what level of permission is
+ * given. Thus, the lifetime of the permission is controlled by the user.
+ *
+ * @param bundle the bundle to ask about.
+ * @param conditionInfo the conditionInfo containing the construction information. Its
+ * {@link ConditionInfo#getArgs()} method should return a String array with 4
+ * strings in it:
+ * <ol start="0">
+ * <li>the possible permission levels. This is a comma-separated list that can contain
+ * following strings: ONESHOT SESSION BLANKET. The order is not important. This
+ * parameter is case-insensitive.
+ * </li>
+ * <li>the default permission level, one chosen from the possible permission levels. If
+ * it is an empty string, then there is no default. This parameter
+ * is case-insensitive.</li>
+ * <li>the message catalog base name. It will be loaded by a {@link java.util.ResourceBundle},
+ * or equivalent
+ * from an exporting OSGi Bundle. Thus, if the catalogName is "com.provider.messages.userprompt",
+ * then there should be an OSGi Bundle exporting the "com.provider.messages" package, and inside
+ * it files like "userprompt_en_US.properties".</li>
+ * <li>textual description of the condition, to be displayed to the user. If
+ * it starts with a '%' sign, then the message is looked up from the catalog specified previously.
+ * The key is the rest of the string after the '%' sign.</li>
+ * </ol>
+ * @return The requested UserPromptCondition.
+ * @throws IllegalArgumentException if the parameters are malformed.
+ * @throws NullPointerException if one of the parameters is <code>null</code>.
+ */
+ public static Condition getCondition(Bundle bundle,ConditionInfo conditionInfo)
+ {
+ String[] args = conditionInfo.getArgs();
+ if (args==null) throw new NullPointerException("args");
+ if (args.length!=4) throw new IllegalArgumentException("args.length=="+args.length+" (should be 4)");
+ if (bundle==null) throw new NullPointerException("bundle");
+ String levels = args[0];
+ String defaultLevel = args[1];
+ String catalogName = args[2];
+ String message = args[3];
+ if (levels==null) throw new NullPointerException("levels");
+ if (defaultLevel==null) throw new NullPointerException("defaultLevel");
+ if (catalogName==null) throw new NullPointerException("catalogName");
+ if (message==null) throw new NullPointerException("message");
+
+ if (factory==null) {
+ // the bundle implementing the UserPromptCondition has not started yet.
+ // Do wrapping magick.
+ return new UserPromptCondition(bundle,levels,defaultLevel,catalogName,message);
+ } else {
+ // there is already a factory, no need to do any wrapping magic
+ try {
+ return (Condition) factory.invoke(null,new Object[]{bundle,levels,defaultLevel,catalogName,message});
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ Throwable original = e.getTargetException();
+ if (original instanceof NullPointerException) throw (NullPointerException) original;
+ if (original instanceof IllegalArgumentException) throw (IllegalArgumentException) original;
+ e.printStackTrace();
+ }
+ // the factory method is not working, fallback behavior:
+ factory = null;
+ return new UserPromptCondition(bundle,levels,defaultLevel,catalogName,message);
+ }
+ }
+
+ /**
+ * Instances of the UserPromptCondition are simply store the construction parameters
+ * until a "real" UserPromptCondition is registered in setFactory(). At that point, it
+ * will delegate all calls there.
+ * @param unused this parameter is here so that ConditionalPermissionAdmin would not
+ * use this as the constructor instead of the getInstance
+ * @param bundle
+ * @param levels
+ * @param defaultLevel
+ * @param catalogName
+ * @param message
+ */
+ private UserPromptCondition(Bundle bundle,String levels,String defaultLevel,String catalogName,String message) {
+ this.bundle=bundle;
+ this.levels=levels;
+ this.defaultLevel=defaultLevel;
+ this.catalogName=catalogName;
+ this.message=message;
+ }
+
+ /**
+ * Check if a factory is registered, and if yes, create userprompt to delegate calls to.
+ */
+ private void lookForImplementation() {
+ if ((realUserPromptCondition==null)&&(factory!=null)) {
+ try {
+ realUserPromptCondition = (Condition) factory.invoke(null,new Object[]{bundle,levels,defaultLevel,catalogName,message});
+ return;
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ // only if the factory call fails with some invocation exception
+ factory = null;
+ }
+ }
+
+ /**
+ * Checks if the {@link #isSatisfied()} method needs to prompt the user, thus cannot
+ * give results instantly.
+ * This depends on the permission level given in
+ * {@link UserPromptCondition#getCondition(Bundle, ConditionInfo)}.
+ * <ul>
+ * <li>ONESHOT - isPostponed always returns true. The user is prompted for question every time.</li>
+ * <li>SESSION - isPostponed returns true until the user decides either yes or no for the current session.</li>
+ * <li>BLANKET - isPostponed returns true until the user decides either always or never.</li>
+ * </ul>
+ * Regardless of the session level, the user is always given the option to reject the prompt
+ * permanently, as if BLANKET/never was chosen. In this case, the question is not postponed
+ * anymore, and {@link #isSatisfied()} returns false.<br/>
+ * If the system supports an separately accessible permission management GUI,
+ * that may reset the condition
+ * to its initial state.
+ *
+ * @return True, if user interaction is needed.
+ */
+ public boolean isPostponed() {
+ lookForImplementation();
+ if (realUserPromptCondition!=null) {
+ return realUserPromptCondition.isPostponed();
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Checks whether the condition may change during the lifetime of the UserPromptCondition object.
+ * This depends on the permission level given in
+ * {@link UserPromptCondition#getCondition(Bundle, ConditionInfo)}.
+ * <ul>
+ * <li>ONESHOT - true</li>
+ * <li>SESSION - true, if the application model's session lifetime is
+ * shorter than the UserPromptCondition object lifetime</li>
+ * <li>BLANKET - false</li>
+ * </ul>
+ * If the system supports separately accessible permission management GUI,
+ * then this function may also return true for SESSION and BLANKET.
+ *
+ * @return True, if the condition can change.
+ */
+ public boolean isMutable() {
+ lookForImplementation();
+ if (realUserPromptCondition!=null) {
+ return realUserPromptCondition.isMutable();
+ } else {
+ // since we don't know what the actual status is, we cannot say
+ // "the condition cannot change anymore"
+ return true;
+ }
+ }
+
+ /**
+ * Displays the prompt string to
+ * the user and returns true if the user accepts. Depending on the
+ * amount of levels the condition is assigned to, the prompt may have
+ * multiple accept buttons and one of them can be selected by default (see
+ * default level parameter at {@link UserPromptCondition#getCondition(Bundle, ConditionInfo)}).
+ * It must always be possible for the user
+ * to stop further prompting of this question, even with ONESHOT and SESSION levels.
+ * In case of BLANKET
+ * and SESSION levels, it is possible that the user has already answered the question,
+ * in this case there will be no prompting, but immediate return with the previous answer.
+ *
+ * @return True if the user accepts the prompt (or accepts any prompt in
+ * case there are multiple permission levels).
+ */
+ public boolean isSatisfied() {
+ lookForImplementation();
+ if (realUserPromptCondition!=null) {
+ return realUserPromptCondition.isSatisfied();
+ } else {
+ // paranoid security option
+ return false;
+ }
+ }
+
+ /**
+ * Checks an array of UserPrompt conditions.
+ *
+ * @param conds The array containing the UserPrompt conditions to evaluate.
+ * @param context Storage area for evaluation. The {@link org.osgi.service.condpermadmin.ConditionalPermissionAdmin}
+ * may evaluate a condition several times for one permission check, so this context
+ * will be used to store results of ONESHOT questions. This way asking the same question
+ * twice in a row can be avoided. If context is null, temporary results will not be stored.
+ * @return True, if all conditions are satisfied.
+ * @throws NullPointerException if conds is null.
+ */
+ public boolean isSatisfied(Condition[] conds, Dictionary context) {
+ lookForImplementation();
+ if (realUserPromptCondition!=null) {
+ return realUserPromptCondition.isSatisfied(conds,context);
+ } else {
+ // paranoid security option
+ return false;
+ }
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/util/mobile/package.html b/deploymentadmin/src/main/java/org/osgi/util/mobile/package.html
new file mode 100644
index 0000000..d7e10be
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/mobile/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.util.mobile/src/org/osgi/util/mobile/package.html,v 1.2 2006/07/12 21:07:11 hargrave Exp $ -->
+<BODY>
+<p>Mobile Conditions Package Version 1.0.
+<p>Bundles wishing to use this package must list the package
+in the Import-Package header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.util.mobile; version=1.0
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/util/mobile/packageinfo b/deploymentadmin/src/main/java/org/osgi/util/mobile/packageinfo
new file mode 100644
index 0000000..a4f1546
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/mobile/packageinfo
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file
diff --git a/deploymentadmin/src/main/java/org/osgi/util/tracker/ServiceTracker.java b/deploymentadmin/src/main/java/org/osgi/util/tracker/ServiceTracker.java
new file mode 100644
index 0000000..253b9f6
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/tracker/ServiceTracker.java
@@ -0,0 +1,1141 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.tracker/src/org/osgi/util/tracker/ServiceTracker.java,v 1.21 2006/07/12 21:05:17 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.util.tracker;
+
+import java.util.*;
+
+import org.osgi.framework.*;
+
+/**
+ * The <code>ServiceTracker</code> class simplifies using services from the
+ * Framework's service registry.
+ * <p>
+ * A <code>ServiceTracker</code> object is constructed with search criteria
+ * and a <code>ServiceTrackerCustomizer</code> object. A
+ * <code>ServiceTracker</code> object can use the
+ * <code>ServiceTrackerCustomizer</code> object to customize the service
+ * objects to be tracked. The <code>ServiceTracker</code> object can then be
+ * opened to begin tracking all services in the Framework's service registry
+ * that match the specified search criteria. The <code>ServiceTracker</code>
+ * object correctly handles all of the details of listening to
+ * <code>ServiceEvent</code> objects and getting and ungetting services.
+ * <p>
+ * The <code>getServiceReferences</code> method can be called to get
+ * references to the services being tracked. The <code>getService</code> and
+ * <code>getServices</code> methods can be called to get the service objects
+ * for the tracked service.
+ *
+ * @version $Revision: 1.21 $
+ */
+public class ServiceTracker implements ServiceTrackerCustomizer {
+ /* set this to true to compile in debug messages */
+ static final boolean DEBUG = false;
+ /**
+ * Bundle context against which this <code>ServiceTracker</code> object is tracking.
+ */
+ protected final BundleContext context;
+ /**
+ * Filter specifying search criteria for the services to track.
+ *
+ * @since 1.1
+ */
+ protected final Filter filter;
+ /**
+ * <code>ServiceTrackerCustomizer</code> object for this tracker.
+ */
+ final ServiceTrackerCustomizer customizer;
+ /**
+ * Filter string for use when adding the ServiceListener. If this field is
+ * set, then certain optimizations can be taken since we don't have a user
+ * supplied filter.
+ */
+ final String listenerFilter;
+ /**
+ * Class name to be tracked. If this field is set, then we are tracking by
+ * class name.
+ */
+ private final String trackClass;
+ /**
+ * Reference to be tracked. If this field is set, then we are tracking a
+ * single ServiceReference.
+ */
+ private final ServiceReference trackReference;
+ /**
+ * Tracked services: <code>ServiceReference</code> object -> customized
+ * Object and <code>ServiceListener</code> object
+ */
+ private Tracked tracked;
+ /**
+ * Modification count. This field is initialized to zero by open, set to -1
+ * by close and incremented by modified.
+ *
+ * This field is volatile since it is accessed by multiple threads.
+ */
+ private volatile int trackingCount = -1;
+ /**
+ * Cached ServiceReference for getServiceReference.
+ *
+ * This field is volatile since it is accessed by multiple threads.
+ */
+ private volatile ServiceReference cachedReference;
+ /**
+ * Cached service object for getService.
+ *
+ * This field is volatile since it is accessed by multiple threads.
+ */
+ private volatile Object cachedService;
+
+ /**
+ * Create a <code>ServiceTracker</code> object on the specified
+ * <code>ServiceReference</code> object.
+ *
+ * <p>
+ * The service referenced by the specified <code>ServiceReference</code>
+ * object will be tracked by this <code>ServiceTracker</code> object.
+ *
+ * @param context <code>BundleContext</code> object against which the
+ * tracking is done.
+ * @param reference <code>ServiceReference</code> object for the service
+ * to be tracked.
+ * @param customizer The customizer object to call when services are added,
+ * modified, or removed in this <code>ServiceTracker</code> object.
+ * If customizer is <code>null</code>, then this
+ * <code>ServiceTracker</code> object will be used as the
+ * <code>ServiceTrackerCustomizer</code> object and the
+ * <code>ServiceTracker</code> object will call the
+ * <code>ServiceTrackerCustomizer</code> methods on itself.
+ */
+ public ServiceTracker(BundleContext context, ServiceReference reference,
+ ServiceTrackerCustomizer customizer) {
+ this.context = context;
+ this.trackReference = reference;
+ this.trackClass = null;
+ this.customizer = (customizer == null) ? this : customizer;
+ this.listenerFilter = "(" + Constants.SERVICE_ID + "=" + reference.getProperty(Constants.SERVICE_ID).toString() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ try {
+ this.filter = context.createFilter(listenerFilter);
+ }
+ catch (InvalidSyntaxException e) { // we could only get this exception
+ // if the ServiceReference was
+ // invalid
+ throw new IllegalArgumentException(
+ "unexpected InvalidSyntaxException: " + e.getMessage()); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Create a <code>ServiceTracker</code> object on the specified class
+ * name.
+ *
+ * <p>
+ * Services registered under the specified class name will be tracked by
+ * this <code>ServiceTracker</code> object.
+ *
+ * @param context <code>BundleContext</code> object against which the
+ * tracking is done.
+ * @param clazz Class name of the services to be tracked.
+ * @param customizer The customizer object to call when services are added,
+ * modified, or removed in this <code>ServiceTracker</code> object.
+ * If customizer is <code>null</code>, then this
+ * <code>ServiceTracker</code> object will be used as the
+ * <code>ServiceTrackerCustomizer</code> object and the
+ * <code>ServiceTracker</code> object will call the
+ * <code>ServiceTrackerCustomizer</code> methods on itself.
+ */
+ public ServiceTracker(BundleContext context, String clazz,
+ ServiceTrackerCustomizer customizer) {
+ this.context = context;
+ this.trackReference = null;
+ this.trackClass = clazz;
+ this.customizer = (customizer == null) ? this : customizer;
+ this.listenerFilter = "(" + Constants.OBJECTCLASS + "=" + clazz.toString() + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ try {
+ this.filter = context.createFilter(listenerFilter);
+ }
+ catch (InvalidSyntaxException e) { // we could only get this exception
+ // if the clazz argument was
+ // malformed
+ throw new IllegalArgumentException(
+ "unexpected InvalidSyntaxException: " + e.getMessage()); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Create a <code>ServiceTracker</code> object on the specified
+ * <code>Filter</code> object.
+ *
+ * <p>
+ * Services which match the specified <code>Filter</code> object will be
+ * tracked by this <code>ServiceTracker</code> object.
+ *
+ * @param context <code>BundleContext</code> object against which the
+ * tracking is done.
+ * @param filter <code>Filter</code> object to select the services to be
+ * tracked.
+ * @param customizer The customizer object to call when services are added,
+ * modified, or removed in this <code>ServiceTracker</code> object.
+ * If customizer is null, then this <code>ServiceTracker</code>
+ * object will be used as the <code>ServiceTrackerCustomizer</code>
+ * object and the <code>ServiceTracker</code> object will call the
+ * <code>ServiceTrackerCustomizer</code> methods on itself.
+ * @since 1.1
+ */
+ public ServiceTracker(BundleContext context, Filter filter,
+ ServiceTrackerCustomizer customizer) {
+ this.context = context;
+ this.trackReference = null;
+ this.trackClass = null;
+ this.listenerFilter = null;
+ this.filter = filter;
+ this.customizer = (customizer == null) ? this : customizer;
+ if ((context == null) || (filter == null)) { // we throw a NPE here
+ // to
+ // be consistent with the
+ // other constructors
+ throw new NullPointerException();
+ }
+ }
+
+ /**
+ * Open this <code>ServiceTracker</code> object and begin tracking
+ * services.
+ *
+ * <p>
+ * This method calls <code>open(false)</code>.
+ *
+ * @throws java.lang.IllegalStateException if the <code>BundleContext</code>
+ * object with which this <code>ServiceTracker</code> object was
+ * created is no longer valid.
+ * @see #open(boolean)
+ */
+ public void open() {
+ open(false);
+ }
+
+ /**
+ * Open this <code>ServiceTracker</code> object and begin tracking
+ * services.
+ *
+ * <p>
+ * Services which match the search criteria specified when this
+ * <code>ServiceTracker</code> object was created are now tracked by this
+ * <code>ServiceTracker</code> object.
+ *
+ * @param trackAllServices If <code>true</code>, then this
+ * <code>ServiceTracker</code> will track all matching services
+ * regardless of class loader accessibility. If <code>false</code>,
+ * then this <code>ServiceTracker</code> will only track matching
+ * services which are class loader accessibile to the bundle whose
+ * <code>BundleContext</code> is used by this
+ * <code>ServiceTracker</code>.
+ * @throws java.lang.IllegalStateException if the <code>BundleContext</code>
+ * object with which this <code>ServiceTracker</code> object was
+ * created is no longer valid.
+ * @since 1.3
+ */
+ public synchronized void open(boolean trackAllServices) {
+ if (tracked != null) {
+ return;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.open: " + filter); //$NON-NLS-1$
+ }
+ tracked = trackAllServices ? new AllTracked() : new Tracked();
+ trackingCount = 0;
+ synchronized (tracked) {
+ try {
+ context.addServiceListener(tracked, listenerFilter);
+ ServiceReference[] references;
+ if (listenerFilter == null) { // user supplied filter
+ references = getInitialReferences(trackAllServices, null,
+ filter.toString());
+ }
+ else { // constructor supplied filter
+ if (trackClass == null) {
+ references = new ServiceReference[] {trackReference};
+ }
+ else {
+ references = getInitialReferences(trackAllServices,
+ trackClass, null);
+ }
+ }
+
+ tracked.setInitialServices(references); // set tracked with
+ // the initial
+ // references
+ }
+ catch (InvalidSyntaxException e) {
+ throw new RuntimeException(
+ "unexpected InvalidSyntaxException: " + e.getMessage()); //$NON-NLS-1$
+ }
+ }
+ /* Call tracked outside of synchronized region */
+ tracked.trackInitialServices(); // process the initial references
+ }
+
+ /**
+ * Returns the list of initial <code>ServiceReference</code> objects that
+ * will be tracked by this <code>ServiceTracker</code> object.
+ *
+ * @param trackAllServices If true, use getAllServiceReferences.
+ * @param trackClass the class name with which the service was registered,
+ * or null for all services.
+ * @param filterString the filter criteria or null for all services.
+ * @return the list of initial <code>ServiceReference</code> objects.
+ * @throws InvalidSyntaxException if the filter uses an invalid syntax.
+ */
+ private ServiceReference[] getInitialReferences(boolean trackAllServices,
+ String trackClass, String filterString)
+ throws InvalidSyntaxException {
+ if (trackAllServices) {
+ return context.getAllServiceReferences(trackClass, filterString);
+ }
+ else {
+ return context.getServiceReferences(trackClass, filterString);
+ }
+ }
+
+ /**
+ * Close this <code>ServiceTracker</code> object.
+ *
+ * <p>
+ * This method should be called when this <code>ServiceTracker</code>
+ * object should end the tracking of services.
+ */
+ public synchronized void close() {
+ if (tracked == null) {
+ return;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.close: " + filter); //$NON-NLS-1$
+ }
+ tracked.close();
+ ServiceReference[] references = getServiceReferences();
+ Tracked outgoing = tracked;
+ tracked = null;
+ try {
+ context.removeServiceListener(outgoing);
+ }
+ catch (IllegalStateException e) {
+ /* In case the context was stopped. */
+ }
+ if (references != null) {
+ for (int i = 0; i < references.length; i++) {
+ outgoing.untrack(references[i]);
+ }
+ }
+ trackingCount = -1;
+ if (DEBUG) {
+ if ((cachedReference == null) && (cachedService == null)) {
+ System.out
+ .println("ServiceTracker.close[cached cleared]: " + filter); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Default implementation of the
+ * <code>ServiceTrackerCustomizer.addingService</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>ServiceTracker</code> object
+ * has been constructed with a <code>null ServiceTrackerCustomizer</code>
+ * argument.
+ *
+ * The default implementation returns the result of calling
+ * <code>getService</code>, on the <code>BundleContext</code> object
+ * with which this <code>ServiceTracker</code> object was created, passing
+ * the specified <code>ServiceReference</code> object.
+ * <p>
+ * This method can be overridden in a subclass to customize the service
+ * object to be tracked for the service being added. In that case, take care
+ * not to rely on the default implementation of removedService that will
+ * unget the service.
+ *
+ * @param reference Reference to service being added to this
+ * <code>ServiceTracker</code> object.
+ * @return The service object to be tracked for the service added to this
+ * <code>ServiceTracker</code> object.
+ * @see ServiceTrackerCustomizer
+ */
+ public Object addingService(ServiceReference reference) {
+ return context.getService(reference);
+ }
+
+ /**
+ * Default implementation of the
+ * <code>ServiceTrackerCustomizer.modifiedService</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>ServiceTracker</code> object
+ * has been constructed with a <code>null ServiceTrackerCustomizer</code>
+ * argument.
+ *
+ * The default implementation does nothing.
+ *
+ * @param reference Reference to modified service.
+ * @param service The service object for the modified service.
+ * @see ServiceTrackerCustomizer
+ */
+ public void modifiedService(ServiceReference reference, Object service) {
+ }
+
+ /**
+ * Default implementation of the
+ * <code>ServiceTrackerCustomizer.removedService</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>ServiceTracker</code> object
+ * has been constructed with a <code>null ServiceTrackerCustomizer</code>
+ * argument.
+ *
+ * The default implementation calls <code>ungetService</code>, on the
+ * <code>BundleContext</code> object with which this
+ * <code>ServiceTracker</code> object was created, passing the specified
+ * <code>ServiceReference</code> object.
+ * <p>
+ * This method can be overridden in a subclass. If the default
+ * implementation of <code>addingService</code> method was used, this
+ * method must unget the service.
+ *
+ * @param reference Reference to removed service.
+ * @param service The service object for the removed service.
+ * @see ServiceTrackerCustomizer
+ */
+ public void removedService(ServiceReference reference, Object service) {
+ context.ungetService(reference);
+ }
+
+ /**
+ * Wait for at least one service to be tracked by this
+ * <code>ServiceTracker</code> object.
+ * <p>
+ * It is strongly recommended that <code>waitForService</code> is not used
+ * during the calling of the <code>BundleActivator</code> methods.
+ * <code>BundleActivator</code> methods are expected to complete in a
+ * short period of time.
+ *
+ * @param timeout time interval in milliseconds to wait. If zero, the method
+ * will wait indefinately.
+ * @return Returns the result of <code>getService()</code>.
+ * @throws InterruptedException If another thread has interrupted the
+ * current thread.
+ * @throws IllegalArgumentException If the value of timeout is negative.
+ */
+ public Object waitForService(long timeout) throws InterruptedException {
+ if (timeout < 0) {
+ throw new IllegalArgumentException("timeout value is negative"); //$NON-NLS-1$
+ }
+ Object object = getService();
+ while (object == null) {
+ Tracked tracked = this.tracked; /*
+ * use local var since we are not
+ * synchronized
+ */
+ if (tracked == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (tracked) {
+ if (tracked.size() == 0) {
+ tracked.wait(timeout);
+ }
+ }
+ object = getService();
+ if (timeout > 0) {
+ return object;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Return an array of <code>ServiceReference</code> objects for all
+ * services being tracked by this <code>ServiceTracker</code> object.
+ *
+ * @return Array of <code>ServiceReference</code> objects or
+ * <code>null</code> if no service are being tracked.
+ */
+ public ServiceReference[] getServiceReferences() {
+ Tracked tracked = this.tracked; /*
+ * use local var since we are not
+ * synchronized
+ */
+ if (tracked == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (tracked) {
+ int length = tracked.size();
+ if (length == 0) {
+ return null;
+ }
+ ServiceReference[] references = new ServiceReference[length];
+ Enumeration keys = tracked.keys();
+ for (int i = 0; i < length; i++) {
+ references[i] = (ServiceReference) keys.nextElement();
+ }
+ return references;
+ }
+ }
+
+ /**
+ * Returns a <code>ServiceReference</code> object for one of the services
+ * being tracked by this <code>ServiceTracker</code> object.
+ *
+ * <p>
+ * If multiple services are being tracked, the service with the highest
+ * ranking (as specified in its <code>service.ranking</code> property) is
+ * returned.
+ *
+ * <p>
+ * If there is a tie in ranking, the service with the lowest service ID (as
+ * specified in its <code>service.id</code> property); that is, the
+ * service that was registered first is returned.
+ * <p>
+ * This is the same algorithm used by
+ * <code>BundleContext.getServiceReference</code>.
+ *
+ * @return <code>ServiceReference</code> object or <code>null</code> if
+ * no service is being tracked.
+ * @since 1.1
+ */
+ public ServiceReference getServiceReference() {
+ ServiceReference reference = cachedReference;
+ if (reference != null) {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.getServiceReference[cached]: " + filter); //$NON-NLS-1$
+ }
+ return reference;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.getServiceReference: " + filter); //$NON-NLS-1$
+ }
+ ServiceReference[] references = getServiceReferences();
+ int length = (references == null) ? 0 : references.length;
+ if (length == 0) /* if no service is being tracked */
+ {
+ return null;
+ }
+ int index = 0;
+ if (length > 1) /* if more than one service, select highest ranking */
+ {
+ int rankings[] = new int[length];
+ int count = 0;
+ int maxRanking = Integer.MIN_VALUE;
+ for (int i = 0; i < length; i++) {
+ Object property = references[i]
+ .getProperty(Constants.SERVICE_RANKING);
+ int ranking = (property instanceof Integer) ? ((Integer) property)
+ .intValue()
+ : 0;
+ rankings[i] = ranking;
+ if (ranking > maxRanking) {
+ index = i;
+ maxRanking = ranking;
+ count = 1;
+ }
+ else {
+ if (ranking == maxRanking) {
+ count++;
+ }
+ }
+ }
+ if (count > 1) /* if still more than one service, select lowest id */
+ {
+ long minId = Long.MAX_VALUE;
+ for (int i = 0; i < length; i++) {
+ if (rankings[i] == maxRanking) {
+ long id = ((Long) (references[i]
+ .getProperty(Constants.SERVICE_ID)))
+ .longValue();
+ if (id < minId) {
+ index = i;
+ minId = id;
+ }
+ }
+ }
+ }
+ }
+ return cachedReference = references[index];
+ }
+
+ /**
+ * Returns the service object for the specified
+ * <code>ServiceReference</code> object if the referenced service is being
+ * tracked by this <code>ServiceTracker</code> object.
+ *
+ * @param reference Reference to the desired service.
+ * @return Service object or <code>null</code> if the service referenced
+ * by the specified <code>ServiceReference</code> object is not
+ * being tracked.
+ */
+ public Object getService(ServiceReference reference) {
+ Tracked tracked = this.tracked; /*
+ * use local var since we are not
+ * synchronized
+ */
+ if (tracked == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (tracked) {
+ return tracked.get(reference);
+ }
+ }
+
+ /**
+ * Return an array of service objects for all services being tracked by this
+ * <code>ServiceTracker</code> object.
+ *
+ * @return Array of service objects or <code>null</code> if no service are
+ * being tracked.
+ */
+ public Object[] getServices() {
+ Tracked tracked = this.tracked; /*
+ * use local var since we are not
+ * synchronized
+ */
+ if (tracked == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (tracked) {
+ ServiceReference[] references = getServiceReferences();
+ int length = (references == null) ? 0 : references.length;
+ if (length == 0) {
+ return null;
+ }
+ Object[] objects = new Object[length];
+ for (int i = 0; i < length; i++) {
+ objects[i] = getService(references[i]);
+ }
+ return objects;
+ }
+ }
+
+ /**
+ * Returns a service object for one of the services being tracked by this
+ * <code>ServiceTracker</code> object.
+ *
+ * <p>
+ * If any services are being tracked, this method returns the result of
+ * calling <code>getService(getServiceReference())</code>.
+ *
+ * @return Service object or <code>null</code> if no service is being
+ * tracked.
+ */
+ public Object getService() {
+ Object service = cachedService;
+ if (service != null) {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.getService[cached]: " + filter); //$NON-NLS-1$
+ }
+ return service;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.getService: " + filter); //$NON-NLS-1$
+ }
+ ServiceReference reference = getServiceReference();
+ if (reference == null) {
+ return null;
+ }
+ return cachedService = getService(reference);
+ }
+
+ /**
+ * Remove a service from this <code>ServiceTracker</code> object.
+ *
+ * The specified service will be removed from this
+ * <code>ServiceTracker</code> object. If the specified service was being
+ * tracked then the <code>ServiceTrackerCustomizer.removedService</code>
+ * method will be called for that service.
+ *
+ * @param reference Reference to the service to be removed.
+ */
+ public void remove(ServiceReference reference) {
+ Tracked tracked = this.tracked; /*
+ * use local var since we are not
+ * synchronized
+ */
+ if (tracked == null) { /* if ServiceTracker is not open */
+ return;
+ }
+ tracked.untrack(reference);
+ }
+
+ /**
+ * Return the number of services being tracked by this
+ * <code>ServiceTracker</code> object.
+ *
+ * @return Number of services being tracked.
+ */
+ public int size() {
+ Tracked tracked = this.tracked; /*
+ * use local var since we are not
+ * synchronized
+ */
+ if (tracked == null) { /* if ServiceTracker is not open */
+ return 0;
+ }
+ return tracked.size();
+ }
+
+ /**
+ * Returns the tracking count for this <code>ServiceTracker</code> object.
+ *
+ * The tracking count is initialized to 0 when this
+ * <code>ServiceTracker</code> object is opened. Every time a service is
+ * added or removed from this <code>ServiceTracker</code> object the
+ * tracking count is incremented.
+ *
+ * <p>
+ * The tracking count can be used to determine if this
+ * <code>ServiceTracker</code> object has added or removed a service by
+ * comparing a tracking count value previously collected with the current
+ * tracking count value. If the value has not changed, then no service has
+ * been added or removed from this <code>ServiceTracker</code> object
+ * since the previous tracking count was collected.
+ *
+ * @since 1.2
+ * @return The tracking count for this <code>ServiceTracker</code> object
+ * or -1 if this <code>ServiceTracker</code> object is not open.
+ */
+ public int getTrackingCount() {
+ return trackingCount;
+ }
+
+ /**
+ * Called by the Tracked object whenever the set of tracked services is
+ * modified. Increments the tracking count and clears the cache.
+ */
+ /*
+ * This method must not be synchronized since it is called by Tracked while
+ * Tracked is synchronized. We don't want synchronization interactions
+ * between the ServiceListener thread and the user thread.
+ */
+ void modified() {
+ trackingCount++; /* increment modification count */
+ cachedReference = null; /* clear cached value */
+ cachedService = null; /* clear cached value */
+ if (DEBUG) {
+ System.out.println("ServiceTracker.modified: " + filter); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Finalize. This method no longer performs any function but it kept to
+ * maintain binary compatibility with prior versions of this class.
+ */
+ protected void finalize() throws Throwable {
+ }
+
+ /**
+ * Inner class to track services. If a <code>ServiceTracker</code> object
+ * is reused (closed then reopened), then a new Tracked object is used. This
+ * class is a hashtable mapping <code>ServiceReference</code> object ->
+ * customized Object. This class is the <code>ServiceListener</code>
+ * object for the tracker. This class is used to synchronize access to the
+ * tracked services. This is not a public class. It is only for use by the
+ * implementation of the <code>ServiceTracker</code> class.
+ *
+ */
+ class Tracked extends Hashtable implements ServiceListener {
+ static final long serialVersionUID = -7420065199791006079L;
+ /**
+ * List of ServiceReferences in the process of being added. This is used
+ * to deal with nesting of ServiceEvents. Since ServiceEvents are
+ * synchronously delivered, ServiceEvents can be nested. For example,
+ * when processing the adding of a service and the customizer causes the
+ * service to be unregistered, notification to the nested call to
+ * untrack that the service was unregistered can be made to the track
+ * method.
+ *
+ * Since the ArrayList implementation is not synchronized, all access to
+ * this list must be protected by the same synchronized object for
+ * thread safety.
+ */
+ private ArrayList adding;
+
+ /**
+ * true if the tracked object is closed.
+ *
+ * This field is volatile because it is set by one thread and read by
+ * another.
+ */
+ private volatile boolean closed;
+
+ /**
+ * Initial list of ServiceReferences for the tracker. This is used to
+ * correctly process the initial services which could become
+ * unregistered before they are tracked. This is necessary since the
+ * initial set of tracked services are not "announced" by ServiceEvents
+ * and therefore the ServiceEvent for unregistration could be delivered
+ * before we track the service.
+ *
+ * A service must not be in both the initial and adding lists at the
+ * same time. A service must be moved from the initial list to the
+ * adding list "atomically" before we begin tracking it.
+ *
+ * Since the LinkedList implementation is not synchronized, all access
+ * to this list must be protected by the same synchronized object for
+ * thread safety.
+ */
+ private LinkedList initial;
+
+ /**
+ * Tracked constructor.
+ */
+ protected Tracked() {
+ super();
+ closed = false;
+ adding = new ArrayList(6);
+ initial = new LinkedList();
+ }
+
+ /**
+ * Set initial list of services into tracker before ServiceEvents begin
+ * to be received.
+ *
+ * This method must be called from ServiceTracker.open while
+ * synchronized on this object in the same synchronized block as the
+ * addServiceListener call.
+ *
+ * @param references The initial list of services to be tracked.
+ */
+ protected void setInitialServices(ServiceReference[] references) {
+ if (references == null) {
+ return;
+ }
+ int size = references.length;
+ for (int i = 0; i < size; i++) {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.setInitialServices: " + references[i]); //$NON-NLS-1$
+ }
+ initial.add(references[i]);
+ }
+ }
+
+ /**
+ * Track the initial list of services. This is called after
+ * ServiceEvents can begin to be received.
+ *
+ * This method must be called from ServiceTracker.open while not
+ * synchronized on this object after the addServiceListener call.
+ *
+ */
+ protected void trackInitialServices() {
+ while (true) {
+ ServiceReference reference;
+ synchronized (this) {
+ if (initial.size() == 0) {
+ /*
+ * if there are no more inital services
+ */
+ return; /* we are done */
+ }
+ /*
+ * move the first service from the initial list to the
+ * adding list within this synchronized block.
+ */
+ reference = (ServiceReference) initial.removeFirst();
+ if (this.get(reference) != null) {
+ /* if we are already tracking this service */
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.trackInitialServices[already tracked]: " + reference); //$NON-NLS-1$
+ }
+ continue; /* skip this service */
+ }
+ if (adding.contains(reference)) {
+ /*
+ * if this service is already in the process of being
+ * added.
+ */
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.trackInitialServices[already adding]: " + reference); //$NON-NLS-1$
+ }
+ continue; /* skip this service */
+ }
+ adding.add(reference);
+ }
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.trackInitialServices: " + reference); //$NON-NLS-1$
+ }
+ trackAdding(reference); /*
+ * Begin tracking it. We call
+ * trackAdding since we have already put
+ * the reference in the adding list.
+ */
+ }
+ }
+
+ /**
+ * Called by the owning <code>ServiceTracker</code> object when it is
+ * closed.
+ */
+ protected void close() {
+ closed = true;
+ }
+
+ /**
+ * <code>ServiceListener</code> method for the
+ * <code>ServiceTracker</code> class. This method must NOT be
+ * synchronized to avoid deadlock potential.
+ *
+ * @param event <code>ServiceEvent</code> object from the framework.
+ */
+ public void serviceChanged(ServiceEvent event) {
+ /*
+ * Check if we had a delayed call (which could happen when we
+ * close).
+ */
+ if (closed) {
+ return;
+ }
+ ServiceReference reference = event.getServiceReference();
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.serviceChanged[" + event.getType() + "]: " + reference); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ switch (event.getType()) {
+ case ServiceEvent.REGISTERED :
+ case ServiceEvent.MODIFIED :
+ if (listenerFilter != null) { // constructor supplied
+ // filter
+ track(reference);
+ /*
+ * If the customizer throws an unchecked exception, it
+ * is safe to let it propagate
+ */
+ }
+ else { // user supplied filter
+ if (filter.match(reference)) {
+ track(reference);
+ /*
+ * If the customizer throws an unchecked exception,
+ * it is safe to let it propagate
+ */
+ }
+ else {
+ untrack(reference);
+ /*
+ * If the customizer throws an unchecked exception,
+ * it is safe to let it propagate
+ */
+ }
+ }
+ break;
+ case ServiceEvent.UNREGISTERING :
+ untrack(reference);
+ /*
+ * If the customizer throws an unchecked exception, it is
+ * safe to let it propagate
+ */
+ break;
+ }
+ }
+
+ /**
+ * Begin to track the referenced service.
+ *
+ * @param reference Reference to a service to be tracked.
+ */
+ protected void track(ServiceReference reference) {
+ Object object;
+ synchronized (this) {
+ object = this.get(reference);
+ }
+ if (object != null) /* we are already tracking the service */
+ {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.track[modified]: " + reference); //$NON-NLS-1$
+ }
+ synchronized (this) {
+ modified(); /* increment modification count */
+ }
+ /* Call customizer outside of synchronized region */
+ customizer.modifiedService(reference, object);
+ /*
+ * If the customizer throws an unchecked exception, it is safe
+ * to let it propagate
+ */
+ return;
+ }
+ synchronized (this) {
+ if (adding.contains(reference)) { /*
+ * if this service is
+ * already in the process of
+ * being added.
+ */
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.track[already adding]: " + reference); //$NON-NLS-1$
+ }
+ return;
+ }
+ adding.add(reference); /* mark this service is being added */
+ }
+
+ trackAdding(reference); /*
+ * call trackAdding now that we have put the
+ * reference in the adding list
+ */
+ }
+
+ /**
+ * Common logic to add a service to the tracker used by track and
+ * trackInitialServices. The specified reference must have been placed
+ * in the adding list before calling this method.
+ *
+ * @param reference Reference to a service to be tracked.
+ */
+ private void trackAdding(ServiceReference reference) {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.trackAdding: " + reference); //$NON-NLS-1$
+ }
+ Object object = null;
+ boolean becameUntracked = false;
+ /* Call customizer outside of synchronized region */
+ try {
+ object = customizer.addingService(reference);
+ /*
+ * If the customizer throws an unchecked exception, it will
+ * propagate after the finally
+ */
+ }
+ finally {
+ synchronized (this) {
+ if (adding.remove(reference)) { /*
+ * if the service was not
+ * untracked during the
+ * customizer callback
+ */
+ if (object != null) {
+ this.put(reference, object);
+ modified(); /* increment modification count */
+ notifyAll(); /*
+ * notify any waiters in
+ * waitForService
+ */
+ }
+ }
+ else {
+ becameUntracked = true;
+ }
+ }
+ }
+ /*
+ * The service became untracked during the customizer callback.
+ */
+ if (becameUntracked) {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.trackAdding[removed]: " + reference); //$NON-NLS-1$
+ }
+ /* Call customizer outside of synchronized region */
+ customizer.removedService(reference, object);
+ /*
+ * If the customizer throws an unchecked exception, it is safe
+ * to let it propagate
+ */
+ }
+ }
+
+ /**
+ * Discontinue tracking the referenced service.
+ *
+ * @param reference Reference to the tracked service.
+ */
+ protected void untrack(ServiceReference reference) {
+ Object object;
+ synchronized (this) {
+ if (initial.remove(reference)) { /*
+ * if this service is
+ * already in the list of
+ * initial references to
+ * process
+ */
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.untrack[removed from initial]: " + reference); //$NON-NLS-1$
+ }
+ return; /*
+ * we have removed it from the list and it will not
+ * be processed
+ */
+ }
+
+ if (adding.remove(reference)) { /*
+ * if the service is in the
+ * process of being added
+ */
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.untrack[being added]: " + reference); //$NON-NLS-1$
+ }
+ return; /*
+ * in case the service is untracked while in the
+ * process of adding
+ */
+ }
+ object = this.remove(reference); /*
+ * must remove from tracker
+ * before calling customizer
+ * callback
+ */
+ if (object == null) { /* are we actually tracking the service */
+ return;
+ }
+ modified(); /* increment modification count */
+ }
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.untrack[removed]: " + reference); //$NON-NLS-1$
+ }
+ /* Call customizer outside of synchronized region */
+ customizer.removedService(reference, object);
+ /*
+ * If the customizer throws an unchecked exception, it is safe to
+ * let it propagate
+ */
+ }
+ }
+
+ /**
+ * Subclass of Tracked which implements the AllServiceListener interface.
+ * This class is used by the ServiceTracker if open is called with true.
+ *
+ * @since 1.3
+ */
+ class AllTracked extends Tracked implements AllServiceListener {
+ static final long serialVersionUID = 4050764875305137716L;
+
+ /**
+ * AllTracked constructor.
+ */
+ protected AllTracked() {
+ super();
+ }
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/util/tracker/ServiceTrackerCustomizer.java b/deploymentadmin/src/main/java/org/osgi/util/tracker/ServiceTrackerCustomizer.java
new file mode 100644
index 0000000..c6f9e20
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/tracker/ServiceTrackerCustomizer.java
@@ -0,0 +1,92 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.tracker/src/org/osgi/util/tracker/ServiceTrackerCustomizer.java,v 1.10 2006/06/16 16:31:13 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2000, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.util.tracker;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The <code>ServiceTrackerCustomizer</code> interface allows a
+ * <code>ServiceTracker</code> object to customize the service objects that are
+ * tracked. The <code>ServiceTrackerCustomizer</code> object is called when a
+ * service is being added to the <code>ServiceTracker</code> object. The
+ * <code>ServiceTrackerCustomizer</code> can then return an object for the tracked
+ * service. The <code>ServiceTrackerCustomizer</code> object is also called when a
+ * tracked service is modified or has been removed from the
+ * <code>ServiceTracker</code> object.
+ *
+ * <p>
+ * The methods in this interface may be called as the result of a
+ * <code>ServiceEvent</code> being received by a <code>ServiceTracker</code> object.
+ * Since <code>ServiceEvent</code> s are synchronously delivered by the Framework,
+ * it is highly recommended that implementations of these methods do not
+ * register (<code>BundleContext.registerService</code>), modify (
+ * <code>ServiceRegistration.setProperties</code>) or unregister (
+ * <code>ServiceRegistration.unregister</code>) a service while being
+ * synchronized on any object.
+ *
+ * @version $Revision: 1.10 $
+ */
+public interface ServiceTrackerCustomizer {
+ /**
+ * A service is being added to the <code>ServiceTracker</code> object.
+ *
+ * <p>
+ * This method is called before a service which matched the search
+ * parameters of the <code>ServiceTracker</code> object is added to it. This
+ * method should return the service object to be tracked for this
+ * <code>ServiceReference</code> object. The returned service object is stored
+ * in the <code>ServiceTracker</code> object and is available from the
+ * <code>getService</code> and <code>getServices</code> methods.
+ *
+ * @param reference Reference to service being added to the
+ * <code>ServiceTracker</code> object.
+ * @return The service object to be tracked for the
+ * <code>ServiceReference</code> object or <code>null</code> if the
+ * <code>ServiceReference</code> object should not be tracked.
+ */
+ public Object addingService(ServiceReference reference);
+
+ /**
+ * A service tracked by the <code>ServiceTracker</code> object has been
+ * modified.
+ *
+ * <p>
+ * This method is called when a service being tracked by the
+ * <code>ServiceTracker</code> object has had it properties modified.
+ *
+ * @param reference Reference to service that has been modified.
+ * @param service The service object for the modified service.
+ */
+ public void modifiedService(ServiceReference reference,
+ Object service);
+
+ /**
+ * A service tracked by the <code>ServiceTracker</code> object has been
+ * removed.
+ *
+ * <p>
+ * This method is called after a service is no longer being tracked by the
+ * <code>ServiceTracker</code> object.
+ *
+ * @param reference Reference to service that has been removed.
+ * @param service The service object for the removed service.
+ */
+ public void removedService(ServiceReference reference,
+ Object service);
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/util/tracker/package.html b/deploymentadmin/src/main/java/org/osgi/util/tracker/package.html
new file mode 100644
index 0000000..f75e8c7
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/tracker/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.util.tracker/src/org/osgi/util/tracker/package.html,v 1.4 2006/07/12 21:07:10 hargrave Exp $ -->
+<BODY>
+<p>Service Tracker Package Version 1.3.
+<p>Bundles wishing to use this package must list the package
+in the Import-Package header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.util.tracker; version=1.3
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/util/tracker/packageinfo b/deploymentadmin/src/main/java/org/osgi/util/tracker/packageinfo
new file mode 100644
index 0000000..350557e
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/tracker/packageinfo
@@ -0,0 +1 @@
+version 1.3.2
diff --git a/deploymentadmin/src/main/java/org/osgi/util/xml/XMLParserActivator.java b/deploymentadmin/src/main/java/org/osgi/util/xml/XMLParserActivator.java
new file mode 100644
index 0000000..77ba538
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/xml/XMLParserActivator.java
@@ -0,0 +1,530 @@
+/*
+ * $Header: /cvshome/build/org.osgi.util.xml/src/org/osgi/util/xml/XMLParserActivator.java,v 1.10 2006/06/21 17:41:20 hargrave Exp $
+ *
+ * Copyright (c) OSGi Alliance (2002, 2006). All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.osgi.util.xml;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+
+import javax.xml.parsers.*;
+
+import org.osgi.framework.*;
+
+/**
+ * A BundleActivator class that allows any JAXP compliant XML Parser to register
+ * itself as an OSGi parser service.
+ *
+ * Multiple JAXP compliant parsers can concurrently register by using this
+ * BundleActivator class. Bundles who wish to use an XML parser can then use the
+ * framework's service registry to locate available XML Parsers with the desired
+ * characteristics such as validating and namespace-aware.
+ *
+ * <p>
+ * The services that this bundle activator enables a bundle to provide are:
+ * <ul>
+ * <li><code>javax.xml.parsers.SAXParserFactory</code>({@link #SAXFACTORYNAME})
+ * <li><code>javax.xml.parsers.DocumentBuilderFactory</code>(
+ * {@link #DOMFACTORYNAME})
+ * </ul>
+ *
+ * <p>
+ * The algorithm to find the implementations of the abstract parsers is derived
+ * from the JAR file specifications, specifically the Services API.
+ * <p>
+ * An XMLParserActivator assumes that it can find the class file names of the
+ * factory classes in the following files:
+ * <ul>
+ * <li><code>/META-INF/services/javax.xml.parsers.SAXParserFactory</code> is
+ * a file contained in a jar available to the runtime which contains the
+ * implementation class name(s) of the SAXParserFactory.
+ * <li><code>/META-INF/services/javax.xml.parsers.DocumentBuilderFactory</code>
+ * is a file contained in a jar available to the runtime which contains the
+ * implementation class name(s) of the <code>DocumentBuilderFactory</code>
+ * </ul>
+ * <p>
+ * If either of the files does not exist, <code>XMLParserActivator</code>
+ * assumes that the parser does not support that parser type.
+ *
+ * <p>
+ * <code>XMLParserActivator</code> attempts to instantiate both the
+ * <code>SAXParserFactory</code> and the <code>DocumentBuilderFactory</code>.
+ * It registers each factory with the framework along with service properties:
+ * <ul>
+ * <li>{@link #PARSER_VALIDATING}- indicates if this factory supports
+ * validating parsers. It's value is a <code>Boolean</code>.
+ * <li>{@link #PARSER_NAMESPACEAWARE}- indicates if this factory supports
+ * namespace aware parsers It's value is a <code>Boolean</code>.
+ * </ul>
+ * <p>
+ * Individual parser implementations may have additional features, properties,
+ * or attributes which could be used to select a parser with a filter. These can
+ * be added by extending this class and overriding the
+ * <code>setSAXProperties</code> and <code>setDOMProperties</code> methods.
+ */
+public class XMLParserActivator implements BundleActivator, ServiceFactory {
+ /** Context of this bundle */
+ private BundleContext context;
+ /**
+ * Filename containing the SAX Parser Factory Class name. Also used as the
+ * basis for the <code>SERVICE_PID<code> registration property.
+ */
+ public static final String SAXFACTORYNAME = "javax.xml.parsers.SAXParserFactory";
+ /**
+ * Filename containing the DOM Parser Factory Class name. Also used as the
+ * basis for the <code>SERVICE_PID</code> registration property.
+ */
+ public static final String DOMFACTORYNAME = "javax.xml.parsers.DocumentBuilderFactory";
+ /** Path to the factory class name files */
+ private static final String PARSERCLASSFILEPATH = "/META-INF/services/";
+ /** Fully qualified path name of SAX Parser Factory Class Name file */
+ public static final String SAXCLASSFILE = PARSERCLASSFILEPATH
+ + SAXFACTORYNAME;
+ /** Fully qualified path name of DOM Parser Factory Class Name file */
+ public static final String DOMCLASSFILE = PARSERCLASSFILEPATH
+ + DOMFACTORYNAME;
+ /** SAX Factory Service Description */
+ private static final String SAXFACTORYDESCRIPTION = "A JAXP Compliant SAX Parser";
+ /** DOM Factory Service Description */
+ private static final String DOMFACTORYDESCRIPTION = "A JAXP Compliant DOM Parser";
+ /**
+ * Service property specifying if factory is configured to support
+ * validating parsers. The value is of type <code>Boolean</code>.
+ */
+ public static final String PARSER_VALIDATING = "parser.validating";
+ /**
+ * Service property specifying if factory is configured to support namespace
+ * aware parsers. The value is of type <code>Boolean</code>.
+ */
+ public static final String PARSER_NAMESPACEAWARE = "parser.namespaceAware";
+ /**
+ * Key for parser factory name property - this must be saved in the parsers
+ * properties hashtable so that the parser factory can be instantiated from
+ * a ServiceReference
+ */
+ private static final String FACTORYNAMEKEY = "parser.factoryname";
+
+ /**
+ * Called when this bundle is started so the Framework can perform the
+ * bundle-specific activities necessary to start this bundle. This method
+ * can be used to register services or to allocate any resources that this
+ * bundle needs.
+ *
+ * <p>
+ * This method must complete and return to its caller in a timely manner.
+ *
+ * <p>
+ * This method attempts to register a SAX and DOM parser with the
+ * Framework's service registry.
+ *
+ * @param context The execution context of the bundle being started.
+ * @throws java.lang.Exception If this method throws an exception, this
+ * bundle is marked as stopped and the Framework will remove this
+ * bundle's listeners, unregister all services registered by this
+ * bundle, and release all services used by this bundle.
+ * @see Bundle#start
+ */
+ public void start(BundleContext context) throws Exception {
+ this.context = context;
+ Bundle parserBundle = context.getBundle();
+ try {
+ // check for sax parsers
+ registerSAXParsers(getParserFactoryClassNames(parserBundle
+ .getResource(SAXCLASSFILE)));
+ // check for dom parsers
+ registerDOMParsers(getParserFactoryClassNames(parserBundle
+ .getResource(DOMCLASSFILE)));
+ }
+ catch (IOException ioe) {
+ // if there were any IO errors accessing the resource files
+ // containing the class names
+ ioe.printStackTrace();
+ throw new FactoryConfigurationError(ioe);
+ }
+ }
+
+ /**
+ * <p>
+ * This method has nothing to do as all active service registrations will
+ * automatically get unregistered when the bundle stops.
+ *
+ * @param context The execution context of the bundle being stopped.
+ * @throws java.lang.Exception If this method throws an exception, the
+ * bundle is still marked as stopped, and the Framework will remove
+ * the bundle's listeners, unregister all services registered by the
+ * bundle, and release all services used by the bundle.
+ * @see Bundle#stop
+ */
+ public void stop(BundleContext context) throws Exception {
+ }
+
+ /**
+ * Given the URL for a file, reads and returns the parser class names. There
+ * may be multiple classes specified in this file, one per line. There may
+ * also be comment lines in the file, which begin with "#".
+ *
+ * @param parserUrl The URL of the service file containing the parser class
+ * names
+ * @return A vector of strings containing the parser class names or null if
+ * parserUrl is null
+ * @throws IOException if there is a problem reading the URL input stream
+ */
+ private Vector getParserFactoryClassNames(URL parserUrl) throws IOException {
+ Vector v = new Vector(1);
+ if (parserUrl != null) {
+ String parserFactoryClassName = null;
+ InputStream is = parserUrl.openStream();
+ BufferedReader br = new BufferedReader(new InputStreamReader(is));
+ while (true) {
+ parserFactoryClassName = br.readLine();
+ if (parserFactoryClassName == null) {
+ break; // end of file reached
+ }
+ String pfcName = parserFactoryClassName.trim();
+ if (pfcName.length() == 0) {
+ continue; // blank line
+ }
+ int commentIdx = pfcName.indexOf("#");
+ if (commentIdx == 0) { // comment line
+ continue;
+ }
+ else
+ if (commentIdx < 0) { // no comment on this line
+ v.addElement(pfcName);
+ }
+ else {
+ v.addElement(pfcName.substring(0, commentIdx).trim());
+ }
+ }
+ return v;
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Register SAX Parser Factory Services with the framework.
+ *
+ * @param parserFactoryClassNames - a <code>Vector</code> of
+ * <code>String</code> objects containing the names of the parser
+ * Factory Classes
+ * @throws FactoryConfigurationError if thrown from <code>getFactory</code>
+ */
+ private void registerSAXParsers(Vector parserFactoryClassNames)
+ throws FactoryConfigurationError {
+ if (parserFactoryClassNames != null) {
+ Enumeration e = parserFactoryClassNames.elements();
+ int index = 0;
+ while (e.hasMoreElements()) {
+ String parserFactoryClassName = (String) e.nextElement();
+ // create a sax parser factory just to get it's default
+ // properties. It will never be used since
+ // this class will operate as a service factory and give each
+ // service requestor it's own SaxParserFactory
+ SAXParserFactory factory = (SAXParserFactory) getFactory(parserFactoryClassName);
+ Hashtable properties = new Hashtable(7);
+ // figure out the default properties of the parser
+ setDefaultSAXProperties(factory, properties, index);
+ // store the parser factory class name in the properties so that
+ // it can be retrieved when getService is called
+ // to return a parser factory
+ properties.put(FACTORYNAMEKEY, parserFactoryClassName);
+ // release the factory
+ factory = null;
+ // register the factory as a service
+ context.registerService(SAXFACTORYNAME, this, properties);
+ index++;
+ }
+ }
+ }
+
+ /**
+ * <p>
+ * Set the SAX Parser Service Properties. By default, the following
+ * properties are set:
+ * <ul>
+ * <li><code>SERVICE_DESCRIPTION</code>
+ * <li><code>SERVICE_PID</code>
+ * <li><code>PARSER_VALIDATING</code>- instantiates a parser and queries
+ * it to find out whether it is validating or not
+ * <li><code>PARSER_NAMESPACEAWARE</code>- instantiates a parser and
+ * queries it to find out whether it is namespace aware or not
+ * <ul>
+ *
+ * @param factory The <code>SAXParserFactory</code> object
+ * @param props <code>Hashtable</code> of service properties.
+ */
+ private void setDefaultSAXProperties(SAXParserFactory factory,
+ Hashtable props, int index) {
+ props.put(Constants.SERVICE_DESCRIPTION, SAXFACTORYDESCRIPTION);
+ props.put(Constants.SERVICE_PID, SAXFACTORYNAME + "."
+ + context.getBundle().getBundleId() + "." + index);
+ setSAXProperties(factory, props);
+ }
+
+ /**
+ * <p>
+ * Set the customizable SAX Parser Service Properties.
+ *
+ * <p>
+ * This method attempts to instantiate a validating parser and a
+ * namespaceaware parser to determine if the parser can support those
+ * features. The appropriate properties are then set in the specified
+ * properties object.
+ *
+ * <p>
+ * This method can be overridden to add additional SAX2 features and
+ * properties. If you want to be able to filter searches of the OSGi service
+ * registry, this method must put a key, value pair into the properties
+ * object for each feature or property. For example,
+ *
+ * properties.put("http://www.acme.com/features/foo", Boolean.TRUE);
+ *
+ * @param factory - the SAXParserFactory object
+ * @param properties - the properties object for the service
+ */
+ public void setSAXProperties(SAXParserFactory factory, Hashtable properties) {
+ // check if this parser can be configured to validate
+ boolean validating = true;
+ factory.setValidating(true);
+ factory.setNamespaceAware(false);
+ try {
+ factory.newSAXParser();
+ }
+ catch (Exception pce_val) {
+ validating = false;
+ }
+ // check if this parser can be configured to be namespaceaware
+ boolean namespaceaware = true;
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+ try {
+ factory.newSAXParser();
+ }
+ catch (Exception pce_nsa) {
+ namespaceaware = false;
+ }
+ // set the factory values
+ factory.setValidating(validating);
+ factory.setNamespaceAware(namespaceaware);
+ // set the OSGi service properties
+ properties.put(PARSER_NAMESPACEAWARE, new Boolean(namespaceaware));
+ properties.put(PARSER_VALIDATING, new Boolean(validating));
+ }
+
+ /**
+ * Register DOM Parser Factory Services with the framework.
+ *
+ * @param parserFactoryClassNames - a <code>Vector</code> of
+ * <code>String</code> objects containing the names of the parser
+ * Factory Classes
+ * @throws FactoryConfigurationError if thrown from <code>getFactory</code>
+ */
+ private void registerDOMParsers(Vector parserFactoryClassNames)
+ throws FactoryConfigurationError {
+ if (parserFactoryClassNames != null) {
+ Enumeration e = parserFactoryClassNames.elements();
+ int index = 0;
+ while (e.hasMoreElements()) {
+ String parserFactoryClassName = (String) e.nextElement();
+ // create a dom parser factory just to get it's default
+ // properties. It will never be used since
+ // this class will operate as a service factory and give each
+ // service requestor it's own DocumentBuilderFactory
+ DocumentBuilderFactory factory = (DocumentBuilderFactory) getFactory(parserFactoryClassName);
+ Hashtable properties = new Hashtable(7);
+ // figure out the default properties of the parser
+ setDefaultDOMProperties(factory, properties, index);
+ // store the parser factory class name in the properties so that
+ // it can be retrieved when getService is called
+ // to return a parser factory
+ properties.put(FACTORYNAMEKEY, parserFactoryClassName);
+ // release the factory
+ factory = null;
+ // register the factory as a service
+ context.registerService(DOMFACTORYNAME, this, properties);
+ index++;
+ }
+ }
+ }
+
+ /**
+ * Set the DOM parser service properties.
+ *
+ * By default, the following properties are set:
+ * <ul>
+ * <li><code>SERVICE_DESCRIPTION</code>
+ * <li><code>SERVICE_PID</code>
+ * <li><code>PARSER_VALIDATING</code>
+ * <li><code>PARSER_NAMESPACEAWARE</code>
+ * <ul>
+ *
+ * @param factory The <code>DocumentBuilderFactory</code> object
+ * @param props <code>Hashtable</code> of service properties.
+ */
+ private void setDefaultDOMProperties(DocumentBuilderFactory factory,
+ Hashtable props, int index) {
+ props.put(Constants.SERVICE_DESCRIPTION, DOMFACTORYDESCRIPTION);
+ props.put(Constants.SERVICE_PID, DOMFACTORYNAME + "."
+ + context.getBundle().getBundleId() + "." + index);
+ setDOMProperties(factory, props);
+ }
+
+ /**
+ * <p>
+ * Set the customizable DOM Parser Service Properties.
+ *
+ * <p>
+ * This method attempts to instantiate a validating parser and a
+ * namespaceaware parser to determine if the parser can support those
+ * features. The appropriate properties are then set in the specified props
+ * object.
+ *
+ * <p>
+ * This method can be overridden to add additional DOM2 features and
+ * properties. If you want to be able to filter searches of the OSGi service
+ * registry, this method must put a key, value pair into the properties
+ * object for each feature or property. For example,
+ *
+ * properties.put("http://www.acme.com/features/foo", Boolean.TRUE);
+ *
+ * @param factory - the DocumentBuilderFactory object
+ * @param props - Hashtable of service properties.
+ */
+ public void setDOMProperties(DocumentBuilderFactory factory, Hashtable props) {
+ // check if this parser can be configured to validate
+ boolean validating = true;
+ factory.setValidating(true);
+ factory.setNamespaceAware(false);
+ try {
+ factory.newDocumentBuilder();
+ }
+ catch (Exception pce_val) {
+ validating = false;
+ }
+ // check if this parser can be configured to be namespaceaware
+ boolean namespaceaware = true;
+ factory.setValidating(false);
+ factory.setNamespaceAware(true);
+ try {
+ factory.newDocumentBuilder();
+ }
+ catch (Exception pce_nsa) {
+ namespaceaware = false;
+ }
+ // set the factory values
+ factory.setValidating(validating);
+ factory.setNamespaceAware(namespaceaware);
+ // set the OSGi service properties
+ props.put(PARSER_VALIDATING, new Boolean(validating));
+ props.put(PARSER_NAMESPACEAWARE, new Boolean(namespaceaware));
+ }
+
+ /**
+ * Given a parser factory class name, instantiate that class.
+ *
+ * @param parserFactoryClassName A <code>String</code> object containing
+ * the name of the parser factory class
+ * @return a parserFactoryClass Object
+ * @pre parserFactoryClassName!=null
+ */
+ private Object getFactory(String parserFactoryClassName)
+ throws FactoryConfigurationError {
+ Exception e = null;
+ try {
+ return Class.forName(parserFactoryClassName).newInstance();
+ }
+ catch (ClassNotFoundException cnfe) {
+ e = cnfe;
+ }
+ catch (InstantiationException ie) {
+ e = ie;
+ }
+ catch (IllegalAccessException iae) {
+ e = iae;
+ }
+ throw new FactoryConfigurationError(e);
+ }
+
+ /**
+ * Creates a new XML Parser Factory object.
+ *
+ * <p>
+ * A unique XML Parser Factory object is returned for each call to this
+ * method.
+ *
+ * <p>
+ * The returned XML Parser Factory object will be configured for validating
+ * and namespace aware support as specified in the service properties of the
+ * specified ServiceRegistration object.
+ *
+ * This method can be overridden to configure additional features in the
+ * returned XML Parser Factory object.
+ *
+ * @param bundle The bundle using the service.
+ * @param registration The <code>ServiceRegistration</code> object for the
+ * service.
+ * @return A new, configured XML Parser Factory object or null if a
+ * configuration error was encountered
+ */
+ public Object getService(Bundle bundle, ServiceRegistration registration) {
+ ServiceReference sref = registration.getReference();
+ String parserFactoryClassName = (String) sref
+ .getProperty(FACTORYNAMEKEY);
+ try {
+ // need to set factory properties
+ Object factory = getFactory(parserFactoryClassName);
+ if (factory instanceof SAXParserFactory) {
+ ((SAXParserFactory) factory).setValidating(((Boolean) sref
+ .getProperty(PARSER_VALIDATING)).booleanValue());
+ ((SAXParserFactory) factory).setNamespaceAware(((Boolean) sref
+ .getProperty(PARSER_NAMESPACEAWARE)).booleanValue());
+ }
+ else
+ if (factory instanceof DocumentBuilderFactory) {
+ ((DocumentBuilderFactory) factory)
+ .setValidating(((Boolean) sref
+ .getProperty(PARSER_VALIDATING))
+ .booleanValue());
+ ((DocumentBuilderFactory) factory)
+ .setNamespaceAware(((Boolean) sref
+ .getProperty(PARSER_NAMESPACEAWARE))
+ .booleanValue());
+ }
+ return factory;
+ }
+ catch (FactoryConfigurationError fce) {
+ fce.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Releases a XML Parser Factory object.
+ *
+ * @param bundle The bundle releasing the service.
+ * @param registration The <code>ServiceRegistration</code> object for the
+ * service.
+ * @param service The XML Parser Factory object returned by a previous call
+ * to the <code>getService</code> method.
+ */
+ public void ungetService(Bundle bundle, ServiceRegistration registration,
+ Object service) {
+ }
+}
diff --git a/deploymentadmin/src/main/java/org/osgi/util/xml/package.html b/deploymentadmin/src/main/java/org/osgi/util/xml/package.html
new file mode 100644
index 0000000..45e61bb
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/xml/package.html
@@ -0,0 +1,10 @@
+<!-- $Header: /cvshome/build/org.osgi.util.xml/src/org/osgi/util/xml/package.html,v 1.2 2006/07/12 21:06:59 hargrave Exp $ -->
+<BODY>
+<p>XML Parser Package Version 1.0.
+<p>Bundles wishing to use this package must list the package
+in the Import-Package header of the bundle's manifest.
+For example:
+<pre>
+Import-Package: org.osgi.util.xml; version=1.0
+</pre>
+</BODY>
diff --git a/deploymentadmin/src/main/java/org/osgi/util/xml/packageinfo b/deploymentadmin/src/main/java/org/osgi/util/xml/packageinfo
new file mode 100644
index 0000000..a4f1546
--- /dev/null
+++ b/deploymentadmin/src/main/java/org/osgi/util/xml/packageinfo
@@ -0,0 +1 @@
+version 1.0
\ No newline at end of file