moved new dm4 from sandbox to trunk.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1663056 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/dependencymanager/org.apache.felix.dependencymanager/.classpath b/dependencymanager/org.apache.felix.dependencymanager/.classpath
new file mode 100644
index 0000000..f89ae43
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/.classpath
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" output="bin" path="src"/>
+ <classpathentry kind="src" output="bin_test" path="test"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+ <classpathentry kind="con" path="aQute.bnd.classpath.container"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/dependencymanager/org.apache.felix.dependencymanager/.gitignore b/dependencymanager/org.apache.felix.dependencymanager/.gitignore
new file mode 100644
index 0000000..90dde36
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/.gitignore
@@ -0,0 +1,3 @@
+/bin/
+/bin_test/
+/generated/
diff --git a/dependencymanager/org.apache.felix.dependencymanager/.project b/dependencymanager/org.apache.felix.dependencymanager/.project
new file mode 100644
index 0000000..8640bab
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/.project
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.apache.felix.dependencymanager</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>bndtools.core.bndbuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>bndtools.core.bndnature</nature>
+ </natures>
+</projectDescription>
diff --git a/dependencymanager/org.apache.felix.dependencymanager/.settings/org.eclipse.jdt.core.prefs b/dependencymanager/org.apache.felix.dependencymanager/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..7341ab1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd b/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd
new file mode 100644
index 0000000..435bc97
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/bnd.bnd
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+-buildpath: \
+ osgi.core;version=4.2,\
+ osgi.cmpn;version=4.2,\
+ org.mockito.mockito-all;version=1.9,\
+ ${junit}
+Private-Package: \
+ org.apache.felix.dm.impl,\
+ org.apache.felix.dm.impl.index,\
+ org.apache.felix.dm.impl.index.multiproperty,\
+ org.apache.felix.dm.impl.metatype
+Export-Package: \
+ org.apache.felix.dm,\
+ org.apache.felix.dm.tracker,\
+ org.apache.felix.dm.context
+Include-Resource: META-INF/=resources/LICENSE,\
+ META-INF/=resources/NOTICE,\
+ META-INF/=resources/DEPENDENCIES,\
+ META-INF/=resources/changelog.txt
+Import-Package: !org.junit,!org.mockito.*,*
+Bundle-Activator: org.apache.felix.dm.impl.Activator
+Bundle-Version: 4.0.0
+Bundle-Name: Apache Felix Dependency Manager
+Bundle-Description: Provides dynamic service and component dependency management
+Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
+Bundle-DocURL: http://felix.apache.org/documentation/subprojects/apache-felix-dependency-manager.html
+Bundle-Vendor: The Apache Software Foundation
+Bundle-Category: osgi
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/design.txt b/dependencymanager/org.apache.felix.dependencymanager/design.txt
new file mode 100644
index 0000000..94dfce7
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/design.txt
@@ -0,0 +1,47 @@
+7 feb 2014 (marrs & uiterlix):
+
+This prototype demonstrates the new concurrency principles that form the basis for the DM:
+
+ * All external events that influence the state of dependencies are recorded and given
+ to the serial executor of the component. We record whatever data comes in, so when the
+ actual job is run by the serial executor, we still have access to the original data
+ without having to access other sources whose state might have changed since.
+ * The serial executor of a component will execute a job immediately if it is being called
+ by the thread that is already executing jobs.
+ * If the serial executor of a component had not yet started a job, it will queue and start
+ it on the current thread.
+ * If the serial executor gets invoked from a different thread than the one currently
+ executing jobs, the job will be put at the end of the queue. As mentioned before, any
+ data associated with the event will also be recorded so it is available when the job
+ executes.
+ * State in the component and dependency can only be modified via the serial executor
+ thread. This means we don't need explicit synchronization anywhere.
+
+20 sept 2014 (pderop):
+
+ * Added support for parallelism: To allow components to be started and handled in parallel, you can
+ now register in the OSGi service registry a ComponentExecutorFactory service that is used to get
+ an Executor for the management of all components dependencies/lifecycle callbacks. See javadoc
+ from the org.apache.felix.dm.ComponentExecutorFactory interface for more informations.
+
+ You can also take a look at the the org.apache.felix.dependencymanager.samples project, which is
+ registering a ComponentExecutorFactory from org.apache.felix.dependencymanager.samples.tpool
+ bundle.
+
+ See also the following property in the org.apache.felix.dependencymanager.samples/bnd.bnd
+
+ org.apache.felix.dependencymanager.parallel=\
+ '!org.apache.felix.dependencymanager.samples.tpool, *',\
+
+ Here, all components will be handled by Executors provided by the ComponentExecutorFactory,
+ except those having a package starting with "org.apache.felix.dependencymanager.samples.tpool"
+ (because the threadpool is itself defined using the Dependency Manager API).
+
+
+
+
+
+
+
+
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager/resources/DEPENDENCIES b/dependencymanager/org.apache.felix.dependencymanager/resources/DEPENDENCIES
new file mode 100644
index 0000000..bb3ee11
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/resources/DEPENDENCIES
@@ -0,0 +1,24 @@
+Apache Felix Dependency Manager
+Copyright 2011-2015 The Apache Software Foundation
+
+This software was developed at the Apache Software Foundation
+(http://www.apache.org) and may have dependencies on other
+Apache software licensed under Apache License 2.0.
+
+I. Included Third-Party Software
+
+This product includes software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2014).
+Licensed under the Apache License 2.0.
+
+II. Used Third-Party Software
+
+This product uses software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2014).
+Licensed under the Apache License 2.0.
+
+III. Overall License Summary
+
+- Apache License 2.0
diff --git a/dependencymanager/org.apache.felix.dependencymanager/resources/LICENSE b/dependencymanager/org.apache.felix.dependencymanager/resources/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/resources/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/dependencymanager/org.apache.felix.dependencymanager/resources/NOTICE b/dependencymanager/org.apache.felix.dependencymanager/resources/NOTICE
new file mode 100644
index 0000000..7df085c
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/resources/NOTICE
@@ -0,0 +1,12 @@
+Apache Felix Dependency Manager
+Copyright 2011-2015 The Apache Software Foundation
+
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+Licensed under the Apache License 2.0.
+
+This product includes software developed at
+The OSGi Alliance (http://www.osgi.org/).
+Copyright (c) OSGi Alliance (2000, 2015).
+Licensed under the Apache License 2.0.
diff --git a/dependencymanager/org.apache.felix.dependencymanager/resources/changelog.txt b/dependencymanager/org.apache.felix.dependencymanager/resources/changelog.txt
new file mode 100644
index 0000000..3cda464
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/resources/changelog.txt
@@ -0,0 +1,85 @@
+Release 4.0.0:
+-------------
+
+FELIX-2706: Support callback delegation for Configuration Dependencies
+FELIX-3914: Log unsuccessful field injections
+FELIX-4158: ComponentDeclaration should give access to component information
+FELIX-4304: DependencyManager ComponentImpl should not assume all service properties are stored in a Hashtable
+FELIX-4394: Race problems in DependencyManager Configuration Dependency
+FELIX-4426: Allow DM to manage collections of services
+FELIX-4588: createCopy method ConfigurationDependency produces a malfunctioning clone
+FELIX-4594: Propagation from dependencies overwrites service properties
+FELIX-4598: BundleDependency can effectively track only one bundle
+FELIX-4600: Cherrypicking of propagated properties
+FELIX-4602: TemporalServiceDependency does not properly propagate RuntimeExceptions
+FELIX-4667: "top" command for the Dependency Manager Shell
+FELIX-4672: Allow callbacks to third party instance for adapters
+FELIX-4673: Log any error thrown when trying to create a null object
+FELIX-4680: Add more DM ServiceDependency callback signatures
+FELIX-4807: New thread model for Dependency Manager
+
+Release 3.2.0:
+--------------
+
+FELIX-3910: Race conditions in DependencyManager
+FELIX-4334: ServiceDependency properties change callback issue
+FELIX-4285: Remove abstract modifier from DependencyActivatorBase.destroy()
+FELIX-4294: Dependency Manager Shell improvements
+FELIX-4305: DependencyMananer Adapters - service properties propagation
+FELIX-4002: ComponentStateListener.started is invoked twice when the listener is added in the start method.
+FELIX-4395: DependencyManager Configuration Dependency does not clone some class fields.
+FELIX-4014: handleAspectAwareRemoved in ServiceDependencyImpl can cause a possible deadlock
+FELIX-4097: Allow debug logging for specific instances of service dependencies to debug wiring issues.
+FELIX-4098: Aspect swap sometimes invokes the callbacks in the wrong order in a multithreaded application.
+FELIX-4099: Add support for negations in the multi property filter index.
+FELIX-4186: NPE in DependencyManager Logger
+FELIX-4226: Add option to have the dependency manager log against a single BundleContext's LogService.
+FELIX-4361: Possible ConcurrentModificationException in DependencyManager.getComponents()
+
+Release 3.1.0
+-------------
+
+FELIX-303 - Support for compositions
+FELIX-1201 - Issue with DM and CM
+FELIX-1278 - DM/ AutoConfig is active event if setCallbacks method has been invoked
+FELIX-1464 - issue when using a negation in ldap service dependency filter
+FELIX-1546 - DM/Temporal Dependency/Bound Service Replacement features
+FELIX-2078 - Not all callbacks invoked when declaring a service as required and starting it after dependencies
+FELIX-2344 - DM / callback method is not invoked when an extra dependency is defined within an "init" component method.
+FELIX-2348 - DM/ ResourceAdapter NPE
+FELIX-2369 - DM/ Service start method is invoked even if an extra required dependency is unavailable
+FELIX-2816 - dependency manager calls init() twice
+FELIX-2947 - Filter indices must use service trackers that track all services and aspects.
+FELIX-2953 - Make the cache that InvocationUtil uses configurable.
+FELIX-2954 - DM/ annotated component factory does not allow to provide a component instance explicitly
+FELIX-2955 - IllegalStateException when doing a 'dm notavail' shell command.
+FELIX-2956 - DM/ json should be embedded in the annotation scanner plugin
+FELIX-2964 - DM/ NPE on some dependency manager adapters, when "auto-configuration" mode is disabled.
+FELIX-2970 - DM/ Factory Configuration Adapter Service does not copy adapter dependencies
+FELIX-2976 - InvocationUtil cache is not used properly for determining that methods do not exist in a class
+FELIX-2987 - DependencyManager ConfigurationDependency update isn't propagated to super classes
+FELIX-3008 - NPE in ServiceRegistryCache when dependency manager bundle is not started first
+FELIX-3042 - [PATCH] Add a convenience clear() method on DependencyManager
+FELIX-3057 - getServiceReferences() should not return an empty array
+FELIX-3186 - Adapter services do not get their adapted services transparently replaced when an aspect is added to them.
+FELIX-3201 - Offer more functional callback methods for services that have aspects on them.
+FELIX-3218 - ServiceTracker performance is not optimal with a service dependency that results in n-thousands of injected services.
+FELIX-3264 - Dependency manager shell should not print the state of optional dependencies when not all required ones are available
+FELIX-3292 - Allow passing of resource properties to a resource handler for use with resource adapters.
+FELIX-3337 - DependencyManager/Updated configuration dependency does not propagate to provided service properties
+FELIX-3402 - DependencyManager stop can trigger IndexOutOfBoundsException
+FELIX-3423 - AdapterImpl copies the DependencyManager.ASPECT service property when adapting an aspect.
+FELIX-3424 - Add support for changed callbacks on Aspect services.
+FELIX-3425 - Provide a filter index for adapter services.
+FELIX-3475 - DependencyManager compatibility bundle - ServiceDependencyImpl does not override toString
+FELIX-3564 - Memory leak in Filterindex / ServiceRegistryCache
+FELIX-3592 - ServiceDependencyImpl does not copy the swapped callback in it's constructor.
+FELIX-3617 - Missing toString methods in DependencyManager compat bundle
+FELIX-3682 - Dependency Manager Annotation-3.0.0 module doesn't expose OSGI meta information in MANIFEST.MF
+FELIX-3828 - Aspect and Adapter filter indices to not handle components that have been bound with multiple interfaces correctly.
+
+
+Release 3.0.0
+-------------
+
+Major, backward incompatible release. Start of recorded changes.
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/.gitignore b/dependencymanager/org.apache.felix.dependencymanager/src/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/.gitignore
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/BundleDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/BundleDependency.java
new file mode 100644
index 0000000..76bb73b
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/BundleDependency.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.dm;
+
+import org.osgi.framework.Bundle;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface BundleDependency extends Dependency, ComponentDependencyDeclaration {
+ /**
+ * Sets the callbacks for this dependency. These callbacks can be used as hooks whenever a dependency is added or removed.
+ * When you specify callbacks, the auto configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ *
+ * @param added the method to call when a bundle was added
+ * @param removed the method to call when a bundle was removed
+ * @return the bundle dependency
+ */
+ public BundleDependency setCallbacks(String added, String removed);
+
+ /**
+ * Sets the callbacks for this dependency. These callbacks can be used as hooks whenever a dependency is added, changed or
+ * removed. When you specify callbacks, the auto configuration feature is automatically turned off, because we're assuming
+ * you don't need it in this case.
+ *
+ * @param added the method to call when a bundle was added
+ * @param changed the method to call when a bundle was changed
+ * @param removed the method to call when a bundle was removed
+ * @return the bundle dependency
+ */
+ public BundleDependency setCallbacks(String added, String changed, String removed);
+
+ /**
+ * Sets the callbacks for this dependency. These callbacks can be used as hooks whenever a dependency is added or removed.
+ * They are called on the instance you provide. When you specify callbacks, the auto configuration feature is automatically
+ * turned off, because we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a bundle was added
+ * @param removed the method to call when a bundle was removed
+ * @return the bundle dependency
+ */
+ public BundleDependency setCallbacks(Object instance, String added, String removed);
+
+ /**
+ * Sets the callbacks for this dependency. These callbacks can be used as hooks whenever a dependency is added, changed or
+ * removed. They are called on the instance you provide. When you specify callbacks, the auto configuration feature is
+ * automatically turned off, because we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a bundle was added
+ * @param changed the method to call when a bundle was changed
+ * @param removed the method to call when a bundle was removed
+ * @return the bundle dependency
+ */
+ public BundleDependency setCallbacks(Object instance, String added, String changed, String removed);
+
+ /**
+ * Enables auto configuration for this dependency. This means the component implementation (composition) will be
+ * injected with this bundle dependency automatically.
+ *
+ * @param autoConfig <code>true</code> to enable auto configuration
+ * @return the bundle dependency
+ */
+ public BundleDependency setAutoConfig(boolean autoConfig);
+
+ /**
+ * Sets the dependency to be required.
+ *
+ * @param required <code>true</code> if this bundle dependency is required
+ * @return the bundle dependency
+ */
+ public BundleDependency setRequired(boolean required);
+
+ /**
+ * Sets the bundle to depend on directly.
+ *
+ * @param bundle the bundle to depend on
+ * @return the bundle dependency
+ */
+ public BundleDependency setBundle(Bundle bundle);
+
+ /**
+ * Sets the filter condition to depend on. Filters are matched against the full manifest of a bundle.
+ *
+ * @param filter the filter condition
+ * @return the bundle dependency
+ * @throws IllegalArgumentException if the filter is invalid
+ */
+ public BundleDependency setFilter(String filter) throws IllegalArgumentException;
+
+ /**
+ * Sets the bundle state mask to depend on. The OSGi BundleTracker explains this mask in more detail, but
+ * it is basically a mask with flags for each potential state a bundle can be in.
+ *
+ * @param mask the mask to use
+ * @return the bundle dependency
+ */
+ public BundleDependency setStateMask(int mask);
+
+ /**
+ * Sets property propagation. If set to <code>true</code> any bundle manifest properties will be added
+ * to the service properties of the component that has this dependency (if it registers as a service).
+ *
+ * @param propagate <code>true</code> to propagate the bundle manifest properties
+ * @return the bundle dependency
+ */
+ public BundleDependency setPropagate(boolean propagate);
+
+ /**
+ * Sets an Object instance and a callback method used to propagate some properties to the provided service properties.
+ * The method will be invoked on the specified object instance and must have one of the following signatures:
+ * <ul><li>Dictionary callback(ServiceReference, Object service)
+ * <li>Dictionary callback(ServiceReference)
+ * </ul>
+ * @param instance the Object instance which is used to retrieve propagated service properties
+ * @param method the method to invoke for retrieving the properties to be propagated to the service properties.
+ * @return this service dependency.
+ */
+ public BundleDependency setPropagate(Object instance, String method);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Component.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Component.java
new file mode 100644
index 0000000..0b618ad
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Component.java
@@ -0,0 +1,292 @@
+/*
+ * 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.dm;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.ServiceRegistration;
+
+
+/**
+ * Component interface. Components are the main building blocks for OSGi applications.
+ * They can publish themselves as a service, and they can have dependencies. These
+ * dependencies will influence their life cycle as component will only be activated
+ * when all required dependencies are available.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface Component {
+ /**
+ * Sets the implementation for this component. You can actually specify
+ * an instance you have instantiated manually, or a <code>Class</code>
+ * that will be instantiated using its default constructor when the
+ * required dependencies are resolved, effectively giving you a lazy
+ * instantiation mechanism.
+ *
+ * There are four special methods that are called when found through
+ * reflection to give you life cycle management options:
+ * <ol>
+ * <li><code>init()</code> is invoked after the instance has been
+ * created and dependencies have been resolved, and can be used to
+ * initialize the internal state of the instance or even to add more
+ * dependencies based on runtime state</li>
+ * <li><code>start()</code> is invoked right before the service is
+ * registered</li>
+ * <li><code>stop()</code> is invoked right after the service is
+ * unregistered</li>
+ * <li><code>destroy()</code> is invoked after all dependencies are
+ * removed</li>
+ * </ol>
+ * In short, this allows you to initialize your instance before it is
+ * registered, perform some post-initialization and pre-destruction code
+ * as well as final cleanup. If a method is not defined, it simply is not
+ * called, so you can decide which one(s) you need. If you need even more
+ * fine-grained control, you can register as a service state listener too.
+ *
+ * @param implementation the implementation
+ * @return this component
+ * @see ComponentStateListener
+ */
+ public Component setImplementation(Object implementation);
+
+ /**
+ * Adds dependency(ies) to this component, atomically. If the component is already active or if you add
+ * dependencies from the init method, then you should add all the dependencies in one single add method call
+ * (using the varargs argument), because this method may trigger component activation (like
+ * the ServiceTracker.open() method does).
+ *
+ * @param dependencies the dependencies to add.
+ * @return this component
+ */
+ public Component add(Dependency ... dependencies);
+
+ /**
+ * Removes a dependency from the component.
+ * @param d the dependency to remove
+ * @return this component
+ */
+ public Component remove(Dependency d);
+
+ /**
+ * Adds a component state listener to this component.
+ *
+ * @param listener the state listener
+ */
+ public Component add(ComponentStateListener listener);
+
+ /**
+ * Removes a component state listener from this component.
+ *
+ * @param listener the state listener
+ */
+ public Component remove(ComponentStateListener listener);
+
+ /**
+ * Sets the public interface under which this component should be registered
+ * in the OSGi service registry.
+ *
+ * @param serviceName the name of the service interface
+ * @param properties the properties for this service
+ * @return this component
+ */
+ public Component setInterface(String serviceName, Dictionary<?,?> properties);
+
+ /**
+ * Sets the public interfaces under which this component should be registered
+ * in the OSGi service registry.
+ *
+ * @param serviceNames the names of the service interface
+ * @param properties the properties for these services
+ * @return this component
+ */
+ public Component setInterface(String[] serviceNames, Dictionary<?, ?> properties);
+
+ /**
+ * Configures auto configuration of injected classes in the component instance.
+ * The following injections are currently performed, unless you explicitly
+ * turn them off:
+ * <dl>
+ * <dt>BundleContext</dt><dd>the bundle context of the bundle</dd>
+ * <dt>ServiceRegistration</dt><dd>the service registration used to register your service</dd>
+ * <dt>DependencyManager</dt><dd>the dependency manager instance</dd>
+ * <dt>Component</dt><dd>the component instance of the dependency manager</dd>
+ * </dl>
+ *
+ * @param clazz the class (from the list above)
+ * @param autoConfig <code>false</code> to turn off auto configuration
+ */
+ public Component setAutoConfig(Class<?> clazz, boolean autoConfig);
+
+ /**
+ * Configures auto configuration of injected classes in the component instance.
+ *
+ * @param clazz the class (from the list above)
+ * @param instanceName the name of the instance to inject the class into
+ * @see #setAutoConfig(Class, boolean)
+ */
+ public Component setAutoConfig(Class<?> clazz, String instanceName);
+
+ /**
+ * Returns the service registration for this component. The method
+ * will return <code>null</code> if no service registration is
+ * available, for example if this component is not registered as a
+ * service at all.
+ *
+ * @return the service registration
+ */
+ public ServiceRegistration getServiceRegistration();
+
+ /**
+ * Returns the instance that make up this component. If the component has a composition of instances,
+ * then the first instance of the composition is returned. Null is returned if the component has not
+ * even been instantiated.
+ *
+ * @return the component instances
+ */
+ public <T> T getInstance();
+
+ /**
+ * Returns the composition instances that make up this component, or just the
+ * component instance if it does not have a composition, or an empty array if
+ * the component has not even been instantiated.
+ *
+ * @return the component instances
+ */
+ public Object[] getInstances();
+
+ /**
+ * Returns the service properties associated with the component.
+ *
+ * @return the properties or <code>null</code> if there are none
+ */
+ public <K,V> Dictionary<K,V> getServiceProperties();
+
+ /**
+ * Sets the service properties associated with the component. If the service
+ * was already registered, it will be updated.
+ *
+ * @param serviceProperties the properties
+ */
+ public Component setServiceProperties(Dictionary<?, ?> serviceProperties);
+
+ /**
+ * Sets the names of the methods used as callbacks. These methods, when found, are
+ * invoked as part of the life cycle management of the component implementation. The
+ * dependency manager will look for a method of this name with the following signatures,
+ * in this order:
+ * <ol>
+ * <li>method(Component component)</li>
+ * <li>method()</li>
+ * </ol>
+ *
+ * @param init the name of the init method
+ * @param start the name of the start method
+ * @param stop the name of the stop method
+ * @param destroy the name of the destroy method
+ * @return the component
+ */
+ public Component setCallbacks(String init, String start, String stop, String destroy);
+
+ /**
+ * Sets the names of the methods used as callbacks. These methods, when found, are
+ * invoked on the specified instance as part of the life cycle management of the component
+ * implementation.
+ * <p>
+ * See setCallbacks(String init, String start, String stop, String destroy) for more
+ * information on the signatures. Specifying an instance means you can create a manager
+ * that will be invoked whenever the life cycle of a component changes and this manager
+ * can then decide how to expose this life cycle to the actual component, offering an
+ * important indirection when developing your own component models.
+ *
+ * @return this component
+ */
+ public Component setCallbacks(Object instance, String init, String start, String stop, String destroy);
+
+ /**
+ * Sets the factory to use to create the implementation. You can specify
+ * both the factory class and method to invoke. The method should return
+ * the implementation, and can use any method to create it. Actually, this
+ * can be used together with <code>setComposition</code> to create a
+ * composition of instances that work together to implement a component. The
+ * factory itself can also be instantiated lazily by not specifying an
+ * instance, but a <code>Class</code>.
+ *
+ * @param factory the factory instance or class
+ * @param createMethod the name of the create method
+ * @return this component
+ */
+ public Component setFactory(Object factory, String createMethod);
+
+ /**
+ * Sets the factory to use to create the implementation. You specify the
+ * method to invoke. The method should return the implementation, and can
+ * use any method to create it. Actually, this can be used together with
+ * <code>setComposition</code> to create a composition of instances that
+ * work together to implement a component.
+ * <p>
+ * Note that currently, there is no default for the factory, so please use
+ * <code>setFactory(factory, createMethod)</code> instead.
+ *
+ * @param createMethod the name of the create method
+ * @return this component
+ */
+ public Component setFactory(String createMethod);
+
+ /**
+ * Sets the instance and method to invoke to get back all instances that
+ * are part of a composition and need dependencies injected. All of them
+ * will be searched for any of the dependencies. The method that is
+ * invoked must return an <code>Object[]</code>.
+ *
+ * @param instance the instance that has the method
+ * @param getMethod the method to invoke
+ * @return this component
+ */
+ public Component setComposition(Object instance, String getMethod);
+
+ /**
+ * Sets the method to invoke on the service implementation to get back all
+ * instances that are part of a composition and need dependencies injected.
+ * All of them will be searched for any of the dependencies. The method that
+ * is invoked must return an <code>Object[]</code>.
+ *
+ * @param getMethod the method to invoke
+ * @return this component
+ */
+ public Component setComposition(String getMethod);
+
+ /**
+ * Returns the dependency manager associated with this component.
+ * @return the dependency manager associated with this component.
+ */
+ public DependencyManager getDependencyManager();
+
+ /**
+ * Returns the component description (dependencies, service provided, etc ...).
+ * @return the component description (dependencies, service provided, etc ...).
+ */
+ public ComponentDeclaration getComponentDeclaration();
+
+ /**
+ * Activate debug for this component. Informations related to dependency processing will be displayed
+ * using osgi log service, our to standard output if no log service is currently available.
+ * @param label
+ */
+ public Component setDebug(String label);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentDeclaration.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentDeclaration.java
new file mode 100644
index 0000000..85353ef
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentDeclaration.java
@@ -0,0 +1,61 @@
+/*
+ * 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.dm;
+
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+
+/**
+ * Describes a component. Component declarations form descriptions of components
+ * that are managed by the dependency manager. They can be used to query their state
+ * for monitoring tools. The dependency manager shell command is an example of
+ * such a tool.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ComponentDeclaration {
+ /** Names for the states of this component. */
+ public static final String[] STATE_NAMES = { "unregistered", "registered" };
+ /** State constant for an unregistered component. */
+ public static final int STATE_UNREGISTERED = 0;
+ /** State constant for a registered component. */
+ public static final int STATE_REGISTERED = 1;
+ /** Returns a list of dependencies associated with this component. */
+ public ComponentDependencyDeclaration[] getComponentDependencies();
+ /** Returns the description of this component (the classname or the provided service(s)) */
+ public String getName();
+ /** Returns the class name of the Component implementation. */
+ public String getClassName();
+ /** Returns the service optionally provided by this component, or null */
+ public String[] getServices();
+ /** Returns the service properties, or null */
+ public <K,V> Dictionary<K, V> getServiceProperties();
+ /** Returns the state of this component. */
+ public int getState();
+ /** Returns the instance id of this component. */
+ public long getId();
+ /** Returns the bundle context associated with this component. */
+ public BundleContext getBundleContext();
+ /** Returns the dependency manager for this component */
+ public DependencyManager getDependencyManager();
+ /** Returns the execution time in nanos for each component callbacks (init/start/stop/destroy) */
+ public Map<String, Long> getCallbacksTime();
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentDependencyDeclaration.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentDependencyDeclaration.java
new file mode 100644
index 0000000..b86f390
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentDependencyDeclaration.java
@@ -0,0 +1,61 @@
+/*
+ * 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.dm;
+
+/**
+ * Describes a component dependency. They form descriptions of dependencies
+ * that are managed by the dependency manager. They can be used to query their state
+ * for monitoring tools. The dependency manager shell command is an example of
+ * such a tool.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ComponentDependencyDeclaration {
+ /** Names for the states of this dependency. */
+ public static final String[] STATE_NAMES = {
+ "optional unavailable",
+ "optional available",
+ "required unavailable",
+ "required available",
+ "optional (not tracking)",
+ "required (not tracking)"
+ };
+ /** State constant for an unavailable, optional dependency. */
+ public static final int STATE_UNAVAILABLE_OPTIONAL = 0;
+ /** State constant for an available, optional dependency. */
+ public static final int STATE_AVAILABLE_OPTIONAL = 1;
+ /** State constant for an unavailable, required dependency. */
+ public static final int STATE_UNAVAILABLE_REQUIRED = 2;
+ /** State constant for an available, required dependency. */
+ public static final int STATE_AVAILABLE_REQUIRED = 3;
+ /** State constant for an optional dependency that has not been started yet. */
+ public static final int STATE_OPTIONAL = 4;
+ /** State constant for a required dependency that has not been started yet. */
+ public static final int STATE_REQUIRED = 5;
+ /** Returns the name of this dependency (a generic name with optional info separated by spaces)*/
+ public String getName();
+ /** Returns the simple dependency name (service classname for example) */
+ public String getSimpleName();
+ /** Returns the Dependency filter or null */
+ public String getFilter();
+ /** Returns the name of the type of this dependency. */
+ public String getType();
+ /** Returns the state of this dependency. */
+ public int getState();
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentExecutorFactory.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentExecutorFactory.java
new file mode 100644
index 0000000..17bdbfd
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentExecutorFactory.java
@@ -0,0 +1,171 @@
+/*
+ * 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.dm;
+
+import java.util.concurrent.Executor;
+
+/**
+ * A <code>ComponentExecutorFactory</code> service can be registered by any management agent bundle
+ * in order to enable parallel activation of Components.<p>
+ *
+ * A <code>ComponentExecutorFactory</code> is part of the new concurrency model that forms the basis
+ * of Dependency Manager 4.0. Let's first give a brief overview of the default thread model used when
+ * no ComponentExecutorFactory is used. Then we'll explain the rationale and the usage of a
+ * <code>ComponentExecutorFactory</code> service.
+ * <p>
+ *
+ * <h3>Default Thread Model</h3>
+ *
+ * By default, Dependency Manager uses a <b>lock-free/single thread</b> model:
+ * <p><ul>
+ *
+ * <li> When an external event that influence the state of a Component is taking place (for example,
+ * when a service dependency on which the Component is depending on is registered in the registry by
+ * a given thread), then DependencyManager does not perform any locking for the handling of the event.
+ * Instead of that, a job that will handle the event is inserted in an internal lock-free
+ * <b><code>Serial Queue</code></b> which is internally maintained in each Component.
+ *
+ * <li> all jobs scheduled in the <code>Serial Queue</code> are then executed in FIFO order, by the first
+ * thread which has triggered the first event. This avoid to use some blocking locks in DM internals, and
+ * also it simplifies the development of DM components, because all lifecycle callbacks
+ * (init/start/stop/destroy) and dependency injections are scheduled through the <code>Serial Queue</code>:
+ * This means that your component is not concurrently called in lifecycle callbacks and in dependency injection
+ * methods.
+ *
+ * <li> Now let's describe which thread is executing the jobs scheduled in a Component <code>Serial Queue</code>:
+ * When a job (J1) is scheduled in the queue while it is empty, then the current thread becomes the "master"
+ * and will immediately execute the </code>Serial Queue</code> tasks (synchronously). And if another thread
+ * triggers another event concurrently while the "master" thread is executing the job J1, then a job (J2)
+ * for this new event is just enqueued in the <code>Serial Queue</code>, but the other thread returns
+ * immediately to the caller, and the job J2 will then be executed by the "master" thread (after J1).
+ * </ul>
+ *
+ * <p>
+ * This mechanism allows to serially handle all Component events (service dependencies) in FIFO order
+ * without maintaining any locks.
+ *
+ * <h3>Enabling parallelism with a <code>ComponentExecutorFactory</code></h3>
+ *
+ * As described above, all the external events that influence the state of a given component are handed by
+ * jobs scheduled in the <code>Serial Queue</code> of the Component, and the jobs are getting executed serially
+ * by a single "master" thread. So usually, bundles are started from a single thread, meaning that all Components
+ * are then activated synchronously.
+ * <p>
+ *
+ * But when you register in the OSGi service registry a <code>ComponentExecutorFactory</code>, that factory
+ * will be used by DependencyManager to create an Executor of your choice for each Component, typically a shared
+ * threadpool configured by yourself. And all the Component <code>Serial Queues</code> will be executed using
+ * the Executor returned by the {@link #getExecutorFor(Component)} method.
+ * However, jobs scheduled in the <code>Serial Queue</code> of a given Component are still executed one at a
+ * time, in FIFO order and the Component remains single threaded, and <b>independent Components
+ * may then each be managed and activated concurrently with respect to each other</b>.
+ * <p>
+ * If you want to ensure that all Components are initialized <b>after</b> the ComponentExecutorFactory is
+ * registered in the OSGI registry, you can use the "org.apache.felix.dependencymanager.parallel" OSGi
+ * system property which specifies the list of components which must wait for the ComponentExecutorFactory
+ * service. This property value can be set to a wildcard ("*"), or a list of components implementation class
+ * prefixes (comma separated). So, all components whose class name starts with the specified prefixes will be cached
+ * until the ComponentExecutorFactory service is registered (In this way, it is not necessary to use
+ * the StartLevel service if you want to ensure that all components are started concurrently).
+ * <p>
+ *
+ * Some class name prefixes can also be negated (using "!"), in order to exclude some components from the
+ * list of components using the ComponentExecutorFactory service.
+ * <p>
+ *
+ * Notice that if the ComponentExecutorFactory itself and all its dependent services are defined using
+ * the Dependency Manager API, then you have to list the package of such components with a "!"
+ * prefix, in order to indicate that those components must not wait for a ComponentExecutorFactory service
+ * (since they are part of the ComponentExecutorFactory implementation !).
+ * <p>
+ *
+ * <h3>Examples for the usage of the "org.apache.felix.dependencymanager.parallel" property:</h3>
+ *
+ * <blockquote><pre>
+ * org.apache.felix.dependencymanager.parallel=*
+ * -> means all components must be cached until a ComponentExecutorFactory comes up.
+ *
+ * org.apache.felix.dependencymanager.parallel=foo.bar, foo.zoo
+ * -> means only components whose implementation class names are starting with "foo.bar" or "foo.zoo"
+ * must be handled using an Executor returned by the ComponentExecutorFactory service. Other Components
+ * will be handled normally, as when there is no ComponentExecutorFactory available.
+ *
+ * org.apache.felix.dependencymanager.parallel=!foo.threadpool, *
+ * -> means all components must be delayed until the ComponentExecutorFactory comes up, except the
+ * components whose implementations class names are starting with "foo.threadpool" prefix).
+ * </pre></blockquote>
+ *
+ * <h3>Examples of a ComponentExecutorFactory that provides a shared threadpool:</h3>
+ *
+ * First, we define the OSGi bundle context system property to enable parallelism for all DM Components
+ * excepts the one which declares the ComponentExecutorFactory:
+ *
+ * <blockquote> <pre>
+ * org.apache.felix.dependencymanager.parallel=!com.acme.management.threadpool, *
+ * </pre></blockquote>
+ *
+ * Next, here is the Activator which declares the ComponentExecutorFactory:
+ *
+ * <blockquote> <pre>
+ * package com.acme.management.threadpool;
+ * import org.apache.felix.dm.*;
+ *
+ * public class Activator extends DependencyActivatorBase {
+ * public void init(BundleContext context, DependencyManager mgr) throws Exception {
+ * mgr.add(createComponent()
+ * .setInterface(ComponentExecutorFactory.class.getName(), null)
+ * .setImplementation(ComponentExecutorFactoryImpl.class)
+ * .add(createConfigurationDependency()
+ * .setPid("com.acme.management.threadpool.ComponentExecutorFactoryImpl")));
+ * }
+ * }
+ * </pre></blockquote>
+ *
+ * And here is the implementation for our ComponentExecutorFactory:
+ *
+ * <blockquote> <pre>
+ * package com.acme.management.threadpool;
+ * import org.apache.felix.dm.*;
+ *
+ * public class ComponentExecutorFactoryImpl implements ComponentExecutorFactory {
+ * volatile Executor m_threadPool;
+ *
+ * void updated(Dictionary conf) {
+ * m_sharedThreadPool = Executors.newFixedThreadPool(Integer.parseInt("threadpool.size"));
+ * }
+ *
+ * @Override
+ * public Executor getExecutorFor(Component component) {
+ * return m_sharedThreadPool; // Use a shared threadpool for all Components
+ * }
+ * }
+ * </pre></blockquote>
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ * @since 4.0.0
+ */
+public interface ComponentExecutorFactory {
+ /**
+ * Returns an Executor (typically a shared thread pool) used to manage a given DependencyManager Component.
+ *
+ * @param component the Component to be managed by the returned Executor
+ * @return an Executor used to manage the given component, or null if the component must not be managed using any executor.
+ */
+ Executor getExecutorFor(Component component);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentState.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentState.java
new file mode 100644
index 0000000..1afb437
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentState.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm;
+
+/**
+ * Component states. Any state listeners registered using @link {@link Component#add(ComponentStateListener)} method
+ * are notified with the following stated whenever the component state changes.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public enum ComponentState {
+ /**
+ * The component is not currently started, and is inactive.
+ */
+ INACTIVE,
+
+ /**
+ * The component is waiting for some required dependencies.
+ */
+ WAITING_FOR_REQUIRED,
+
+ /**
+ * The component has all its initial required dependencies available, but is now waiting for some extra required
+ * dependencies which have been added after the component have been started (like from the component init method for example).
+ */
+ INSTANTIATED_AND_WAITING_FOR_REQUIRED,
+
+ /**
+ * The component is active, and is now tracking available optional dependencies.
+ */
+ TRACKING_OPTIONAL
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java
new file mode 100644
index 0000000..f3d3291
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ComponentStateListener.java
@@ -0,0 +1,31 @@
+/*
+ * 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.dm;
+
+/**
+ * This interface can be used to register a component state listener. Component
+ * state listeners are called whenever a component state changes. You get notified
+ * when the component is starting, started, stopping and stopped. Each callback
+ * includes a reference to the component in question.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ComponentStateListener {
+ public void changed(Component c, ComponentState state);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
new file mode 100644
index 0000000..a58b2ef
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ConfigurationDependency.java
@@ -0,0 +1,106 @@
+/*
+ * 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.dm;
+
+/**
+ * Configuration dependency that can track the availability of a (valid) configuration. To use
+ * it, specify a PID for the configuration. The dependency is always required, because if it is
+ * not, it does not make sense to use the dependency manager. In that scenario, simply register
+ * your component as a <code>ManagedService(Factory)</code> and handle everything yourself. Also,
+ * only managed services are supported, not factories. There are a couple of things you need to
+ * be aware of when implementing the <code>updated(Dictionary)</code> method:
+ * <ul>
+ * <li>Make sure it throws a <code>ConfigurationException</code> when you get a configuration
+ * that is invalid. In this case, the dependency will not change: if it was not available, it
+ * will still not be. If it was available, it will remain available and implicitly assume you
+ * keep working with your old configuration.</li>
+ * <li>This method will be called before all required dependencies are available. Make sure you
+ * do not depend on these to parse your settings.</li>
+ * </ul>
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ConfigurationDependency extends Dependency, ComponentDependencyDeclaration {
+ /**
+ * Sets the name of the callback method that should be invoked when a configuration
+ * is available. The contract for this method is identical to that of
+ * <code>ManagedService.updated(Dictionary) throws ConfigurationException</code>.
+ *
+ * @param callback the name of the callback method
+ */
+ ConfigurationDependency setCallback(String callback);
+
+ /**
+ * Sets the name of the callback method that should be invoked when a configuration
+ * is available. The contract for this method is identical to that of
+ * <code>ManagedService.updated(Dictionary) throws ConfigurationException</code>.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param callback the name of the callback method
+ */
+ ConfigurationDependency setCallback(Object instance, String callback);
+
+ /**
+ * Sets the <code>service.pid</code> of the configuration you are depending
+ * on.
+ */
+ ConfigurationDependency setPid(String pid);
+
+ /**
+ * Sets propagation of the configuration properties to the service
+ * properties. Any additional service properties specified directly are
+ * merged with these.
+ */
+ ConfigurationDependency setPropagate(boolean propagate);
+
+ /**
+ * The label used to display the tab name (or section) where the properties
+ * are displayed. Example: "Printer Service".
+ *
+ * @return The label used to display the tab name where the properties are
+ * displayed (may be localized)
+ */
+ ConfigurationDependency setHeading(String heading);
+
+ /**
+ * A human readable description of the PID this configuration is associated
+ * with. Example: "Configuration for the PrinterService bundle".
+ *
+ * @return A human readable description of the PID this configuration is
+ * associated with (may be localized)
+ */
+ ConfigurationDependency setDescription(String description);
+
+ /**
+ * Points to the basename of the Properties file that can localize the Meta
+ * Type informations. The default localization base name for the properties
+ * is OSGI-INF/l10n/bundle, but can be overridden by the manifest
+ * Bundle-Localization header (see core specification, in section
+ * Localization on page 68). You can specify a specific localization
+ * basename file using this method (e.g.
+ * <code>setLocalization("person")</code> will match person_du_NL.properties
+ * in the root bundle directory.
+ */
+ ConfigurationDependency setLocalization(String path);
+
+ /**
+ * Adds a MetaData regarding a given configuration property.
+ */
+ ConfigurationDependency add(PropertyMetaData properties);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Dependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Dependency.java
new file mode 100644
index 0000000..7014ebb
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Dependency.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm;
+
+import java.util.Dictionary;
+
+/**
+ * Generic dependency for a component.
+ * Can be added to a single component. Can be available, or not..
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface Dependency {
+ /**
+ * Returns <code>true</code> if this a required dependency. Required dependencies
+ * are dependencies that must be available before the component can be activated.
+ *
+ * @return <code>true</code> if the dependency is required
+ */
+ public boolean isRequired();
+
+ /**
+ * Returns <code>true</code> if the dependency is available.
+ *
+ * @return <code>true</code> if the dependency is available
+ */
+ public boolean isAvailable();
+
+ /**
+ * Returns <code>true</code> if auto configuration is enabled for this dependency.
+ * Auto configuration means that a dependency is injected in the component instance
+ * when it's available, and if it's unavailable, a "null object" will be inserted
+ * instead.
+ *
+ * @return true if auto configuration is enabled for this dependency
+ */
+ public boolean isAutoConfig();
+
+ /**
+ * Returns the name of the member in the class of the component instance
+ * to inject into. If you specify this, not all members of the right
+ * type will be injected, only the member whose name matches.
+ *
+ * @return the name of the member in the class of the component instance to inject into
+ */
+ public String getAutoConfigName();
+
+ /**
+ * Determines if the properties associated with this dependency should be propagated to
+ * the properties of the service registered by the component they belong to.
+ *
+ * @see Dependency#getProperties()
+ *
+ * @return <code>true</code> if the properties should be propagated
+ */
+ public boolean isPropagated();
+
+ /**
+ * Returns the properties associated with this dependency.
+ *
+ * @see Dependency#isPropagated()
+ *
+ * @return the properties
+ */
+ public <K,V> Dictionary<K,V> getProperties();
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyActivatorBase.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyActivatorBase.java
new file mode 100644
index 0000000..d06b91a
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyActivatorBase.java
@@ -0,0 +1,345 @@
+/*
+ * 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.dm;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+/**
+ * Base bundle activator class. Subclass this activator if you want to use dependency
+ * management in your bundle. There are two methods you should implement:
+ * <code>init()</code> and <code>destroy()</code>. Both methods take two arguments,
+ * the bundle context and the dependency manager. The dependency manager can be used
+ * to define all the dependencies.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class DependencyActivatorBase implements BundleActivator {
+ private BundleContext m_context;
+ private DependencyManager m_manager;
+ private Logger m_logger;
+
+ /**
+ * Initialize the dependency manager. Here you can add all components and their dependencies.
+ * If something goes wrong and you do not want your bundle to be started, you can throw an
+ * exception. This exception will be passed on to the <code>start()</code> method of the
+ * bundle activator, causing the bundle not to start.
+ *
+ * @param context the bundle context
+ * @param manager the dependency manager
+ * @throws Exception if the initialization fails
+ */
+ public abstract void init(BundleContext context, DependencyManager manager) throws Exception;
+
+ /**
+ * Destroy the dependency manager. Here you can remove all components and their dependencies.
+ * Actually, the base class will clean up your dependencies anyway, so most of the time you
+ * don't need to do anything here.
+ * <p>
+ * If something goes wrong and you do not want your bundle to be stopped, you can throw an
+ * exception. This exception will be passed on to the <code>stop()</code> method of the
+ * bundle activator, causing the bundle not to stop.
+ *
+ * @param context the bundle context
+ * @param manager the dependency manager
+ * @throws Exception if the destruction fails
+ */
+ public void destroy(BundleContext context, DependencyManager manager) throws Exception { }
+
+ /**
+ * Start method of the bundle activator. Initializes the dependency manager
+ * and calls <code>init()</code>.
+ *
+ * @param context the bundle context
+ */
+ public void start(BundleContext context) throws Exception {
+ m_context = context;
+ m_logger = new Logger(context);
+ m_manager = new DependencyManager(context, m_logger);
+ init(m_context, m_manager);
+ }
+
+ /**
+ * Stop method of the bundle activator. Calls the <code>destroy()</code> method
+ * and cleans up all left over dependencies.
+ *
+ * @param context the bundle context
+ */
+ public void stop(BundleContext context) throws Exception {
+ destroy(m_context, m_manager);
+ m_manager.clear();
+ m_manager = null;
+ m_context = null;
+ }
+
+ /**
+ * Returns the bundle context that is associated with this bundle.
+ *
+ * @return the bundle context
+ */
+ public BundleContext getBundleContext() {
+ return m_context;
+ }
+
+ /**
+ * Returns the dependency manager that is associated with this bundle.
+ *
+ * @return the dependency manager
+ */
+ public DependencyManager getDependencyManager() {
+ return m_manager;
+ }
+
+ /**
+ * Returns the logger that is associated with this bundle. A logger instance
+ * is a proxy that will log to a real OSGi logservice if available and standard
+ * out if not.
+ *
+ * @return the logger
+ */
+ public Logger getLogger() {
+ return m_logger;
+ }
+
+ /**
+ * Creates a new component.
+ *
+ * @return the new component
+ */
+ public Component createComponent() {
+ return m_manager.createComponent();
+ }
+
+ /**
+ * Creates a new service dependency.
+ *
+ * @return the service dependency
+ */
+ public ServiceDependency createServiceDependency() {
+ return m_manager.createServiceDependency();
+ }
+
+ /**
+ * Creates a new temporal service dependency.
+ *
+ * @param timeout the max number of milliseconds to wait for a service availability.
+ * @return the service dependency
+ */
+ public ServiceDependency createTemporalServiceDependency(long timeout) {
+ return m_manager.createTemporalServiceDependency(timeout);
+ }
+
+ /**
+ * Creates a new configuration dependency.
+ *
+ * @return the configuration dependency
+ */
+ public ConfigurationDependency createConfigurationDependency() {
+ return m_manager.createConfigurationDependency();
+ }
+
+ /**
+ * Creates a new configuration property metadata.
+ *
+ * @return the configuration property metadata
+ */
+ public PropertyMetaData createPropertyMetaData() {
+ return m_manager.createPropertyMetaData();
+ }
+
+ /**
+ * Creates a new bundle dependency.
+ *
+ * @return the bundle dependency
+ */
+ public BundleDependency createBundleDependency() {
+ return m_manager.createBundleDependency();
+ }
+
+ /**
+ * Creates a new resource dependency.
+ *
+ * @return the resource dependency
+ */
+ public ResourceDependency createResourceDependency() {
+ return m_manager.createResourceDependency();
+ }
+
+ /**
+ * Creates a new aspect service.
+ *
+ * @return the aspect service
+ * @see DependencyManager#createAspectService(Class, String, int, String)
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking, String attributeName) {
+ return m_manager.createAspectService(serviceInterface, serviceFilter, ranking, attributeName);
+ }
+
+ /**
+ * Creates a new aspect service.
+ *
+ * @return the aspect service
+ * @see DependencyManager#createAspectService(Class, String, int)
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking) {
+ return m_manager.createAspectService(serviceInterface, serviceFilter, ranking);
+ }
+
+ /**
+ * Creates a new aspect service.
+ *
+ * @return the aspect service
+ * @see DependencyManager#createAspectService(Class, String, int, String, String, String)
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking, String add, String change, String remove) {
+ return m_manager.createAspectService(serviceInterface, serviceFilter, ranking, add, change, remove);
+ }
+
+ /**
+ * Creates a new aspect service.
+ *
+ * @return the aspect service
+ * @see DependencyManager#createAspectService(Class, String, int, String, String, String, String)
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking, String add, String change, String remove, String swap) {
+ return m_manager.createAspectService(serviceInterface, serviceFilter, ranking, add, change, remove, swap);
+ }
+
+ /**
+ * Creates a new adapter service.
+ *
+ * @return the adapter service
+ * @see DependencyManager#createAdapterService(Class, String)
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter) {
+ return m_manager.createAdapterService(serviceInterface, serviceFilter);
+ }
+
+ /**
+ * Creates a new adapter service.
+ *
+ * @return the adapter service
+ * @see DependencyManager#createAdapterService(Class, String, String)
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter, String autoConfig) {
+ return m_manager.createAdapterService(serviceInterface, serviceFilter, autoConfig);
+ }
+
+ /**
+ * Creates a new adapter service.
+ *
+ * @return the adapter service
+ * @see DependencyManager#createAdapterService(Class, String, String, String, String)
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter, String add, String change, String remove) {
+ return m_manager.createAdapterService(serviceInterface, serviceFilter, add, change, remove);
+ }
+
+ /**
+ * Creates a new adapter service.
+ * @return the adapter service
+ * @see DependencyManager#createAdapterService(Class, String, String, String, String, String)
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter, String add, String change, String remove, String swap) {
+ return m_manager.createAdapterService(serviceInterface, serviceFilter, add, change, remove, swap);
+ }
+
+ /**
+ * Creates a new adapter service.
+ * @return the adapter service
+ * @see DependencyManager#createAdapterService(Class, String, String, Object, String, String, String, String, boolean)
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter,
+ String autoConfig, Object callbackInstance, String add, String change, String remove, String swap) {
+ return m_manager.createAdapterService(serviceInterface, serviceFilter, autoConfig, callbackInstance, add, change, remove, swap, true);
+ }
+
+ /**
+ * Creates a new adapter service.
+ * @return the adapter service
+ * @see DependencyManager#createAdapterService(Class, String, String, Object, String, String, String, String, boolean)
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter,
+ String autoConfig, Object callbackInstance, String add, String change, String remove, String swap, boolean propagate) {
+ return m_manager.createAdapterService(serviceInterface, serviceFilter, autoConfig, callbackInstance, add, change, remove, swap, propagate);
+ }
+
+ /**
+ * Creates a new resource adapter service.
+ *
+ * @return the resource adapter service
+ */
+ public Component createResourceAdapter(String resourceFilter, boolean propagate, Object callbackInstance, String callbackChanged) {
+ return m_manager.createResourceAdapterService(resourceFilter, propagate, callbackInstance, callbackChanged);
+ }
+
+ /**
+ * Creates a new resource adapter service.
+ *
+ * @return the resource adapter service
+ */
+ public Component createResourceAdapter(String resourceFilter, boolean propagate, Object callbackInstance, String callbackSet, String callbackChanged) {
+ return m_manager.createResourceAdapterService(resourceFilter, propagate, callbackInstance, callbackSet, callbackChanged);
+ }
+
+ /**
+ * Creates a new resource adapter service.
+ *
+ * @return the resource adapter service
+ */
+ public Component createResourceAdapter(String resourceFilter, Object propagateCallbackInstance, String propagateCallbackMethod, Object callbackInstance, String callbackChanged) {
+ return m_manager.createResourceAdapterService(resourceFilter, propagateCallbackInstance, propagateCallbackMethod, callbackInstance, null, callbackChanged);
+ }
+
+ /**
+ * Creates a new resource adapter service.
+ *
+ * @return the resource adapter service
+ */
+ public Component createResourceAdapter(String resourceFilter, Object propagateCallbackInstance, String propagateCallbackMethod, Object callbackInstance, String callbackSet, String callbackChanged) {
+ return m_manager.createResourceAdapterService(resourceFilter, propagateCallbackInstance, propagateCallbackMethod, callbackInstance, callbackSet, callbackChanged);
+ }
+
+ /**
+ * Creates a new bundle adapter service.
+ *
+ * @return the bundle adapter service
+ */
+ public Component createBundleAdapterService(int bundleStateMask, String bundleFilter, boolean propagate) {
+ return m_manager.createBundleAdapterService(bundleStateMask, bundleFilter, propagate);
+ }
+
+ /**
+ * Creates a new factory configuration adapter service.
+ *
+ * @return the factory configuration adapter service
+ */
+ public Component createFactoryConfigurationAdapterService(String factoryPid, String update, boolean propagate) {
+ return m_manager.createFactoryConfigurationAdapterService(factoryPid, update, propagate);
+ }
+
+ /**
+ * Creates a new factory configuration adapter service.
+ *
+ * @return the factory configuration adapter service
+ */
+ public Component createFactoryConfigurationAdapterService(String factoryPid, String update, boolean propagate, String heading, String desc, String localization, PropertyMetaData[] propertiesMetaData) {
+ return m_manager.createAdapterFactoryConfigurationService(factoryPid, update, propagate, heading, desc, localization, propertiesMetaData);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyManager.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyManager.java
new file mode 100644
index 0000000..336f8ae
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/DependencyManager.java
@@ -0,0 +1,707 @@
+/*
+ * 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.dm;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.dm.impl.AdapterServiceImpl;
+import org.apache.felix.dm.impl.AspectServiceImpl;
+import org.apache.felix.dm.impl.BundleAdapterImpl;
+import org.apache.felix.dm.impl.BundleDependencyImpl;
+import org.apache.felix.dm.impl.ComponentImpl;
+import org.apache.felix.dm.impl.ComponentScheduler;
+import org.apache.felix.dm.impl.ConfigurationDependencyImpl;
+import org.apache.felix.dm.impl.FactoryConfigurationAdapterImpl;
+import org.apache.felix.dm.impl.ResourceAdapterImpl;
+import org.apache.felix.dm.impl.ResourceDependencyImpl;
+import org.apache.felix.dm.impl.ServiceDependencyImpl;
+import org.apache.felix.dm.impl.TemporalServiceDependencyImpl;
+import org.apache.felix.dm.impl.index.AdapterFilterIndex;
+import org.apache.felix.dm.impl.index.AspectFilterIndex;
+import org.apache.felix.dm.impl.index.ServiceRegistryCache;
+import org.apache.felix.dm.impl.index.multiproperty.MultiPropertyFilterIndex;
+import org.apache.felix.dm.impl.metatype.PropertyMetaDataImpl;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+
+/**
+ * The dependency manager manages all components and their dependencies. Using
+ * this API you can declare all components and their dependencies. Under normal
+ * circumstances, you get passed an instance of this class through the
+ * <code>DependencyActivatorBase</code> subclass you use as your
+ * <code>BundleActivator</code>, but it is also possible to create your
+ * own instance.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DependencyManager {
+ /**
+ * The DependencyManager Activator will wait for a threadpool before creating any DM components if the following
+ * OSGi system property is set to true.
+ */
+ public final static String PARALLEL = "org.apache.felix.dependencymanager.parallel";
+
+ public static final String ASPECT = "org.apache.felix.dependencymanager.aspect";
+ public static final String SERVICEREGISTRY_CACHE_INDICES = "org.apache.felix.dependencymanager.filterindex";
+ public static final String METHOD_CACHE_SIZE = "org.apache.felix.dependencymanager.methodcache";
+
+ private final BundleContext m_context;
+ private final Logger m_logger;
+ private final ConcurrentHashMap<Component, Component> m_components = new ConcurrentHashMap<>();
+
+ // service registry cache
+ private static ServiceRegistryCache m_serviceRegistryCache;
+ private static final Set<WeakReference<DependencyManager>> m_dependencyManagers = new HashSet<>();
+ static {
+ try {
+ Bundle bundle = FrameworkUtil.getBundle(DependencyManager.class);
+ if (bundle != null && bundle.getState() != Bundle.ACTIVE) {
+ bundle.start();
+ BundleContext bundleContext = bundle.getBundleContext();
+ String index = bundleContext.getProperty(SERVICEREGISTRY_CACHE_INDICES);
+ if (index != null) {
+ m_serviceRegistryCache = new ServiceRegistryCache(bundleContext);
+ m_serviceRegistryCache.open(); // TODO close it somewhere
+ String[] props = index.split(";");
+ for (int i = 0; i < props.length; i++) {
+ if (props[i].equals("*aspect*")) {
+ m_serviceRegistryCache.addFilterIndex(new AspectFilterIndex());
+ }
+ else if (props[i].equals("*adapter*")) {
+ m_serviceRegistryCache.addFilterIndex(new AdapterFilterIndex());
+ }
+ else {
+ m_serviceRegistryCache.addFilterIndex(new MultiPropertyFilterIndex(props[i]));
+ }
+ }
+ }
+ }
+ }
+ catch (BundleException e) {
+ // if we cannot start ourselves, we cannot use the indices
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Creates a new dependency manager. You need to supply the
+ * <code>BundleContext</code> to be used by the dependency
+ * manager to register services and communicate with the
+ * framework.
+ *
+ * @param context the bundle context
+ */
+ public DependencyManager(BundleContext context) {
+ this(context, new Logger(context));
+ }
+
+ DependencyManager(BundleContext context, Logger logger) {
+ m_context = createContext(context);
+ m_logger = logger;
+ synchronized (m_dependencyManagers) {
+ m_dependencyManagers.add(new WeakReference<DependencyManager>(this));
+ }
+ }
+
+ /**
+ * Returns the list of currently created dependency managers.
+ * @return the list of currently created dependency managers
+ */
+ public static List<DependencyManager> getDependencyManagers() {
+ List<DependencyManager> result = new ArrayList<>();
+ synchronized (m_dependencyManagers) {
+ Iterator<WeakReference<DependencyManager>> iterator = m_dependencyManagers.iterator();
+ while (iterator.hasNext()) {
+ WeakReference<DependencyManager> reference = iterator.next();
+ DependencyManager manager = reference.get();
+ if (manager != null) {
+ try {
+ manager.getBundleContext().getBundle();
+ result.add(manager);
+ continue;
+ }
+ catch (IllegalStateException e) {
+ }
+ }
+ iterator.remove();
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the bundle context associated with this dependency manager.
+ * @return the bundle context associated with this dependency manager.
+ */
+ public BundleContext getBundleContext() {
+ return m_context;
+ }
+
+ /**
+ * Adds a new component to the dependency manager. After the service is added
+ * it will be started immediately.
+ *
+ * @param c the service to add
+ */
+ public void add(Component c) {
+ m_components.put(c, c);
+ ComponentScheduler.instance().add(c);
+ }
+
+ /**
+ * Removes a service from the dependency manager. Before the service is removed
+ * it is stopped first.
+ *
+ * @param c the component to remove
+ */
+ public void remove(Component c) {
+ ComponentScheduler.instance().remove(c);
+ m_components.remove(c);
+ }
+
+ /**
+ * Creates a new component.
+ *
+ * @return the new component
+ */
+ public Component createComponent() {
+ return new ComponentImpl(m_context, this, m_logger);
+ }
+
+ /**
+ * Creates a new service dependency.
+ *
+ * @return the service dependency
+ */
+ public ServiceDependency createServiceDependency() {
+ return new ServiceDependencyImpl();
+ }
+
+ /**
+ * Creates a new configuration dependency.
+ *
+ * @return the configuration dependency
+ */
+ public ConfigurationDependency createConfigurationDependency() {
+ return new ConfigurationDependencyImpl(m_context, m_logger);
+ }
+
+ /**
+ * Creates a new bundle dependency.
+ *
+ * @return a new BundleDependency instance.
+ */
+ public BundleDependency createBundleDependency() {
+ return new BundleDependencyImpl();
+ }
+
+ /**
+ * Creates a new resource dependency.
+ *
+ * @return the resource dependency
+ */
+ public ResourceDependency createResourceDependency() {
+ return new ResourceDependencyImpl();
+ }
+
+ /**
+ * Creates a new timed required service dependency. A timed dependency blocks the invoker thread is the required dependency
+ * is currently unavailable, until it comes up again.
+ *
+ * @return a new timed service dependency
+ */
+ public ServiceDependency createTemporalServiceDependency(long timeout) {
+ return new TemporalServiceDependencyImpl(m_context, timeout);
+ }
+
+ /**
+ * Creates a new adapter. The adapter will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface and existing properties
+ * from the original service plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAdapterService(AdapteeService.class, "(foo=bar)")
+ * .setInterface(AdapterService.class, new Hashtable() {{ put("extra", "property"); }})
+ * .setImplementation(AdapterImpl.class);
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the adapter to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @return a service that acts as a factory for generating adapters
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter) {
+ return new AdapterServiceImpl(this, serviceInterface, serviceFilter, null, null, null, null, null, null, true);
+ }
+
+ /**
+ * Creates a new adapter. The adapter will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface and existing properties
+ * from the original service plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAdapterService(AdapteeService.class, "(foo=bar)", "m_service")
+ * .setInterface(AdapterService.class, new Hashtable() {{ put("extra", "property"); }})
+ * .setImplementation(AdapterImpl.class);
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the adapter to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param autoConfig the name of the member to inject the service into
+ * @return a service that acts as a factory for generating adapters
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter, String autoConfig) {
+ return new AdapterServiceImpl(this, serviceInterface, serviceFilter, autoConfig, null, null, null, null, null, true);
+ }
+
+ /**
+ * Creates a new adapter. The adapter will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface and existing properties
+ * from the original service plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAdapterService(AdapteeService.class, "(foo=bar)", "add", "change", "remove")
+ * .setInterface(AdapterService.class, new Hashtable() {{ put("extra", "property"); }})
+ * .setImplementation(AdapterImpl.class);
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the adapter to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param add name of the callback method to invoke on add
+ * @param change name of the callback method to invoke on change
+ * @param remove name of the callback method to invoke on remove
+ * @return a service that acts as a factory for generating adapters
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter, String add, String change,
+ String remove)
+ {
+ return new AdapterServiceImpl(this, serviceInterface, serviceFilter, null, null, add, change, remove, null, true);
+ }
+
+ /**
+ * Creates a new adapter. The adapter will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface and existing properties
+ * from the original service plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAdapterService(AdapteeService.class, "(foo=bar)", "add", "change", "remove", "swap")
+ * .setInterface(AdapterService.class, new Hashtable() {{ put("extra", "property"); }})
+ * .setImplementation(AdapterImpl.class);
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the adapter to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param add name of the callback method to invoke on add
+ * @param change name of the callback method to invoke on change
+ * @param remove name of the callback method to invoke on remove
+ * @param swap name of the callback method to invoke on swap
+ * @return a service that acts as a factory for generating adapters
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter, String add, String change,
+ String remove, String swap)
+ {
+ return new AdapterServiceImpl(this, serviceInterface, serviceFilter, null, null, add, change, remove, swap, true);
+ }
+
+ /**
+ * Creates a new adapter. The adapter will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface (and existing properties
+ * from the original service if you set the propagate flag) plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAdapterService(AdapteeService.class, "(foo=bar)", "add", "change", "remove", "swap")
+ * .setInterface(AdapterService.class, new Hashtable() {{ put("extra", "property"); }})
+ * .setImplementation(AdapterImpl.class);
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the adapter to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param autoConfig the name of the member to inject the service into, or null.
+ * @param callbackInstance the instance to invoke the callbacks on, or null if the callbacks have to be invoked on the adapter itself
+ * @param add name of the callback method to invoke on add
+ * @param change name of the callback method to invoke on change
+ * @param remove name of the callback method to invoke on remove
+ * @param swap name of the callback method to invoke on swap
+ * @param propagate true if the adaptee service properties should be propagated to the adapter service consumers
+ * @return a service that acts as a factory for generating adapters
+ */
+ public Component createAdapterService(Class<?> serviceInterface, String serviceFilter,
+ String autoConfig, Object callbackInstance, String add, String change, String remove,
+ String swap, boolean propagate)
+ {
+ return new AdapterServiceImpl(this, serviceInterface, serviceFilter, autoConfig, callbackInstance, add, change, remove, swap, propagate);
+ }
+
+ /**
+ * Creates a new Managed Service Factory Configuration Adapter. For each new Config Admin factory configuration matching
+ * the factoryPid, an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface, and with the specified adapter service properties.
+ * Depending on the <code>propagate</code> parameter, every public factory configuration properties
+ * (which don't start with ".") will be propagated along with the adapter service properties.
+ * It will also inherit all dependencies.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createFactoryConfigurationAdapterService("MyFactoryPid", "update", true)
+ * // The interface to use when registering adapter
+ * .setInterface(AdapterService.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
+ * // the implementation of the adapter
+ * .setImplementation(AdapterServiceImpl.class);
+ * </pre></blockquote>
+ *
+ * @param factoryPid the pid matching the factory configuration
+ * @param update the adapter method name that will be notified when the factory configuration is created/updated.
+ * @param propagate true if public factory configuration should be propagated to the adapter service properties
+ * @return a service that acts as a factory for generating the managed service factory configuration adapter
+ */
+ public Component createFactoryConfigurationAdapterService(String factoryPid, String update, boolean propagate) {
+ return new FactoryConfigurationAdapterImpl(this, factoryPid, update, propagate);
+ }
+
+ /**
+ * Creates a new Managed Service Factory Configuration Adapter with meta type support. For each new Config Admin
+ * factory configuration matching the factoryPid, an adapter will be created based on the adapter implementation
+ * class. The adapter will be registered with the specified interface, and with the specified adapter service
+ * properties. Depending on the <code>propagate</code> parameter, every public factory configuration properties
+ * (which don't start with ".") will be propagated along with the adapter service properties.
+ * It will also inherit all dependencies.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * PropertyMetaData[] propertiesMetaData = new PropertyMetaData[] {
+ * manager.createPropertyMetaData()
+ * .setCardinality(Integer.MAX_VALUE)
+ * .setType(String.class)
+ * .setHeading("English words")
+ * .setDescription("Declare here some valid english words")
+ * .setDefaults(new String[] {"hello", "world"})
+ * .setId("words")
+ * };
+ *
+ * manager.add(createFactoryConfigurationAdapterService("FactoryPid",
+ * "updated",
+ * true, // propagate CM settings
+ * "EnglishDictionary",
+ * "English dictionary configuration properties",
+ * null,
+ * propertiesMetaData)
+ * .setImplementation(Adapter.class));
+ * </pre></blockquote>
+ *
+ * @param factoryPid the pid matching the factory configuration
+ * @param update the adapter method name that will be notified when the factory configuration is created/updated.
+ * @param propagate true if public factory configuration should be propagated to the adapter service properties
+ * @param heading The label used to display the tab name (or section) where the properties are displayed.
+ * Example: "Printer Service"
+ * @param desc A human readable description of the factory PID this configuration is associated with.
+ * Example: "Configuration for the PrinterService bundle"
+ * @param localization Points to the basename of the Properties file that can localize the Meta Type informations.
+ * The default localization base name for the properties is OSGI-INF/l10n/bundle, but can
+ * be overridden by the manifest Bundle-Localization header (see core specification, in section Localization
+ * on page 68). You can specify a specific localization basename file using this parameter
+ * (e.g. <code>"person"</code> will match person_du_NL.properties in the root bundle directory).
+ * @param propertiesMetaData Array of MetaData regarding configuration properties
+ * @return a service that acts as a factory for generating the managed service factory configuration adapter
+ */
+ public Component createAdapterFactoryConfigurationService(String factoryPid, String update, boolean propagate,
+ String heading, String desc, String localization, PropertyMetaData[] propertiesMetaData)
+ {
+ return new FactoryConfigurationAdapterImpl(this, factoryPid, update, propagate, m_context, m_logger, heading,
+ desc, localization, propertiesMetaData);
+ }
+
+ /**
+ * Creates a new bundle adapter. The adapter will be applied to any bundle that
+ * matches the specified bundle state mask and filter condition. For each matching
+ * bundle an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface
+ *
+ * TODO and existing properties from the original resource plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createBundleAdapterService(Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE,
+ * "(Bundle-SymbolicName=org.apache.felix.dependencymanager)",
+ * true)
+ * // The interface to use when registering adapter
+ * .setInterface(AdapterService.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
+ * // the implementation of the adapter
+ * .setImplementation(AdapterServiceImpl.class);
+ * </pre></blockquote>
+ *
+ * @param bundleStateMask the bundle state mask to apply
+ * @param bundleFilter the filter to apply to the bundle manifest
+ * @param propagate <code>true</code> if properties from the bundle should be propagated to the service
+ * @return a service that acts as a factory for generating bundle adapters
+ */
+ public Component createBundleAdapterService(int bundleStateMask, String bundleFilter, boolean propagate) {
+ return new BundleAdapterImpl(this, bundleStateMask, bundleFilter, propagate);
+ }
+
+ /**
+ * Creates a new resource adapter. The adapter will be applied to any resource that
+ * matches the specified filter condition. For each matching resource
+ * an adapter will be created based on the adapter implementation class.
+ * The adapter will be registered with the specified interface and existing properties
+ * from the original resource plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createResourceAdapterService("(&(path=/test)(repository=TestRepository))", true)
+ * // The interface to use when registering adapter
+ * .setInterface(AdapterService.class.getName(), new Hashtable() {{ put("foo", "bar"); }})
+ * // the implementation of the adapter
+ * .setImplementation(AdapterServiceImpl.class);
+ * </pre></blockquote>
+ *
+ * @param resourceFilter the filter condition to use with the resource
+ * @param propagate <code>true</code> if properties from the resource should be propagated to the service
+ * @param callbackInstance instance to invoke the callback on
+ * @param callbackChanged the name of the callback method
+ * @return a service that acts as a factory for generating resource adapters
+ */
+ public Component createResourceAdapterService(String resourceFilter, boolean propagate, Object callbackInstance,
+ String callbackChanged)
+ {
+ return new ResourceAdapterImpl(this, resourceFilter, propagate, callbackInstance, null, callbackChanged);
+ }
+
+ /** @see DependencyManager#createResourceAdapterService(String, boolean, Object, String) */
+ public Component createResourceAdapterService(String resourceFilter, boolean propagate, Object callbackInstance,
+ String callbackSet, String callbackChanged)
+ {
+ return new ResourceAdapterImpl(this, resourceFilter, propagate, callbackInstance, callbackSet, callbackChanged);
+ }
+
+ /** @see DependencyManager#createResourceAdapterService(String, boolean, Object, String) */
+ public Component createResourceAdapterService(String resourceFilter, Object propagateCallbackInstance,
+ String propagateCallbackMethod, Object callbackInstance, String callbackChanged)
+ {
+ return new ResourceAdapterImpl(this, resourceFilter, propagateCallbackInstance, propagateCallbackMethod,
+ callbackInstance, null, callbackChanged);
+ }
+
+ /** @see DependencyManager#createResourceAdapterService(String, boolean, Object, String) */
+ public Component createResourceAdapterService(String resourceFilter, Object propagateCallbackInstance,
+ String propagateCallbackMethod, Object callbackInstance, String callbackSet, String callbackChanged)
+ {
+ return new ResourceAdapterImpl(this, resourceFilter, propagateCallbackInstance, propagateCallbackMethod,
+ callbackInstance, callbackSet, callbackChanged);
+ }
+
+ /**
+ * Returns a list of components.
+ *
+ * @return a list of components
+ */
+ public List<Component> getComponents() {
+ return Collections.list(m_components.elements());
+ }
+
+ /**
+ * Creates a new aspect. The aspect will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an aspect will be created based on the aspect implementation class.
+ * The aspect will be registered with the same interface and properties
+ * as the original service, plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAspectService(ExistingService.class, "(foo=bar)", 10, "m_service")
+ * .setImplementation(ExistingServiceAspect.class)
+ * );
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the aspect to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param ranking the level used to organize the aspect chain ordering
+ * @param autoConfig the aspect implementation field name where to inject original service.
+ * If null, any field matching the original service will be injected.
+ * @return a service that acts as a factory for generating aspects
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking, String autoConfig) {
+ return new AspectServiceImpl(this, serviceInterface, serviceFilter, ranking, autoConfig, null, null, null, null);
+ }
+
+ /**
+ * Creates a new aspect. The aspect will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an aspect will be created based on the aspect implementation class.
+ * The aspect will be registered with the same interface and properties
+ * as the original service, plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAspectService(ExistingService.class, "(foo=bar)", 10)
+ * .setImplementation(ExistingServiceAspect.class)
+ * );
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the aspect to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param ranking the level used to organize the aspect chain ordering
+ * @return a service that acts as a factory for generating aspects
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking) {
+ return new AspectServiceImpl(this, serviceInterface, serviceFilter, ranking, null, null, null, null, null);
+ }
+
+ /**
+ * Creates a new aspect. The aspect will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an aspect will be created based on the aspect implementation class.
+ * The aspect will be registered with the same interface and properties
+ * as the original service, plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAspectService(ExistingService.class, "(foo=bar)", 10, "add", "change", "remove")
+ * .setImplementation(ExistingServiceAspect.class)
+ * );
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the aspect to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param ranking the level used to organize the aspect chain ordering
+ * @param add name of the callback method to invoke on add
+ * @param change name of the callback method to invoke on change
+ * @param remove name of the callback method to invoke on remove
+ * @return a service that acts as a factory for generating aspects
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking, String add,
+ String change, String remove)
+ {
+ return new AspectServiceImpl(this, serviceInterface, serviceFilter, ranking, null, add, change, remove, null);
+ }
+
+ /**
+ * Creates a new aspect. The aspect will be applied to any service that
+ * matches the specified interface and filter. For each matching service
+ * an aspect will be created based on the aspect implementation class.
+ * The aspect will be registered with the same interface and properties
+ * as the original service, plus any extra properties you supply here.
+ * It will also inherit all dependencies, and if you declare the original
+ * service as a member it will be injected.
+ *
+ * <h3>Usage Example</h3>
+ *
+ * <blockquote><pre>
+ * manager.createAspectService(ExistingService.class, "(foo=bar)", 10, "add", "change", "remove")
+ * .setImplementation(ExistingServiceAspect.class)
+ * );
+ * </pre></blockquote>
+ *
+ * @param serviceInterface the service interface to apply the aspect to
+ * @param serviceFilter the filter condition to use with the service interface
+ * @param ranking the level used to organize the aspect chain ordering
+ * @param add name of the callback method to invoke on add
+ * @param change name of the callback method to invoke on change
+ * @param remove name of the callback method to invoke on remove
+ * @param swap name of the callback method to invoke on swap
+ * @return a service that acts as a factory for generating aspects
+ */
+ public Component createAspectService(Class<?> serviceInterface, String serviceFilter, int ranking, String add,
+ String change, String remove, String swap)
+ {
+ return new AspectServiceImpl(this, serviceInterface, serviceFilter, ranking, null, add, change, remove, swap);
+ }
+
+ /**
+ * Removes all components and their dependencies.
+ */
+ public void clear() {
+ for (Component component : m_components.keySet()) {
+ remove(component);
+ }
+ m_components.clear();
+ }
+
+ /**
+ * Creates a new configuration property metadata.
+ *
+ * @return the configuration property metadata.
+ */
+ public PropertyMetaData createPropertyMetaData() {
+ return new PropertyMetaDataImpl();
+ }
+
+ private BundleContext createContext(BundleContext context) {
+ if (m_serviceRegistryCache != null) {
+ return m_serviceRegistryCache.createBundleContextInterceptor(context);
+ }
+ else {
+ return context;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/FilterIndex.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/FilterIndex.java
new file mode 100644
index 0000000..23a4b07
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/FilterIndex.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm;
+
+import java.util.List;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * A filter index is an interface you can implement to create your own, optimized index for specific filter expressions.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface FilterIndex {
+ /** Opens this filter index. */
+ public void open(BundleContext context);
+ /** Closes this filter index. */
+ public void close();
+ /** Determines if the combination of class and filter is applicable for this filter index. */
+ public boolean isApplicable(String clazz, String filter);
+ /** Returns all service references that match the specified class and filter. Never returns null. */
+ public List<ServiceReference> getAllServiceReferences(String clazz, String filter);
+ /** Invoked whenever a service event occurs. */
+ public void serviceChanged(ServiceEvent event);
+ /** Adds a service listener to this filter index. */
+ public void addServiceListener(ServiceListener listener, String filter);
+ /** Removes a service listener from this filter index. If the listener is not present in the filter index, this method does nothing. */
+ public void removeServiceListener(ServiceListener listener);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Logger.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Logger.java
new file mode 100644
index 0000000..65d6e9f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/Logger.java
@@ -0,0 +1,310 @@
+/*
+ * 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.dm;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+/**
+ * This class mimics the standard OSGi <tt>LogService</tt> interface. An
+ * instance of this class is used by the dependency manager for all logging.
+ * By default this class logs messages to standard out. The log level can be set to
+ * control the amount of logging performed, where a higher number results in
+ * more logging. A log level of zero turns off logging completely.
+ *
+ * The log levels match those specified in the OSGi Log Service.
+ * This class also tracks log services and will use the highest ranking
+ * log service, if present, as a back end instead of printing to standard
+ * out. The class uses reflection to invoking the log service's method to
+ * avoid a dependency on the log interface, which is also why it does not
+ * actually implement <code>LogService</code>. This class is in many ways
+ * similar to the one used in the system bundle for that same purpose.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Logger implements ServiceListener {
+ private static final String LOG_SINGLE_CONTEXT = "org.apache.felix.dependencymanager.singleContextLog";
+ public static final int LOG_ERROR = 1;
+ public static final int LOG_WARNING = 2;
+ public static final int LOG_INFO = 3;
+ public static final int LOG_DEBUG = 4;
+
+ private final BundleContext m_context;
+
+ private final static int LOGGER_OBJECT_IDX = 0;
+ private final static int LOGGER_METHOD_IDX = 1;
+ private static final String ENABLED_LOG_LEVEL = "org.apache.felix.dependencymanager.loglevel";
+ private ServiceReference m_logRef = null;
+ private Object[] m_logger = null;
+ private int m_enabledLevel = LogService.LOG_WARNING;
+ private String m_debugKey;
+
+ public Logger(BundleContext context) {
+ if (context != null && "true".equals(context.getProperty(LOG_SINGLE_CONTEXT))) {
+ m_context = FrameworkUtil.getBundle(DependencyManager.class).getBundleContext();
+ } else {
+ m_context = context;
+ }
+ if (m_context != null) {
+ String enabledLevel = m_context.getProperty(ENABLED_LOG_LEVEL);
+ if (enabledLevel != null) {
+ try {
+ m_enabledLevel = Integer.valueOf(enabledLevel);
+ } catch (NumberFormatException e) {}
+ }
+ startListeningForLogService();
+ }
+ }
+
+ public final void log(int level, String msg) {
+ _log(null, level, msg, null);
+ }
+
+ public final void log(int level, String msg, Throwable throwable) {
+ _log(null, level, msg, throwable);
+ }
+
+ public final void log(ServiceReference sr, int level, String msg) {
+ _log(sr, level, msg, null);
+ }
+
+ public final void log(ServiceReference sr, int level, String msg, Throwable throwable) {
+ _log(sr, level, msg, throwable);
+ }
+
+ protected void doLog(ServiceReference sr, int level, String msg, Throwable throwable) {
+ String s = (sr == null) ? null : "SvcRef " + sr;
+ s = (s == null) ? msg : s + " " + msg;
+ s = (throwable == null) ? s : s + " (" + throwable + ")";
+ switch (level) {
+ case LOG_DEBUG:
+ System.out.println("DEBUG: " + s);
+ break;
+ case LOG_ERROR:
+ System.out.println("ERROR: " + s);
+ if (throwable != null) {
+ if ((throwable instanceof BundleException) && (((BundleException) throwable).getNestedException() != null)) {
+ throwable = ((BundleException) throwable).getNestedException();
+ }
+ throwable.printStackTrace();
+ }
+ break;
+ case LOG_INFO:
+ System.out.println("INFO: " + s);
+ break;
+ case LOG_WARNING:
+ System.out.println("WARNING: " + s);
+ break;
+ default:
+ System.out.println("UNKNOWN[" + level + "]: " + s);
+ }
+ }
+
+ private void _log(ServiceReference sr, int level, String msg, Throwable throwable) {
+ if (level <= m_enabledLevel) {
+ StringBuilder sb = new StringBuilder("[");
+ if (m_debugKey != null) {
+ sb.append(m_debugKey).append(" - ");
+ }
+ sb.append(Thread.currentThread().getName());
+ sb.append("] ");
+ sb.append(msg);
+
+ // Save our own copy just in case it changes. We could try to do
+ // more conservative locking here, but let's be optimistic.
+ Object[] logger = m_logger;
+ // Use the log service if available.
+ if (logger != null) {
+ _logReflectively(logger, sr, level, sb.toString(), throwable);
+ }
+ // Otherwise, default logging action.
+ else {
+ doLog(sr, level, sb.toString(), throwable);
+ }
+ }
+ }
+
+ private void _logReflectively(Object[] logger, ServiceReference sr, int level, String msg, Throwable throwable) {
+ if (logger != null) {
+ Object[] params = { sr, new Integer(level), msg, throwable };
+ try {
+ ((Method) logger[LOGGER_METHOD_IDX]).invoke(logger[LOGGER_OBJECT_IDX], params);
+ }
+ catch (InvocationTargetException ex) {
+ System.err.println("Logger: " + ex);
+ }
+ catch (IllegalAccessException ex) {
+ System.err.println("Logger: " + ex);
+ }
+ }
+ }
+
+ /**
+ * This method is called when the bundle context is set;
+ * it simply adds a service listener so that the bundle can track
+ * log services to be used as the back end of the logging mechanism. It also
+ * attempts to get an existing log service, if present, but in general
+ * there will never be a log service present since the system bundle is
+ * started before every other bundle.
+ */
+ private synchronized void startListeningForLogService() {
+ try {
+ // add a service listener for log services, carefully avoiding any code dependency on it
+ m_context.addServiceListener(this, "(objectClass=org.osgi.service.log.LogService)");
+ }
+ catch (InvalidSyntaxException ex) {
+ // this will never happen since the filter is hard coded
+ }
+ // try to get an existing log service
+ m_logRef = m_context.getServiceReference("org.osgi.service.log.LogService");
+ // get the service object if available and set it in the logger
+ if (m_logRef != null) {
+ setLogger(m_context.getService(m_logRef));
+ }
+ }
+
+ /**
+ * This method implements the callback for the ServiceListener interface.
+ * It is public as a byproduct of implementing the interface and should
+ * not be called directly. This method tracks run-time changes to log
+ * service availability. If the log service being used by the framework's
+ * logging mechanism goes away, then this will try to find an alternative.
+ * If a higher ranking log service is registered, then this will switch
+ * to the higher ranking log service.
+ */
+ public final synchronized void serviceChanged(ServiceEvent event) {
+ // if no logger is in use, then grab this one
+ if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef == null)) {
+ m_logRef = event.getServiceReference();
+ // get the service object and set it in the logger
+ setLogger(m_context.getService(m_logRef));
+ }
+ // if a logger is in use, but this one has a higher ranking, then swap
+ // it for the existing logger
+ else if ((event.getType() == ServiceEvent.REGISTERED) && (m_logRef != null)) {
+ ServiceReference ref = m_context.getServiceReference("org.osgi.service.log.LogService");
+ if (!ref.equals(m_logRef)) {
+ m_context.ungetService(m_logRef);
+ m_logRef = ref;
+ setLogger(m_context.getService(m_logRef));
+ }
+ }
+ // if the current logger is going away, release it and try to
+ // find another one
+ else if ((event.getType() == ServiceEvent.UNREGISTERING) && m_logRef != null && m_logRef.equals(event.getServiceReference())) {
+ // Unget the service object.
+ m_context.ungetService(m_logRef);
+ // Try to get an existing log service.
+ m_logRef = m_context.getServiceReference("org.osgi.service.log.LogService");
+ // get the service object if available and set it in the logger
+ if (m_logRef != null) {
+ setLogger(m_context.getService(m_logRef));
+ }
+ else {
+ setLogger(null);
+ }
+ }
+ }
+
+ /**
+ * This method sets the new log service object. It also caches the method to
+ * invoke. The service object and method are stored in array to optimistically
+ * eliminate the need to locking when logging.
+ */
+ private void setLogger(Object logObj) {
+ if (logObj == null) {
+ m_logger = null;
+ }
+ else {
+ Class<?>[] formalParams = { ServiceReference.class, Integer.TYPE, String.class, Throwable.class };
+ try {
+ Method logMethod = logObj.getClass().getMethod("log", formalParams);
+ logMethod.setAccessible(true);
+ m_logger = new Object[] { logObj, logMethod };
+ }
+ catch (NoSuchMethodException ex) {
+ System.err.println("Logger: " + ex);
+ m_logger = null;
+ }
+ }
+ }
+
+ public void setEnabledLevel(int enabledLevel) {
+ m_enabledLevel = enabledLevel;
+ }
+
+ public void setDebugKey(String debugKey) {
+ m_debugKey = debugKey;
+ }
+
+ public String getDebugKey() {
+ return m_debugKey;
+ }
+
+ // --------------- Convenient helper log methods --------------------------------------------
+
+ public void err(String format, Object... params) {
+ log(LogService.LOG_ERROR, String.format(format, params));
+ }
+
+ public void err(String format, Throwable err, Object... params) {
+ log(LogService.LOG_ERROR, String.format(format, params), err);
+ }
+
+ public void warn(String format, Object... params) {
+ log(LogService.LOG_WARNING, String.format(format, params));
+ }
+
+ public void warn(String format, Throwable err, Object... params) {
+ log(LogService.LOG_WARNING, String.format(format, params), err);
+ }
+
+ public boolean info() {
+ return m_enabledLevel >= LogService.LOG_INFO;
+ }
+
+ public void info(String format, Object... params) {
+ log(LogService.LOG_INFO, String.format(format, params));
+ }
+
+ public void info(String format, Throwable err, Object... params) {
+ log(LogService.LOG_INFO, String.format(format, params), err);
+ }
+
+ public boolean debug() {
+ return m_enabledLevel >= LogService.LOG_DEBUG;
+ }
+
+ public void debug(String format, Object... params) {
+ log(LogService.LOG_DEBUG, String.format(format, params));
+ }
+
+ public void debug(String format, Throwable err, Object... params) {
+ log(LogService.LOG_DEBUG, String.format(format, params), err);
+ }
+}
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/PropertyMetaData.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/PropertyMetaData.java
new file mode 100644
index 0000000..66a4a41
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/PropertyMetaData.java
@@ -0,0 +1,91 @@
+/*
+ * 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.dm;
+
+/**
+ * This interface defines meta data regarding a given configuration property.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface PropertyMetaData {
+ /**
+ * The label used to display the property. Example: "Log Level".
+ *
+ * @return The label used to display the property (may be localized)
+ */
+ public PropertyMetaData setHeading(String heading);
+
+ /**
+ * The key of a ConfigurationAdmin property. Example: "printer.logLevel"
+ *
+ * @return The Configuration Admin property name
+ */
+ public PropertyMetaData setId(String id);
+
+ /**
+ * Returns the property primitive type. If must be either one of the following types:<p>
+ * <ul>
+ * <li>String.class</li>
+ * <li>Long.class</li>
+ * <li>Integer.class</li>
+ * <li>Character.class</li>
+ * <li>Byte.class</li>
+ * <li>Double.class</li>
+ * <li>Float.class</li>
+ * <li>Boolean.class</li>
+ * </ul>
+ */
+ public PropertyMetaData setType(Class<?> type);
+
+ /**
+ * Returns a default for this property. The object must be of the appropriate type as defined by the cardinality and getType().
+ * The return type is a list of String 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.
+ */
+ public PropertyMetaData setDefaults(String[] defaults);
+
+ /**
+ * Returns the property description. The description may be localized and must describe the semantics of this type and any
+ * constraints. Example: "Select the log level for the Printer Service".
+ *
+ * @return a localizable description of the property.
+ */
+ public PropertyMetaData setDescription(String description);
+
+ /**
+ * Return the cardinality of this property. The OSGi environment handles multi valued properties in arrays ([]) or in Vector objects.
+ * The return value is defined as follows:<p>
+ *
+ * <ul>
+ * <li> x = Integer.MIN_VALUE no limit, but use Vector</li>
+ * <li> x < 0 -x = max occurrences, store in Vector</li>
+ * <li> x > 0 x = max occurrences, store in array []</li>
+ * <li> x = Integer.MAX_VALUE no limit, but use array []</li>
+ * <li> x = 0 1 occurrence required</li>
+ * </ul>
+ */
+ public PropertyMetaData setCardinality(int cardinality);
+
+ /**
+ * Tells if this property is required or not.
+ */
+ public PropertyMetaData setRequired(boolean required);
+
+ /**
+ * Return a list of valid options for this property (the labels may be localized).
+ *
+ * @return the list of valid options for this property.
+ */
+ public PropertyMetaData addOption(String optionLabel, String optionValue);
+}
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceDependency.java
new file mode 100644
index 0000000..d14cc19
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceDependency.java
@@ -0,0 +1,139 @@
+/*
+ * 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.dm;
+
+import java.net.URL;
+
+/**
+ * A resource dependency is a dependency on a resource. A resource in this context is an object that is
+ * identified by a URL. Resources should somehow be provided by an external component, the resource
+ * provider. These dependencies then react on them becoming available or not. Use cases for such dependencies
+ * are resources that are embedded in bundles, in a workspace or some remote or local repository, etc.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ResourceDependency extends Dependency, ComponentDependencyDeclaration, ResourceHandler {
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added or removed. When you specify callbacks, the auto configuration
+ * feature is automatically turned off, because we're assuming you don't need it in this
+ * case.
+ *
+ * @param added the method to call when a service was added
+ * @param removed the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ResourceDependency setCallbacks(String added, String removed);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ *
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ResourceDependency setCallbacks(String added, String changed, String removed);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added or removed. They are called on the instance you provide. When you
+ * specify callbacks, the auto configuration feature is automatically turned off, because
+ * we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a service was added
+ * @param removed the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ResourceDependency setCallbacks(Object instance, String added, String removed);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. They are called on the instance you provide. When you
+ * specify callbacks, the auto configuration feature is automatically turned off, because
+ * we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ResourceDependency setCallbacks(Object instance, String added, String changed, String removed);
+
+ /**
+ * Sets auto configuration for this service. Auto configuration allows the
+ * dependency to fill in any attributes in the service implementation that
+ * are of the same type as this dependency. Default is on.
+ *
+ * @param autoConfig the value of auto config
+ * @return this service dependency
+ */
+ public ResourceDependency setAutoConfig(boolean autoConfig);
+
+ /**
+ * Sets auto configuration for this service. Auto configuration allows the
+ * dependency to fill in the attribute in the service implementation that
+ * has the same type and instance name.
+ *
+ * @param instanceName the name of attribute to auto config
+ * @return this service dependency
+ */
+ public ResourceDependency setAutoConfig(String instanceName);
+
+ /**
+ * Sets the resource for this dependency.
+ *
+ * @param resource the URL of the resource
+ */
+ public ResourceDependency setResource(URL resource);
+
+ /**
+ * Determines if this is a required dependency or not.
+ *
+ * @param required <code>true</code> if the dependency is required
+ */
+ public ResourceDependency setRequired(boolean required);
+
+ /**
+ * Sets the filter condition for this resource dependency.
+ *
+ * @param resourceFilter the filter condition
+ */
+ public ResourceDependency setFilter(String resourceFilter);
+
+ /** @see ResourceDependency#setPropagate(Object, String) */
+ public ResourceDependency setPropagate(boolean propagate);
+
+ /**
+ * Sets an Object instance and a callback method used to propagate some properties to the provided service properties.
+ * The method will be invoked on the specified object instance and must have one of the following signatures:<p>
+ * <ul><li>Dictionary callback(ServiceReference, Object service)
+ * <li>Dictionary callback(ServiceReference)
+ * </ul>
+ * @param instance the Object instance which is used to retrieve propagated service properties
+ * @param method the method to invoke for retrieving the properties to be propagated to the service properties.
+ * @return this service dependency.
+ */
+ public ResourceDependency setPropagate(Object instance, String method);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceHandler.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceHandler.java
new file mode 100644
index 0000000..5534580
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceHandler.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.dm;
+
+import java.net.URL;
+import java.util.Dictionary;
+
+/**
+ * Service interface for anybody wanting to be notified of changes to resources.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ResourceHandler {
+ /** Name of the property that's used to describe the filter condition for a resource. */
+ public static final String FILTER = "filter";
+ /** Exact URL that this handler is looking for. Can be used instead of a filter to be very explicit about the resource you're looking for. */
+ public static final String URL = "url";
+ /** The host part of the URL. */
+ public static final String HOST = "host";
+ /** The path part of the URL. */
+ public static final String PATH = "path";
+ /** The protocol part of the URL. */
+ public static final String PROTOCOL = "protocol";
+ /** The port part of the URL. */
+ public static final String PORT = "port";
+
+ /**
+ * @deprecated Please use {@link #added(URL, Dictionary)} instead. When both are specified,
+ * the new method takes precedence and the deprecated one is not invoked.
+ */
+ public void added(URL resource);
+
+ /**
+ * Invoked whenever a new resource is added.
+ */
+ public void added(URL resource, Dictionary<?, ?> resourceProperties);
+
+ /**
+ * @deprecated Please use {@link #changed(URL, Dictionary)} instead. When both are specified,
+ * the new method takes precedence and the deprecated one is not invoked.
+ */
+ public void changed(URL resource);
+
+ /**
+ * Invoked whenever an existing resource changes.
+ */
+ public void changed(URL resource, Dictionary<?, ?> resourceProperties);
+
+ /**
+ * @deprecated Please use {@link #removed(URL, Dictionary)} instead. When both are specified,
+ * the new method takes precedence and the deprecated one is not invoked.
+ */
+ public void removed(URL resource);
+
+ /**
+ * Invoked whenever an existing resource is removed.
+ */
+ public void removed(URL resource, Dictionary<?, ?> resourceProperties);
+}
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceUtil.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceUtil.java
new file mode 100644
index 0000000..2112ffe
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ResourceUtil.java
@@ -0,0 +1,45 @@
+/*
+ * 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.dm;
+
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+/**
+ * Utility class for resource handling.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceUtil {
+ /**
+ * Creates a set of properties for a resource based on its URL.
+ *
+ * @param url the URL
+ * @return a set of properties
+ */
+ public static Dictionary<?, ?> createProperties(URL url) {
+ Hashtable<String, Object> props = new Hashtable<>();
+ props.put(ResourceHandler.PROTOCOL, url.getProtocol());
+ props.put(ResourceHandler.HOST, url.getHost());
+ props.put(ResourceHandler.PORT, Integer.toString(url.getPort()));
+ props.put(ResourceHandler.PATH, url.getPath());
+ return props;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ServiceDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ServiceDependency.java
new file mode 100644
index 0000000..7cda8a8
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/ServiceDependency.java
@@ -0,0 +1,285 @@
+/*
+ * 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.dm;
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Service dependency that can track an OSGi service.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ServiceDependency extends Dependency, ComponentDependencyDeclaration {
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added or removed. When you specify callbacks, the auto configuration
+ * feature is automatically turned off, because we're assuming you don't need it in this
+ * case.
+ *
+ * @param add the method to call when a service was added
+ * @param remove the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(String add, String remove);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ *
+ * @param add the method to call when a service was added
+ * @param change the method to call when a service was changed
+ * @param remove the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(String add, String change, String remove);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ * @param add the method to call when a service was added
+ * @param change the method to call when a service was changed
+ * @param remove the method to call when a service was removed
+ * @param swap the method to call when the service was swapped due to addition or
+ * removal of an aspect
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(String add, String change, String remove, String swap);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added or removed. They are called on the instance you provide. When you
+ * specify callbacks, the auto configuration feature is automatically turned off, because
+ * we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param add the method to call when a service was added
+ * @param remove the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(Object instance, String add, String remove);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. They are called on the instance you provide. When you
+ * specify callbacks, the auto configuration feature is automatically turned off, because
+ * we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param add the method to call when a service was added
+ * @param change the method to call when a service was changed
+ * @param remove the method to call when a service was removed
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(Object instance, String add, String change, String remove);
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. When you specify callbacks, the auto
+ * configuration feature is automatically turned off, because we're assuming you don't
+ * need it in this case.
+ * @param instance the instance to call the callbacks on
+ * @param added the method to call when a service was added
+ * @param changed the method to call when a service was changed
+ * @param removed the method to call when a service was removed
+ * @param swapped the method to call when the service was swapped due to addition or
+ * removal of an aspect
+ * @return this service dependency
+ */
+ public ServiceDependency setCallbacks(Object instance, String added, String changed, String removed, String swapped);
+
+ /**
+ * Sets the required flag which determines if this service is required or not.
+ * A ServiceDependency is false by default.
+ *
+ * @param required the required flag
+ * @return this service dependency
+ */
+ public ServiceDependency setRequired(boolean required);
+
+ /**
+ * Sets auto configuration for this service. Auto configuration allows the
+ * dependency to fill in the attribute in the service implementation that
+ * has the same type and instance name. Dependency services will be injected
+ * in the following kind of fields:<p>
+ * <ul>
+ * <li> a field having the same type as the dependency. If the field may be accessed by anythread, then
+ * the field should be declared volatile, in order to ensure visibility when the field is auto injected concurrently.
+ *
+ * <li> a field which is assignable to an <code>Iterable<T></code> where T must match the dependency type.
+ * In this case, an Iterable will be injected by DependencyManager before the start callback is called.
+ * The Iterable field may then be traversed to inspect the currently available dependency services. The Iterable
+ * can possibly be set to a final value so you can choose the Iterable implementation of your choice
+ * (for example, a CopyOnWrite ArrayList, or a ConcurrentLinkedQueue).
+ *
+ * <li> a <code>Map<K,V></code> where K must match the dependency type and V must exactly equals <code>Dictionary</code>.
+ * In this case, a ConcurrentHashMap will be injected by DependencyManager before the start callback is called.
+ * The Map may then be consulted to lookup current available dependency services, including the dependency service
+ * properties (the map key holds the dependency service, and the map value holds the dependency service properties).
+ *
+ * The Map field may be set to a final value so you can choose a Map of your choice (Typically a ConcurrentHashMap).
+ *
+ * A ConcurrentHashMap is "weakly consistent", meaning that when traversing
+ * the elements, you may or may not see any concurrent updates made on the map. So, take care to traverse
+ * the map using an iterator on the map entry set, which allows to atomically lookup pairs of Dependency service/Service properties.
+ * </ul>
+ *
+ * <p> Here are some example using an Iterable:
+ * <blockquote>
+ *
+ * <pre>
+ *
+ * public class SpellChecker {
+ * // can be traversed to inspect currently available dependencies
+ * final Iterable<DictionaryService> dictionaries = new ConcurrentLinkedQueue<>();
+ *
+ * Or
+ *
+ * // will be injected by DM automatically and can be traversed any time to inspect all currently available dependencies.
+ * volatile Iterable<DictionaryService> dictionaries = null;
+ * }
+ *
+ * </pre>
+ * </blockquote>
+ *
+ * Here are some example using a Map:
+ * <blockquote>
+ *
+ * <pre>
+ *
+ * public class SpellChecker {
+ * // can be traversed to inspect currently available dependencies
+ * final Map<DictionaryService, Dictionary> dictionaries = new ConcurrentLinkedQueue<>();
+ *
+ * or
+ *
+ * // will be injected by DM automatically and can be traversed to inspect currently available dependencies
+ * volatile Map<DictionaryService, Dictionary> dictionaries = null;
+ *
+ * void iterateOnAvailableServices() {
+ * for (Map.Entry<MyService, Dictionary> entry : this.services.entrySet()) {
+ * MyService currentService = entry.getKey();
+ * Dictionary currentServiceProperties = entry.getValue();
+ * // ...
+ * }
+ * }
+ * }
+ *
+ * </pre>
+ * </blockquote>
+ *
+ * @param autoConfig the name of attribute to auto configure
+ * @return this service dependency
+ */
+ public ServiceDependency setAutoConfig(boolean autoConfig);
+
+ /**
+ * Sets auto configuration for this service. Auto configuration allows the
+ * dependency to fill in the attribute in the service implementation that
+ * has the same type and instance name.
+ *
+ * @param instanceName the name of attribute to auto config
+ * @return this service dependency
+ * @see #setAutoConfig(boolean)
+ */
+ public ServiceDependency setAutoConfig(String instanceName);
+
+ /**
+ * Sets the name of the service that should be tracked.
+ *
+ * @param serviceName the name of the service
+ * @return this service dependency
+ */
+ public ServiceDependency setService(Class<?> serviceName);
+
+ /**
+ * Sets the name of the service that should be tracked. You can either specify
+ * only the name, or the name and a filter. In the latter case, the filter is used
+ * to track the service and should only return services of the type that was specified
+ * in the name. To make sure of this, the filter is actually extended internally to
+ * filter on the correct name.
+ *
+ * @param serviceName the name of the service
+ * @param serviceFilter the filter condition
+ * @return this service dependency
+ */
+ public ServiceDependency setService(Class<?> serviceName, String serviceFilter);
+
+ /**
+ * Sets the filter for the services that should be tracked. Any service object
+ * matching the filter will be returned, without any additional filter on the
+ * class.
+ *
+ * @param serviceFilter the filter condition
+ * @return this service dependency
+ */
+ public ServiceDependency setService(String serviceFilter);
+
+ /**
+ * Sets the name of the service that should be tracked. You can either specify
+ * only the name, or the name and a reference. In the latter case, the service reference
+ * is used to track the service and should only return services of the type that was
+ * specified in the name.
+ *
+ * @param serviceName the name of the service
+ * @param serviceReference the service reference to track
+ * @return this service dependency
+ */
+ public ServiceDependency setService(Class<?> serviceName, ServiceReference serviceReference);
+
+ /**
+ * Sets the default implementation for this service dependency. You can use this to supply
+ * your own implementation that will be used instead of a Null Object when the dependency is
+ * not available. This is also convenient if the service dependency is not an interface
+ * (which would cause the Null Object creation to fail) but a class.
+ *
+ * @param implementation the instance to use or the class to instantiate if you want to lazily
+ * instantiate this implementation
+ * @return this service dependency
+ */
+ public ServiceDependency setDefaultImplementation(Object implementation);
+
+ /**
+ * Sets propagation of the service dependency properties to the provided service properties. Any additional
+ * service properties specified directly are merged with these.
+ */
+ public ServiceDependency setPropagate(boolean propagate);
+
+ /**
+ * Sets an Object instance and a callback method used to propagate some properties to the provided service properties.
+ * The method will be invoked on the specified object instance and must have one of the following signatures:<p>
+ * <ul><li>Dictionary callback(ServiceReference, Object service)
+ * <li>Dictionary callback(ServiceReference)
+ * </ul>
+ * @param instance the Object instance which is used to retrieve propagated service properties
+ * @param method the method to invoke for retrieving the properties to be propagated to the service properties.
+ * @return this service dependency.
+ */
+ public ServiceDependency setPropagate(Object instance, String method);
+
+ /**
+ * Enabled debug logging for this dependency instance. The logging is prefixed with the given identifier.
+ * @param debugKey a prefix log identifier
+ * @return this service dependency.
+ */
+ public ServiceDependency setDebug(String debugKey);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/AbstractDependency.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/AbstractDependency.java
new file mode 100644
index 0000000..d3890bd
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/AbstractDependency.java
@@ -0,0 +1,559 @@
+/*
+ * 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.dm.context;
+
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.ServiceDependency;
+
+/**
+ * Abstract class for implementing Dependencies.
+ * You can extends this class in order to supply your own custom dependencies to any Dependency Manager Component.
+ *
+ * @param <T> The type of the interface representing a Dependency Manager Dependency (must extends the Dependency interface).
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class AbstractDependency<T extends Dependency> implements
+ Dependency, DependencyContext, ComponentDependencyDeclaration {
+
+ /**
+ * The Component implementation is exposed to Dependencies through this interface.
+ */
+ protected ComponentContext m_component;
+
+ /**
+ * Is this Dependency available ? Volatile because the getState method (part of the
+ * {@link ComponentDependencyDeclaration} interface) may be called by any thread, at any time.
+ */
+ protected volatile boolean m_available;
+
+ /**
+ * Is this Dependency "instance bound" ? A dependency is "instance bound" if it is defined within the component's
+ * init method, meaning that it won't deactivate the component if it is not currently available when being added
+ * from the component's init method.
+ */
+ protected boolean m_instanceBound;
+
+ /**
+ * Is this dependency required (false by default) ?
+ */
+ protected volatile boolean m_required;
+
+ /**
+ * Component callback used to inject an added dependency.
+ */
+ protected String m_add;
+
+ /**
+ * Component callback invoked when the dependency has changed.
+ */
+ protected String m_change;
+
+ /**
+ * Component callback invoked when the dependency becomes unavailable.
+ */
+ protected String m_remove;
+
+ /**
+ * Can this Dependency be auto configured in the component instance fields ?
+ */
+ protected boolean m_autoConfig = true;
+
+ /**
+ * The Component field name where the Dependency can be injected (null means any field with a compatible type
+ * will be injected).
+ */
+ protected String m_autoConfigInstance;
+
+ /**
+ * Indicates if the setAutoConfig method has been invoked. This flag is used to force autoconfig to "false"
+ * when the setCallbacks method is invoked, unless the setAutoConfig method has been called.
+ */
+ protected boolean m_autoConfigInvoked;
+
+ /**
+ * Has this Dependency been started by the Component implementation ? Volatile because the getState method
+ * (part of the {@link ComponentDependencyDeclaration} interface) may be called by any thread, at any time.
+ */
+ protected volatile boolean m_isStarted;
+
+ /**
+ * The object instance on which the dependency callbacks are invoked on. Null means the dependency will be
+ * injected to the Component implementation instance(s).
+ */
+ protected Object m_callbackInstance;
+
+ /**
+ * Tells if the dependency service properties have to be propagated to the Component service properties.
+ */
+ protected boolean m_propagate;
+
+ /**
+ * The propagate callback instance that is invoked in order to supply dynamically some dependency service properties.
+ */
+ protected Object m_propagateCallbackInstance;
+
+ /**
+ * The propagate callback method that is invoked in order to supply dynamically some dependency service properties.
+ * @see {@link #m_propagateCallbackInstance}
+ */
+ protected volatile String m_propagateCallbackMethod;
+
+ /**
+ * Default empty dependency properties.
+ */
+ protected final static Dictionary<Object, Object> EMPTY_PROPERTIES = new Hashtable<>(0);
+
+ /**
+ * Creates a new Dependency. By default, the dependency is optional and autoconfig.
+ */
+ public AbstractDependency() {
+ }
+
+ /**
+ * Create a clone of a given Dependency.
+ * @param prototype all the fields of the prototype will be copied to this dependency.
+ */
+ public AbstractDependency(AbstractDependency<T> prototype) {
+ m_instanceBound = prototype.m_instanceBound;
+ m_required = prototype.m_required;
+ m_add = prototype.m_add;
+ m_change = prototype.m_change;
+ m_remove = prototype.m_remove;
+ m_autoConfig = prototype.m_autoConfig;
+ m_autoConfigInstance = prototype.m_autoConfigInstance;
+ m_autoConfigInvoked = prototype.m_autoConfigInvoked;
+ m_callbackInstance = prototype.m_callbackInstance;
+ m_propagate = prototype.m_propagate;
+ m_propagateCallbackInstance = prototype.m_propagateCallbackInstance;
+ m_propagateCallbackMethod = prototype.m_propagateCallbackMethod;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder(getType()).append(" dependency [").append(getName()).append("]").toString();
+ }
+
+ // ----------------------- Dependency interface -----------------------------
+
+ /**
+ * Is this Dependency required (false by default) ?
+ */
+ @Override
+ public boolean isRequired() {
+ return m_required;
+ }
+
+ /**
+ * Is this Dependency satisfied and available ?
+ */
+ @Override
+ public boolean isAvailable() {
+ return m_available;
+ }
+
+ /**
+ * Can this dependency be injected in a component class field (by reflexion, true by default) ?
+ */
+ @Override
+ public boolean isAutoConfig() {
+ return m_autoConfig;
+ }
+
+ /**
+ * Returns the field name when the dependency can be injected to.
+ */
+ @Override
+ public String getAutoConfigName() {
+ return m_autoConfigInstance;
+ }
+
+ /**
+ * Returns the propagate callback method that is invoked in order to supply dynamically some dependency service properties.
+ * @see {@link #m_propagateCallbackInstance}
+ */
+ @Override
+ public boolean isPropagated() {
+ return m_propagate;
+ }
+
+ /**
+ * Returns the dependency service properties (empty by default).
+ */
+ @SuppressWarnings("unchecked")
+ @Override
+ public <K,V> Dictionary<K, V> getProperties() {
+ return (Dictionary<K, V>) EMPTY_PROPERTIES;
+ }
+
+ // -------------- DependencyContext interface -----------------------------------------------
+
+ /**
+ * Called by the Component implementation before the Dependency can be started.
+ */
+ @Override
+ public void setComponentContext(ComponentContext component) {
+ m_component = component;
+ }
+
+ /**
+ * A Component callback must be invoked with dependency event(s).
+ * @param type the dependency event type
+ * @param events the dependency service event to inject in the component.
+ * The number of events depends on the dependency event type: ADDED/CHANGED/REMOVED types only has one event parameter,
+ * but the SWAPPED type has two event parameters: the first one is the old event which must be replaced by the second one.
+ */
+ @Override
+ public void invokeCallback(EventType type, Event ... events) {
+ }
+
+ /**
+ * Starts this dependency. Subclasses can override this method but must then call super.start().
+ */
+ @Override
+ public void start() {
+ m_isStarted = true;
+ }
+
+ /**
+ * Starts this dependency. Subclasses can override this method but must then call super.stop().
+ */
+ @Override
+ public void stop() {
+ m_isStarted = false;
+ }
+
+ /**
+ * Indicates if this dependency has been started by the Component implementation.
+ */
+ @Override
+ public boolean isStarted() {
+ return m_isStarted;
+ }
+
+ /**
+ * Called by the Component implementation when the dependency is considered to be available.
+ */
+ @Override
+ public void setAvailable(boolean available) {
+ m_available = available;
+ }
+
+ /**
+ * Is this Dependency "instance bound" (has been defined within the component's init method) ?
+ */
+ public boolean isInstanceBound() {
+ return m_instanceBound;
+ }
+
+ /**
+ * Called by the Component implementation when the dependency is declared within the Component's init method.
+ */
+ public void setInstanceBound(boolean instanceBound) {
+ m_instanceBound = instanceBound;
+ }
+
+ /**
+ * Tells if the Component must be first instantiated before starting this dependency (false by default).
+ */
+ @Override
+ public boolean needsInstance() {
+ return false;
+ }
+
+ /**
+ * Returns the type of the field where this dependency can be injected (auto config), or return null
+ * if autoconfig is not supported.
+ */
+ @Override
+ public abstract Class<?> getAutoConfigType();
+
+ /**
+ * Get the highest ranked available dependency service, or null.
+ */
+ @Override
+ public Event getService() {
+ Event event = m_component.getDependencyEvent(this);
+ if (event == null) {
+ Object defaultService = getDefaultService(true);
+ if (defaultService != null) {
+ event = new Event(defaultService);
+ }
+ }
+ return event;
+ }
+
+ /**
+ * Copy all dependency service instances to the given collection.
+ */
+ @Override
+ public void copyToCollection(Collection<Object> services) {
+ Set<Event> events = m_component.getDependencyEvents(this);
+ if (events.size() > 0) {
+ for (Event e : events) {
+ services.add(e.getEvent());
+ }
+ } else {
+ Object defaultService = getDefaultService(false);
+ if (defaultService != null) {
+ services.add(defaultService);
+ }
+ }
+ }
+
+ /**
+ * Copy all dependency service instances to the given map (key = dependency service, value = dependency service properties.
+ */
+ @Override
+ public void copyToMap(Map<Object, Dictionary<?, ?>> map) {
+ Set<Event> events = m_component.getDependencyEvents(this);
+ if (events.size() > 0) {
+ for (Event e : events) {
+ map.put(e.getEvent(), e.getProperties());
+ }
+ } else {
+ Object defaultService = getDefaultService(false);
+ if (defaultService != null) {
+ map.put(defaultService, EMPTY_PROPERTIES);
+ }
+ }
+ }
+
+ /**
+ * Creates a copy of this Dependency.
+ */
+ @Override
+ public abstract DependencyContext createCopy();
+
+ // -------------- ComponentDependencyDeclaration -----------------------------------------------
+
+ /**
+ * Returns a description of this dependency (like the dependency service class name with associated filters)
+ */
+ @Override
+ public String getName() {
+ return getSimpleName();
+ }
+
+ /**
+ * Returns a simple name for this dependency (like the dependency service class name).
+ */
+ @Override
+ public abstract String getSimpleName();
+
+ /**
+ * Returns the dependency symbolic type.
+ */
+ @Override
+ public abstract String getType();
+
+ /**
+ * Returns the dependency filter, if any.
+ */
+ @Override
+ public String getFilter() {
+ return null;
+ }
+
+ /**
+ * Returns this dependency state.
+ */
+ @Override
+ public int getState() { // Can be called from any threads, but our class attributes are volatile
+ if (m_isStarted) {
+ return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
+ } else {
+ return isRequired() ? ComponentDependencyDeclaration.STATE_REQUIRED
+ : ComponentDependencyDeclaration.STATE_OPTIONAL;
+ }
+ }
+
+ // -------------- Methods common to sub interfaces of Dependendency
+
+ /**
+ * Activates Dependency service properties propagation (to the service properties of the component to which this
+ * dependency is added).
+ *
+ * @param propagate true if the dependency service properties must be propagated to the service properties of
+ * the component to which this dependency is added.
+ * @return this dependency instance
+ */
+ @SuppressWarnings("unchecked")
+ public T setPropagate(boolean propagate) {
+ ensureNotActive();
+ m_propagate = propagate;
+ return (T) this;
+ }
+
+ /**
+ * Sets a callback instance which can ba invoked with the given method in order to dynamically retrieve the
+ * dependency service properties.
+ *
+ * @param instance the callback instance
+ * @param method the method to invoke on the callback instance
+ * @return this dependency instance
+ */
+ @SuppressWarnings("unchecked")
+ public T setPropagate(Object instance, String method) {
+ setPropagate(instance != null && method != null);
+ m_propagateCallbackInstance = instance;
+ m_propagateCallbackMethod = method;
+ return (T) this;
+ }
+
+ /**
+ * Sets the add/remove callbacks.
+ * @param add the callback to invoke when a dependency is added
+ * @param remove the callback to invoke when a dependency is removed
+ * @return this dependency instance
+ */
+ public T setCallbacks(String add, String remove) {
+ return setCallbacks(add, null, remove);
+ }
+
+ /**
+ * Sets the add/change/remove callbacks.
+ * @param add the callback to invoke when a dependency is added
+ * @param change the callback to invoke when a dependency has changed
+ * @param remove the callback to invoke when a dependency is removed
+ * @return this dependency instance
+ */
+ public T setCallbacks(String add, String change, String remove) {
+ return setCallbacks(null, add, change, remove);
+ }
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added or removed. They are called on the instance you provide. When you
+ * specify callbacks, the auto configuration feature is automatically turned off, because
+ * we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param add the method to call when a service was added
+ * @param remove the method to call when a service was removed
+ * @return this service dependency
+ */
+ public T setCallbacks(Object instance, String add, String remove) {
+ return setCallbacks(instance, add, null, remove);
+ }
+
+ /**
+ * Sets the callbacks for this service. These callbacks can be used as hooks whenever a
+ * dependency is added, changed or removed. They are called on the instance you provide. When you
+ * specify callbacks, the auto configuration feature is automatically turned off, because
+ * we're assuming you don't need it in this case.
+ *
+ * @param instance the instance to call the callbacks on
+ * @param add the method to call when a service was added
+ * @param change the method to call when a service was changed
+ * @param remove the method to call when a service was removed
+ * @return this service dependency
+ */
+ @SuppressWarnings("unchecked")
+ public T setCallbacks(Object instance, String add, String change, String remove) {
+ if ((add != null || change != null || remove != null) && !m_autoConfigInvoked) {
+ setAutoConfig(false);
+ }
+ m_callbackInstance = instance;
+ m_add = add;
+ m_change = change;
+ m_remove = remove;
+ return (T) this;
+ }
+
+ /**
+ * Returns the dependency callback instances
+ * @return the dependency callback instances
+ */
+ public Object[] getInstances() {
+ if (m_callbackInstance == null) {
+ return m_component.getInstances();
+ } else {
+ return new Object[] { m_callbackInstance };
+ }
+ }
+
+ /**
+ * @see {@link ServiceDependency#setRequired(boolean)}
+ */
+ @SuppressWarnings("unchecked")
+ public T setRequired(boolean required) {
+ m_required = required;
+ return (T) this;
+ }
+
+ /**
+ * @see {@link ServiceDependency#setAutoConfig(boolean)}
+ */
+ @SuppressWarnings("unchecked")
+ public T setAutoConfig(boolean autoConfig) {
+ if (autoConfig && getAutoConfigType() == null) {
+ throw new IllegalStateException("Dependency does not support auto config mode");
+ }
+ m_autoConfig = autoConfig;
+ m_autoConfigInvoked = true;
+ return (T) this;
+ }
+
+ /**
+ * @see {@link ServiceDependency#setAutoConfig(String instanceName)}
+ */
+ @SuppressWarnings("unchecked")
+ public T setAutoConfig(String instanceName) {
+ if (instanceName != null && getAutoConfigType() == null) {
+ throw new IllegalStateException("Dependency does not support auto config mode");
+ }
+ m_autoConfig = (instanceName != null);
+ m_autoConfigInstance = instanceName;
+ m_autoConfigInvoked = true;
+ return (T) this;
+ }
+
+ /**
+ * Returns the component implementation context
+ * @return the component implementation context
+ */
+ public ComponentContext getComponentContext() {
+ return m_component;
+ }
+
+ /**
+ * Returns the default service, or null.
+ * @param nullObject if true, a null object may be returned.
+ * @return the default service
+ */
+ protected Object getDefaultService(boolean nullObject) {
+ return null;
+ }
+
+ /**
+ * Checks if the component dependency is not started.
+ */
+ protected void ensureNotActive() {
+ if (isStarted()) {
+ throw new IllegalStateException("Cannot modify state while active.");
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
new file mode 100644
index 0000000..a62619c
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/ComponentContext.java
@@ -0,0 +1,148 @@
+/*
+ * 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.dm.context;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.Logger;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+/**
+ * This interface is the entry point to the Component implementation context.
+ * It is used by all DependencyManager Dependency implementations.
+ *
+ * @see DependencyContext interface
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface ComponentContext extends Component {
+ /**
+ * Returns the logger which can be used by the DependencyManager Dependencies implementations.
+ */
+ public Logger getLogger();
+
+ /**
+ * Returns the Component's bundle context
+ * @return the Component's bundle context
+ */
+ public BundleContext getBundleContext();
+
+ /**
+ * Returns the Compoent's bundle.
+ * @return the Compoent's bundle.
+ */
+ public Bundle getBundle();
+
+ /**
+ * Sets a threadpool that the component will use when handling external events
+ * @param threadPool a threadpool used to handle component events and invoke the component's lifecycle callbacks
+ */
+ public void setThreadPool(Executor threadPool);
+
+ /**
+ * Starts the component. All initial dependencies previously added to the component will be started.
+ */
+ public void start();
+
+ /**
+ * Stops the component.
+ */
+ public void stop();
+
+ /**
+ * Is this component already started ?
+ * @return true if this component has been started
+ */
+ public boolean isActive();
+
+ /**
+ * Is this component available (all required dependencies are available) ?
+ * @return true if this component is available (all dependencies are available), or false
+ */
+ public boolean isAvailable();
+
+ /**
+ * Notifies the Component about a dependency event.
+ * An event is for example fired when:<p>
+ * <ul>
+ * <li> a dependency service becomes available {@link EventType#ADDED})
+ * <li> a dependenc service has changed is changed {@link EventType#CHANGED})
+ * <li> a dependency service has been lost {@link EventType#REMOVED})
+ * <li> a dependency service has been swapped by another {@link EventType#SWAPPED})
+ * </ul>
+ * @param dc the dependency
+ * @param type the dependency event type
+ * @param e the dependency event
+ * @see EventType
+ */
+ public void handleEvent(DependencyContext dc, EventType type, Event ... event);
+
+ /**
+ * Returns the list of dependencies that has been registered on this component
+ * @return the list of dependencies that has been registered on this component
+ */
+ public List<DependencyContext> getDependencies();
+
+ /**
+ * Invoke a component callback method with a given dependency service instance
+ * @param instances the component instances
+ * @param methodName the method name
+ * @param signatures the method signatures (types)
+ * @param parameters the method parameters
+ */
+ public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures, Object[][] parameters);
+
+ /**
+ * Returns the component instances
+ * @return the component instances
+ */
+ public Object[] getInstances();
+
+ /**
+ * Returns the component instance field that is assignable to a given class type
+ * @param clazz the type of an object that has to be injected in the component instance field
+ * @return the name of the component instance field that can be assigned to an object having the same type as
+ * the "clazz" parameter
+ */
+ public String getAutoConfigInstance(Class<?> clazz);
+
+ /**
+ * Indicates if an object of the given class can be injected in one field of the component
+ * @param clazz the class of an object that has to be injected in one of the component fields
+ * @return true if the component can be injected with an object having the specified "clazz" type.
+ */
+ public boolean getAutoConfig(Class<?> clazz);
+
+ /**
+ * Returns the highest ranked dependency service instance for a given dependency
+ * @param dc the dependency
+ * @return the highest ranked dependency service instance for a given dependency
+ */
+ public Event getDependencyEvent(DependencyContext dc);
+
+ /**
+ * Returns all the available dependency services for a given dependency
+ * @param dc the dependency
+ * @return all the available dependency services for a given dependency
+ */
+ public Set<Event> getDependencyEvents(DependencyContext dc);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/DependencyContext.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/DependencyContext.java
new file mode 100644
index 0000000..42043e8
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/DependencyContext.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.dm.context;
+
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Map;
+
+import org.apache.felix.dm.Dependency;
+
+/**
+ * Every DependencyManager Dependency implementations must implement this interface.
+ *
+ * @see {@link AbstractDependency} which already implements most of the methods from this interface.
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public interface DependencyContext extends Dependency {
+ /**
+ * Stores the Component implementation context in the Dependency Implementation. This object is the entry point to
+ * the Component implementation.
+ * @param component the Component implementation context
+ */
+ public void setComponentContext(ComponentContext component);
+
+ /**
+ * Returns the Component implementation context associated to this Dependency context.
+ */
+ public ComponentContext getComponentContext();
+
+ /**
+ * The Component implementation asks this dependency to invoke a component dependency callback.
+ *
+ * @param type the type of the callback to invoke (add/change/remove/swap ...)
+ * @param events the dependency service event(s) that has previously been submitted to the component implementation using
+ * the ComponentContext.handleEvent method. The number of events depends on the event type: one event for ADDED/CHANGED/REMOVED,
+ * and two events for the SWAPPED event.
+ * @see ComponentContext#handleEvent(DependencyContext, EventType, Event...)
+ * @see EventType
+ */
+ public void invokeCallback(EventType type, Event ... events);
+
+ /**
+ * Invoked by the component context when the dependency should start working.
+ **/
+ public void start();
+
+ /**
+ * Invoked by the component context when the dependency should stop working.
+ **/
+ public void stop();
+
+ /**
+ * Returns true if the dependency has been started, false if not
+ * @return true if the dependency has been started, false if not
+ */
+ public boolean isStarted();
+
+ /**
+ * Sets this dependency as available, meaning that at least one dependency service is available.
+ * @param available true to mark this dependency as available, false to mark it as unavailable
+ */
+ public void setAvailable(boolean available);
+
+ /**
+ * Sets this dependency as "instance bound". A dependency is "instance bound" if it is defined from the
+ * component's init method.
+ * @param true if the dependency has to be marked as "intance bound", false if not.
+ */
+ public void setInstanceBound(boolean instanceBound);
+
+ /**
+ * Is this dependency instance bound ?
+ * @return true if this dependency is instance bound, false if not
+ */
+ public boolean isInstanceBound();
+
+ /**
+ * Does this dependency need the component instances to determine if the dependency is available or not.
+ * @return true if the dependency need the component instances before it can be started, false if not.
+ **/
+ public boolean needsInstance();
+
+ /**
+ * Returns the type of the field which can be injected with the dependency service.
+ * @return the type of the field which can be injected with the dependency service, or null if the dependency does not
+ * support auto config mode.
+ */
+ public Class<?> getAutoConfigType();
+
+ /**
+ * Returns the highest ranked available dependency service instance, or null if the dependency is unavailable.
+ * @return the highest ranked available dependency service instance, or null
+ */
+ public Event getService();
+
+ /**
+ * Copies all the dependency service instances to the given collection.
+ * @param coll the collection where the dependency service instances will be copied
+ */
+ public void copyToCollection(Collection<Object> coll);
+
+ /**
+ * Copies all the dependency service instances to the given map (key = dependency service, value = dependency servie properties).
+ * @param map the map where the dependency service instances (with the corresponding service properties)
+ */
+ public void copyToMap(Map<Object, Dictionary<?, ?>> map);
+
+ /**
+ * Creates a clone of this dependency.
+ * @return a clone of this dependency.
+ */
+ public DependencyContext createCopy();
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/Event.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/Event.java
new file mode 100644
index 0000000..1d48eaf
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/Event.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dm.context;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+/**
+ * An event holds all data that belongs to some external event as it comes in via
+ * the 'changed' callback of a dependency.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Event implements Comparable<Event> {
+ protected final static Dictionary<Object, Object> EMPTY_PROPERTIES = new Hashtable<>();
+ private final Object m_event; // the actual event object (a Service, a Bundle, a Configuration, etc ...)
+
+ public Event(Object event) {
+ m_event = event;
+ }
+
+ /**
+ * Returns the actual event object wrapped by this event (a Service Dependency, a Bundle for Bundle Dependency, etc...).
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T getEvent() {
+ return (T) m_event;
+ }
+
+ /**
+ * Returns the properties of the actual event object wrapped by this event (Service Dependency properties, ...).
+ */
+ @SuppressWarnings("unchecked")
+ public <K,V> Dictionary<K,V> getProperties() {
+ return (Dictionary<K,V>) EMPTY_PROPERTIES;
+ }
+
+ @Override
+ public int hashCode() {
+ return m_event.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // an instanceof check here is not "strong" enough with subclasses overriding the
+ // equals: we need to be sure that a.equals(b) == b.equals(a) at all times
+ if (obj != null && obj.getClass().equals(Event.class)) {
+ return (((Event) obj).m_event).equals(m_event);
+ }
+ return false;
+ }
+
+ @Override
+ public int compareTo(Event o) {
+ return 0;
+ }
+
+ /**
+ * Release the resources this event is holding (like service reference for example).
+ */
+ public void close() {
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/EventType.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/EventType.java
new file mode 100644
index 0000000..28f514f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/EventType.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.context;
+
+/**
+ * Types of dependency events
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public enum EventType {
+ /**
+ * A Dependency service becomes available.
+ */
+ ADDED,
+
+ /**
+ * A Dependency service has changed.
+ */
+ CHANGED,
+
+ /**
+ * A Dependency service becomes unavailable.
+ */
+ REMOVED,
+
+ /**
+ * A Dependency service has been swapped by another one.
+ */
+ SWAPPED
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/packageinfo b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/packageinfo
new file mode 100644
index 0000000..6af07c8
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/context/packageinfo
@@ -0,0 +1 @@
+version 4.0.0
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AbstractDecorator.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AbstractDecorator.java
new file mode 100644
index 0000000..044166b
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AbstractDecorator.java
@@ -0,0 +1,221 @@
+/*
+ * 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.dm.impl;
+
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.context.ComponentContext;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class AbstractDecorator {
+ protected volatile DependencyManager m_manager;
+ private final Map<Object, Component> m_services = new ConcurrentHashMap<>();
+
+ public abstract Component createService(Object[] properties);
+
+ /**
+ * Catches our DependencyManager handle from our component init method.
+ */
+ public void init(Component c) {
+ m_manager = c.getDependencyManager();
+ }
+
+ /**
+ * Extra method, which may be used by sub-classes, when adaptee has changed.
+ * For now, it's only used by the FactoryConfigurationAdapterImpl class,
+ * but it might also make sense to use this for Resource Adapters ...
+ */
+ public void updateService(Object[] properties) {
+ throw new NoSuchMethodError("Method updateService not implemented");
+ }
+
+ /**
+ * Set some service properties to all already instantiated services.
+ */
+ public void setServiceProperties(Dictionary<?,?> serviceProperties) {
+ for (Component component : m_services.values()) {
+ component.setServiceProperties(serviceProperties);
+ }
+ }
+
+ /**
+ * Remove a StateListener from all already instantiated services.
+ */
+ public void addStateListener(ComponentStateListener listener) {
+ for (Component component : m_services.values()) {
+ component.add(listener);
+ }
+ }
+
+ /**
+ * Remove a StateListener from all already instantiated services.
+ */
+ public void removeStateListener(ComponentStateListener listener) {
+ for (Component component : m_services.values()) {
+ component.remove(listener);
+ }
+ }
+
+ /**
+ * Add a Dependency to all already instantiated services.
+ */
+ public void addDependency(Dependency ... dependencies) {
+ for (Component component : m_services.values()) {
+ component.add(dependencies);
+ }
+ }
+
+ /**
+ * Remove a Dependency from all instantiated services.
+ */
+ public void removeDependency(Dependency d) {
+ for (Component component : m_services.values()) {
+ component.remove(d);
+ }
+ }
+
+ // callbacks for FactoryConfigurationAdapterImpl
+ public void updated(String pid, @SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
+ try {
+ Component service = m_services.get(pid);
+ if (service == null) {
+ service = createService(new Object[] { properties });
+ m_services.put(pid, service);
+ m_manager.add(service);
+ }
+ else {
+ updateService(new Object[] { properties, service });
+ }
+ }
+ catch (Throwable t) {
+ if (t instanceof ConfigurationException) {
+ throw (ConfigurationException) t;
+ }
+ else if (t.getCause() instanceof ConfigurationException) {
+ throw (ConfigurationException) t.getCause();
+ }
+ else {
+ throw new ConfigurationException(null, "Could not create service for ManagedServiceFactory Pid " + pid, t);
+ }
+ }
+ }
+
+ public void deleted(String pid) {
+ Component service = m_services.remove(pid);
+ if (service != null) {
+ m_manager.remove(service);
+ }
+ }
+
+ // callbacks for resources
+ public void added(URL resource) {
+ Component newService = createService(new Object[] { resource });
+ m_services.put(resource, newService);
+ m_manager.add(newService);
+ }
+
+ public void removed(URL resource) {
+ Component newService = m_services.remove(resource);
+ if (newService == null) {
+ throw new IllegalStateException("Service should not be null here.");
+ }
+ m_manager.remove(newService);
+ }
+
+ // callbacks for services
+ public void added(ServiceReference ref, Object service) {
+ Component newService = createService(new Object[] { ref, service });
+ m_services.put(ref, newService);
+ m_manager.add(newService);
+ }
+
+ public void removed(ServiceReference ref, Object service) {
+ Component newService;
+ newService = (Component) m_services.remove(ref);
+ if (newService == null) {
+ throw new IllegalStateException("Service should not be null here.");
+ }
+ m_manager.remove(newService);
+ }
+
+ public void swapped(ServiceReference oldRef, Object oldService, ServiceReference newRef, Object newService) {
+ Component service = (Component) m_services.remove(oldRef);
+ if (service == null) {
+ throw new IllegalStateException("Service should not be null here.");
+ }
+ m_services.put(newRef, service);
+ }
+
+ // callbacks for bundles
+ public void added(Bundle bundle) {
+ Component newService = createService(new Object[] { bundle });
+ m_services.put(bundle, newService);
+ m_manager.add(newService);
+ }
+
+ public void removed(Bundle bundle) {
+ Component newService;
+ newService = (Component) m_services.remove(bundle);
+ if (newService == null) {
+ throw new IllegalStateException("Service should not be null here.");
+ }
+ m_manager.remove(newService);
+ }
+
+ public void stop() {
+ for (Component component : m_services.values()) {
+ m_manager.remove(component);
+ }
+ }
+
+ public void configureAutoConfigState(Component target, ComponentContext source) {
+ configureAutoConfigState(target, source, BundleContext.class);
+ configureAutoConfigState(target, source, ServiceRegistration.class);
+ configureAutoConfigState(target, source, DependencyManager.class);
+ configureAutoConfigState(target, source, Component.class);
+ }
+
+ public Map<Object, Component> getServices() {
+ return m_services;
+ }
+
+ private void configureAutoConfigState(Component target, ComponentContext source, Class<?> clazz) {
+ String name = source.getAutoConfigInstance(clazz);
+ if (name != null) {
+ target.setAutoConfig(clazz, name);
+ }
+ else {
+ target.setAutoConfig(clazz, source.getAutoConfig(clazz));
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Activator.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Activator.java
new file mode 100644
index 0000000..a3fa10c
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/Activator.java
@@ -0,0 +1,67 @@
+/*
+ * 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.dm.impl;
+
+import org.apache.felix.dm.ComponentExecutorFactory;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+/**
+ * DependencyManager Activator used to track a ComponentExecutorFactory service optionally registered by
+ * a management agent bundle.
+ *
+ * @see {@link ComponentExecutorFactory}
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Activator implements BundleActivator, ServiceTrackerCustomizer {
+ private BundleContext m_context;
+
+ @Override
+ public void start(BundleContext context) throws Exception {
+ m_context = context;
+ Filter filter = context.createFilter("(objectClass=" + ComponentExecutorFactory.class.getName() + ")");
+ ServiceTracker tracker = new ServiceTracker(context, filter, this);
+ tracker.open();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ }
+
+ @Override
+ public Object addingService(ServiceReference reference) {
+ ComponentExecutorFactory factory = (ComponentExecutorFactory) m_context.getService(reference);
+ ComponentScheduler.instance().bind(factory);
+ return factory;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference reference, Object service) {
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service) {
+ ComponentExecutorFactory factory = (ComponentExecutorFactory) service;
+ ComponentScheduler.instance().unbind(factory);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AdapterServiceImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AdapterServiceImpl.java
new file mode 100644
index 0000000..6ee3264
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AdapterServiceImpl.java
@@ -0,0 +1,171 @@
+/*
+ * 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.dm.impl;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.context.DependencyContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Adapter Service implementation. This class extends the FilterService in order to catch
+ * some Service methods for configuring actual adapter service implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterServiceImpl extends FilterComponent {
+ /**
+ * Creates a new Adapter Service implementation.
+ *
+ * @param dm the dependency manager used to create our internal adapter service
+ * @param adapteeInterface the service interface to apply the adapter to
+ * @param adapteeFilter the filter condition to use with the service interface
+ * @param autoConfig the name of the member to inject the service into
+ * @param callbackInstance the instance to invoke the callback on, or null
+ * @param add name of the callback method to invoke on add
+ * @param change name of the callback method to invoke on change
+ * @param remove name of the callback method to invoke on remove
+ * @param swap name of the callback method to invoke on swap
+ * @param propagate true if the adaptee service properties should be propagated to the adapter service consumers
+ */
+ public AdapterServiceImpl(DependencyManager dm, Class<?> adapteeInterface, String adapteeFilter, String autoConfig,
+ Object callbackInstance, String add, String change, String remove, String swap, boolean propagate)
+ {
+ super(dm.createComponent()); // This service will be filtered by our super class, allowing us to take control.
+ m_component.setImplementation(new AdapterImpl(adapteeInterface, adapteeFilter, autoConfig, callbackInstance, add,
+ change, remove, swap, propagate))
+ .add(dm.createServiceDependency()
+ .setService(adapteeInterface, adapteeFilter)
+ .setAutoConfig(false)
+ .setCallbacks("added", null, "removed", "swapped"))
+ .setCallbacks("init", null, "stop", null);
+ }
+
+ public class AdapterImpl extends AbstractDecorator {
+ private final Class<?> m_adapteeInterface;
+ private final String m_adapteeFilter;
+ private final Object m_dependencyCallbackInstance;
+ private final String m_add;
+ private final String m_change;
+ private final String m_remove;
+ private final String m_swap;
+ private final String m_autoConfig;
+ private final boolean m_propagate;
+
+ public AdapterImpl(Class<?> adapteeInterface, String adapteeFilter, String autoConfig, Object callbackInstance, String add,
+ String change, String remove, String swap, boolean propagate) {
+ m_adapteeInterface = adapteeInterface;
+ m_adapteeFilter = adapteeFilter;
+ m_autoConfig = autoConfig;
+ m_dependencyCallbackInstance = callbackInstance;
+ m_add = add;
+ m_change = change;
+ m_swap = swap;
+ m_remove = remove;
+ m_propagate = propagate;
+ }
+
+ public Component createService(Object[] properties) {
+ ServiceReference ref = (ServiceReference) properties[0];
+ Object aspect = ref.getProperty(DependencyManager.ASPECT);
+ String serviceIdToTrack = (aspect != null) ? aspect.toString() : ref.getProperty(Constants.SERVICE_ID).toString();
+ List<DependencyContext> dependencies = m_component.getDependencies();
+ dependencies.remove(0);
+ ServiceDependency dependency = m_manager.createServiceDependency()
+ // create a dependency on both the service id we're adapting and possible aspects for this given service id
+ .setService(m_adapteeInterface, "(|(" + Constants.SERVICE_ID + "=" + serviceIdToTrack
+ + ")(" + DependencyManager.ASPECT + "=" + serviceIdToTrack + "))")
+ .setRequired(true);
+ if (m_add != null || m_change != null || m_remove != null || m_swap != null) {
+ dependency.setCallbacks(m_dependencyCallbackInstance, m_add, m_change, m_remove, m_swap);
+ }
+ if (m_autoConfig != null) {
+ dependency.setAutoConfig(m_autoConfig);
+ } else {
+ // enable auto configuration if there is no add callback or if there is one on a callbackInstance
+ dependency.setAutoConfig(m_add == null || (m_add != null && m_dependencyCallbackInstance != null));
+ }
+
+ if (m_propagate) {
+ dependency.setPropagate(this, "propagateAdapteeProperties");
+ }
+
+// dependency.setDebug("AdapterDependency#" + m_adapteeInterface.getSimpleName());
+
+ Component service = m_manager.createComponent()
+ .setInterface(m_serviceInterfaces, getServiceProperties(ref))
+ .setImplementation(m_serviceImpl)
+ .setFactory(m_factory, m_factoryCreateMethod) // if not set, no effect
+ .setComposition(m_compositionInstance, m_compositionMethod) // if not set, no effect
+ .setCallbacks(m_callbackObject, m_init, m_start, m_stop, m_destroy) // if not set, no effect
+ .add(dependency);
+
+ configureAutoConfigState(service, m_component);
+
+ for (DependencyContext dc : dependencies) {
+ service.add((Dependency) dc.createCopy());
+ }
+
+ for (ComponentStateListener stateListener : m_stateListeners) {
+ service.add(stateListener);
+ }
+ return service;
+ }
+
+ public String toString() {
+ return "Adapter for " + m_adapteeInterface + ((m_adapteeFilter != null) ? " with filter " + m_adapteeFilter : "");
+ }
+
+ public Dictionary<String, Object> getServiceProperties(ServiceReference ref) {
+ Dictionary<String, Object> props = new Hashtable<>();
+ if (m_serviceProperties != null) {
+ Enumeration<String> e = m_serviceProperties.keys();
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ props.put(key, m_serviceProperties.get(key));
+ }
+ }
+ return props;
+ }
+
+ public Dictionary<String, Object> propagateAdapteeProperties(ServiceReference ref) {
+ Dictionary<String, Object> props = new Hashtable<>();
+ String[] keys = ref.getPropertyKeys();
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ if (key.equals(DependencyManager.ASPECT) || key.equals(Constants.SERVICE_ID) || key.equals(Constants.SERVICE_RANKING) || key.equals(Constants.OBJECTCLASS)) {
+ // do not copy these either
+ }
+ else {
+ props.put(key, ref.getProperty(key));
+ }
+ }
+ return props;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AspectServiceImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AspectServiceImpl.java
new file mode 100644
index 0000000..8adbe12
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/AspectServiceImpl.java
@@ -0,0 +1,258 @@
+/*
+ * 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.dm.impl;
+
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.context.DependencyContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectServiceImpl extends FilterComponent {
+
+ private final String m_add;
+ private final String m_change;
+ private final String m_remove;
+ private final String m_swap;
+ private int m_ranking;
+
+ public AspectServiceImpl(DependencyManager dm, Class<?> aspectInterface, String aspectFilter, int ranking, String autoConfig, String add, String change, String remove, String swap) {
+ super(dm.createComponent());
+ this.m_ranking = ranking;
+ this.m_add = add;
+ this.m_change = change;
+ this.m_remove = remove;
+ this.m_swap = swap;
+
+ m_component.setImplementation(new AspectImpl(aspectInterface, autoConfig))
+ .add(dm.createServiceDependency()
+ .setService(aspectInterface, createDependencyFilterForAspect(aspectFilter))
+ .setAutoConfig(false)
+ .setCallbacks("added", "removed"))
+ .setCallbacks("init", null, "stop", null);
+
+// m_component.setDebug("aspectfactory-" + m_ranking);
+ }
+
+ private String createDependencyFilterForAspect(String aspectFilter) {
+ // we only want to match services which are not themselves aspects
+ if (aspectFilter == null || aspectFilter.length() == 0) {
+ return "(!(" + DependencyManager.ASPECT + "=*))";
+ }
+ else {
+ return "(&(!(" + DependencyManager.ASPECT + "=*))" + aspectFilter + ")";
+ }
+ }
+
+ private Hashtable<String, Object> getServiceProperties(ServiceReference ref) {
+ Hashtable<String, Object> props = new Hashtable<>();
+ String[] keys = ref.getPropertyKeys();
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ if (key.equals(Constants.SERVICE_ID) || key.equals(Constants.SERVICE_RANKING) || key.equals(DependencyManager.ASPECT) || key.equals(Constants.OBJECTCLASS)) {
+ // do not copy these
+ }
+ else {
+ props.put(key, ref.getProperty(key));
+ }
+ }
+ if (m_serviceProperties != null) {
+ Enumeration<String> e = m_serviceProperties.keys();
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ props.put(key, m_serviceProperties.get(key));
+ }
+ }
+ // finally add our aspect property
+ props.put(DependencyManager.ASPECT, ref.getProperty(Constants.SERVICE_ID));
+ // and the ranking
+ props.put(Constants.SERVICE_RANKING, Integer.valueOf(m_ranking));
+ return props;
+ }
+
+ class AspectImpl extends AbstractDecorator {
+
+ private final Class<?> m_aspectInterface;
+ private final String m_autoConfig;
+
+ public AspectImpl(Class<?> aspectInterface, String autoConfig) {
+ this.m_aspectInterface = aspectInterface;
+ this.m_autoConfig = autoConfig;
+ }
+
+ /**
+ * Creates an aspect implementation component for a new original service.
+ * @param param First entry contains the ref to the original service
+ */
+ @Override
+ public Component createService(Object[] params) {
+ // Get the new original service reference.
+ ServiceReference ref = (ServiceReference) params[0];
+ List<DependencyContext> dependencies = m_component.getDependencies();
+ // remove our internal dependency, replace it with one that points to the specific service that just was passed in.
+ dependencies.remove(0);
+ Hashtable<String, Object> serviceProperties = getServiceProperties(ref);
+ String[] serviceInterfaces = getServiceInterfaces();
+
+ ServiceDependency aspectDependency = (ServiceDependencyImpl)
+ m_manager.createServiceDependency().setService(m_aspectInterface, createAspectFilter(ref)).setRequired(true);
+ //aspectDependency.setDebug("aspect " + m_ranking);
+
+ aspectDependency.setCallbacks(new CallbackProxy(aspectDependency, ref),
+ m_add != null ? "addAspect" : null,
+ "changeAspect", // We have to propagate in case aspect does not have a change callback
+ m_remove != null ? "removeAspect" : null,
+ m_swap != null ? "swapAspect" : null);
+
+ if (m_autoConfig != null) {
+ aspectDependency.setAutoConfig(m_autoConfig);
+ } else if (m_add == null && m_change == null && m_remove == null && m_swap == null) {
+ // Since we have set callbacks, we must reactivate setAutoConfig because user has not specified any callbacks.
+ aspectDependency.setAutoConfig(true);
+ }
+
+ Component service = m_manager.createComponent()
+ .setInterface(serviceInterfaces, serviceProperties)
+ .setImplementation(m_serviceImpl)
+ .setFactory(m_factory, m_factoryCreateMethod) // if not set, no effect
+ .setComposition(m_compositionInstance, m_compositionMethod) // if not set, no effect
+ .setCallbacks(m_callbackObject, m_init, m_start, m_stop, m_destroy) // if not set, no effect
+ .add(aspectDependency);
+
+ //service.setDebug("aspectimpl-" + m_ranking);
+
+ configureAutoConfigState(service, m_component);
+
+ for (DependencyContext dc : dependencies) {
+ service.add((Dependency) dc.createCopy());
+ }
+
+ for (int i = 0; i < m_stateListeners.size(); i++) {
+ service.add((ComponentStateListener) m_stateListeners.get(i));
+ }
+ return service;
+ }
+
+ /**
+ * Modify some specific aspect service properties.
+ */
+ @Override
+ public void setServiceProperties(Dictionary<?,?> props) {
+ for (Map.Entry<Object, Component> e : super.getServices().entrySet()) {
+ ServiceReference originalServiceRef = (ServiceReference) e.getKey();
+ Component c = e.getValue();
+ // m_serviceProperties is already set to the new service properties; and the getServiceProperties will
+ // merge m_serviceProperties with the original service properties.
+ Dictionary<String, Object> newProps = getServiceProperties(originalServiceRef);
+ c.setServiceProperties(newProps);
+ }
+ }
+
+ private String[] getServiceInterfaces() {
+ List<String> serviceNames = new ArrayList<>();
+ // Of course, we provide the aspect interface.
+ serviceNames.add(m_aspectInterface.getName());
+ // But also append additional aspect implementation interfaces.
+ if (m_serviceInterfaces != null) {
+ for (int i = 0; i < m_serviceInterfaces.length; i ++) {
+ if (!m_serviceInterfaces[i].equals(m_aspectInterface.getName())) {
+ serviceNames.add(m_serviceInterfaces[i]);
+ }
+ }
+ }
+ return (String[]) serviceNames.toArray(new String[serviceNames.size()]);
+ }
+
+ private String createAspectFilter(ServiceReference ref) {
+ Long sid = (Long) ref.getProperty(Constants.SERVICE_ID);
+ return "(&(|(!(" + Constants.SERVICE_RANKING + "=*))(" + Constants.SERVICE_RANKING + "<=" + (m_ranking - 1) + "))(|(" + Constants.SERVICE_ID + "=" + sid + ")(" + DependencyManager.ASPECT + "=" + sid + ")))";
+ }
+
+ public String toString() {
+ return "Aspect for " + m_aspectInterface.getName();
+ }
+ }
+
+ class CallbackProxy {
+ private final ServiceDependencyImpl m_aspectDependency;
+ private final ServiceReference m_originalServiceRef;
+
+ CallbackProxy(ServiceDependency aspectDependency, ServiceReference originalServiceRef) {
+ m_aspectDependency = (ServiceDependencyImpl) aspectDependency;
+ m_originalServiceRef = originalServiceRef;
+ }
+
+ @SuppressWarnings("unused")
+ private void addAspect(Component c, ServiceReference ref, Object service) {
+ // Just forward "add" service dependency callback.
+
+ // Invoke is done on dependency.getInstances() which unfortunately returns this callback instance...
+ ServiceEventImpl event = new ServiceEventImpl(ref, service);
+ m_aspectDependency.invoke(m_add, event, m_aspectDependency.getComponentContext().getInstances());
+ }
+
+ @SuppressWarnings("unused")
+ private void changeAspect(Component c, ServiceReference ref, Object service) {
+ // Invoke "change" service dependency callback
+ if (m_change != null) {
+ ServiceEventImpl event = new ServiceEventImpl(ref, service);
+ m_aspectDependency.invoke(m_change, event, m_aspectDependency.getComponentContext().getInstances());
+ }
+ // Propagate change to immediate higher aspect, or to client using our aspect.
+ // We always propagate our own properties, and the ones from the original service, but we don't inherit
+ // from lower ranked aspect service properties.
+ Dictionary<String, Object> props = getServiceProperties(m_originalServiceRef);
+ c.setServiceProperties(props);
+ }
+
+ @SuppressWarnings("unused")
+ private void removeAspect(Component c, ServiceReference ref, Object service) {
+ // Just forward "remove" service dependency callback.
+ ServiceEventImpl event = new ServiceEventImpl(ref, service);
+ m_aspectDependency.invoke(m_remove, event, m_aspectDependency.getComponentContext().getInstances());
+ }
+
+ @SuppressWarnings("unused")
+ private void swapAspect(Component c, ServiceReference prevRef, Object prev, ServiceReference currRef,
+ Object curr) {
+ Object[] instances = m_aspectDependency.getComponentContext().getInstances();
+ // Just forward "swap" service dependency callback.
+ m_aspectDependency.invokeSwap(m_swap, prevRef, prev, currRef, curr, m_aspectDependency.getComponentContext().getInstances());
+ }
+
+ @Override
+ public String toString() {
+ return "CallbackProxy";
+ }
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleAdapterImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleAdapterImpl.java
new file mode 100644
index 0000000..c883ebf
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleAdapterImpl.java
@@ -0,0 +1,102 @@
+/*
+ * 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.dm.impl;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.context.DependencyContext;
+import org.osgi.framework.Bundle;
+
+/**
+ * Bundle Adapter Service implementation. This class extends the FilterService in order to catch
+ * some Service methods for configuring actual adapter service implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class BundleAdapterImpl extends FilterComponent
+{
+ /**
+ * Creates a new Bundle Adapter Service implementation.
+ */
+ public BundleAdapterImpl(DependencyManager dm, int bundleStateMask, String bundleFilter, boolean propagate)
+ {
+ super(dm.createComponent()); // This service will be filtered by our super class, allowing us to take control.
+ m_component.setImplementation(new BundleAdapterDecorator(bundleStateMask, propagate))
+ .add(dm.createBundleDependency()
+ .setFilter(bundleFilter)
+ .setStateMask(bundleStateMask)
+ .setCallbacks("added", "removed"))
+ .setCallbacks("init", null, "stop", null);
+ }
+
+ public class BundleAdapterDecorator extends AbstractDecorator {
+ private final boolean m_propagate;
+ private final int m_bundleStateMask;
+
+ public BundleAdapterDecorator(int bundleStateMask, boolean propagate) {
+ m_bundleStateMask = bundleStateMask;
+ m_propagate = propagate;
+ }
+
+ public Component createService(Object[] properties) {
+ Bundle bundle = (Bundle) properties[0];
+ Hashtable<String, Object> props = new Hashtable<>();
+ if (m_serviceProperties != null) {
+ Enumeration<String> e = m_serviceProperties.keys();
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ props.put(key, m_serviceProperties.get(key));
+ }
+ }
+ List<DependencyContext> dependencies = m_component.getDependencies();
+ // the first dependency is always the dependency on the bundle, which
+ // will be replaced with a more specific dependency below
+ dependencies.remove(0);
+ Component service = m_manager.createComponent()
+ .setInterface(m_serviceInterfaces, props)
+ .setImplementation(m_serviceImpl)
+ .setFactory(m_factory, m_factoryCreateMethod) // if not set, no effect
+ .setComposition(m_compositionInstance, m_compositionMethod) // if not set, no effect
+ .setCallbacks(m_callbackObject, m_init, m_start, m_stop, m_destroy) // if not set, no effect
+ .add(m_manager.createBundleDependency()
+ .setBundle(bundle)
+ .setStateMask(m_bundleStateMask)
+ .setPropagate(m_propagate)
+ .setCallbacks(null, "changed", null)
+ .setAutoConfig(true)
+ .setRequired(true));
+
+ for (DependencyContext dc : dependencies) {
+ service.add((Dependency) dc.createCopy());
+ }
+
+ for (ComponentStateListener stateListener : m_stateListeners) {
+ service.add(stateListener);
+ }
+ configureAutoConfigState(service, m_component);
+ return service;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleDependencyImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleDependencyImpl.java
new file mode 100644
index 0000000..6e5dbd1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleDependencyImpl.java
@@ -0,0 +1,298 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.util.Dictionary;
+
+import org.apache.felix.dm.BundleDependency;
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.context.AbstractDependency;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.context.EventType;
+import org.apache.felix.dm.tracker.BundleTracker;
+import org.apache.felix.dm.tracker.BundleTrackerCustomizer;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class BundleDependencyImpl extends AbstractDependency<BundleDependency> implements BundleDependency, BundleTrackerCustomizer, ComponentDependencyDeclaration {
+ private BundleTracker m_tracker;
+ private int m_stateMask = Bundle.INSTALLED | Bundle.RESOLVED | Bundle.ACTIVE;
+ private Bundle m_bundleInstance;
+ private Filter m_filter;
+ private long m_bundleId = -1;
+ private Object m_nullObject;
+ private boolean m_propagate;
+ private Object m_propagateCallbackInstance;
+ private String m_propagateCallbackMethod;
+
+ public BundleDependencyImpl() {
+ }
+
+ public BundleDependencyImpl(BundleDependencyImpl prototype) {
+ super(prototype);
+ m_stateMask = prototype.m_stateMask;
+ m_nullObject = prototype.m_nullObject;
+ m_bundleInstance = prototype.m_bundleInstance;
+ m_filter = prototype.m_filter;
+ m_bundleId = prototype.m_bundleId;
+ m_propagate = prototype.m_propagate;
+ m_propagateCallbackInstance = prototype.m_propagateCallbackInstance;
+ m_propagateCallbackMethod = prototype.m_propagateCallbackMethod;
+ }
+
+ @Override
+ public DependencyContext createCopy() {
+ return new BundleDependencyImpl(this);
+ }
+
+ @Override
+ public void start() {
+ m_tracker = new BundleTracker(m_component.getBundleContext(), m_stateMask, this);
+ m_tracker.open();
+ super.start();
+ }
+
+ @Override
+ public void stop() {
+ m_tracker.close();
+ m_tracker = null;
+ super.stop();
+ }
+
+ @Override
+ public String getName() {
+ StringBuilder sb = new StringBuilder();
+ getSimpleName(sb);
+ if (m_filter != null) {
+ sb.append(" ");
+ sb.append(m_filter.toString());
+ }
+ if (m_bundleId != -1) {
+ sb.append("{bundle.id=" + m_bundleId + "}");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String getSimpleName() {
+ // Return the state mask, but don't include the filter or bundle id.
+ StringBuilder sb = new StringBuilder();
+ if ((m_stateMask & Bundle.ACTIVE) != 0) {
+ sb.append("active");
+ }
+ if ((m_stateMask & Bundle.INSTALLED) != 0) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append("installed");
+ }
+ if ((m_stateMask & Bundle.RESOLVED) != 0) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append("resolved");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String getFilter() {
+ if (m_filter != null || m_bundleId != -1) {
+ StringBuilder sb = new StringBuilder();
+ if (m_filter != null) {
+ sb.append(m_filter.toString());
+ }
+ if (m_bundleId != -1) {
+ sb.append("{bundle.id=" + m_bundleId + "}");
+ }
+ return sb.toString();
+ }
+ return null;
+ }
+
+ @Override
+ public String getType() {
+ return "bundle";
+ }
+
+ public Object addingBundle(Bundle bundle, BundleEvent event) {
+ // if we don't like a bundle, we could reject it here by returning null
+ long bundleId = bundle.getBundleId();
+ if (m_bundleId >= 0 && m_bundleId != bundleId) {
+ return null;
+ }
+ Filter filter = m_filter;
+ if (filter != null) {
+ Dictionary<?,?> headers = bundle.getHeaders();
+ if (!m_filter.match(headers)) {
+ return null;
+ }
+ }
+ return bundle;
+ }
+
+ public void addedBundle(Bundle bundle, BundleEvent event, Object object) {
+ m_component.handleEvent(this, EventType.ADDED, new BundleEventImpl(bundle, event));
+ }
+
+ public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
+ m_component.handleEvent(this, EventType.CHANGED, new BundleEventImpl(bundle, event));
+ }
+
+ public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
+ m_component.handleEvent(this, EventType.REMOVED, new BundleEventImpl(bundle, event));
+ }
+
+ @Override
+ public void invokeCallback(EventType type, Event ... e) {
+ switch (type) {
+ case ADDED:
+ if (m_add != null) {
+ invoke(m_add, e[0]);
+ }
+ break;
+ case CHANGED:
+ if (m_change != null) {
+ invoke (m_change, e[0]);
+ }
+ break;
+ case REMOVED:
+ if (m_remove != null) {
+ invoke (m_remove, e[0]);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void invoke(String method, Event e) {
+ BundleEventImpl be = (BundleEventImpl) e;
+ m_component.invokeCallbackMethod(getInstances(), method,
+ new Class[][] {{Bundle.class}, {Object.class}, {}},
+ new Object[][] {{be.getBundle()}, {be.getBundle()}, {}});
+ }
+
+ public BundleDependency setBundle(Bundle bundle) {
+ m_bundleId = bundle.getBundleId();
+ return this;
+ }
+
+ public BundleDependency setFilter(String filter) throws IllegalArgumentException {
+ if (filter != null) {
+ try {
+ m_filter = FrameworkUtil.createFilter(filter);
+ }
+ catch (InvalidSyntaxException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ }
+ return this;
+ }
+
+ public BundleDependency setStateMask(int mask) {
+ m_stateMask = mask;
+ return this;
+ }
+
+ @Override
+ public Class<?> getAutoConfigType() {
+ return Bundle.class;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ Event event = getService();
+ if (event != null) {
+ Bundle bundle = (Bundle) event.getEvent();
+ if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
+ try {
+ return (Dictionary<String, Object>) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, new Class[][] {{ Bundle.class }}, new Object[][] {{ bundle }});
+ }
+ catch (InvocationTargetException e) {
+ m_component.getLogger().warn("Exception while invoking callback method", e.getCause());
+ }
+ catch (Throwable e) {
+ m_component.getLogger().warn("Exception while trying to invoke callback method", e);
+ }
+ throw new IllegalStateException("Could not invoke callback");
+ }
+ else {
+ return (Dictionary<String, Object>) bundle.getHeaders();
+ }
+ }
+ else {
+ throw new IllegalStateException("cannot find bundle");
+ }
+ }
+
+ @Override
+ public Object getDefaultService(boolean nullObject) {
+ Object service = null;
+ if (isAutoConfig()) {
+ // TODO does it make sense to add support for custom bundle impls?
+ // service = getDefaultImplementation();
+ if (service == null && nullObject) {
+ service = getNullObject();
+ }
+ }
+ return service;
+ }
+
+ private Bundle getNullObject() {
+ if (m_nullObject == null) {
+ try {
+ m_nullObject = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Bundle.class }, new DefaultNullObject());
+ }
+ catch (Throwable e) {
+ m_component.getLogger().err("Could not create null object for Bundle.", e);
+ }
+ }
+ return (Bundle) m_nullObject;
+ }
+
+ private void getSimpleName(StringBuilder sb) {
+ if ((m_stateMask & Bundle.ACTIVE) != 0) {
+ sb.append("active");
+ }
+ if ((m_stateMask & Bundle.INSTALLED) != 0) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append("installed");
+ }
+ if ((m_stateMask & Bundle.RESOLVED) != 0) {
+ if (sb.length() > 0) {
+ sb.append(" ");
+ }
+ sb.append("resolved");
+ }
+ }
+}
+
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleEventImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleEventImpl.java
new file mode 100644
index 0000000..187165f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/BundleEventImpl.java
@@ -0,0 +1,69 @@
+/*
+ * 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.dm.impl;
+
+import java.util.Dictionary;
+
+import org.apache.felix.dm.context.Event;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class BundleEventImpl extends Event {
+ final BundleEvent m_event;
+
+ public BundleEventImpl(Bundle bundle, BundleEvent event) {
+ super(bundle);
+ m_event = event;
+ }
+
+ public Bundle getBundle() {
+ return getEvent();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ return getBundle().getHeaders();
+ }
+
+ public BundleEvent getBundleEvent() {
+ return m_event;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof BundleEventImpl) {
+ return getBundle().getBundleId() == ((BundleEventImpl) obj).getBundle().getBundleId();
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getBundle().hashCode();
+ }
+
+ @Override
+ public int compareTo(Event b) {
+ return Long.compare(getBundle().getBundleId(), ((BundleEventImpl) b).getBundle().getBundleId());
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
new file mode 100644
index 0000000..f08d4c1
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentImpl.java
@@ -0,0 +1,1345 @@
+/*
+ * 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.dm.impl;
+
+import static org.apache.felix.dm.ComponentState.INACTIVE;
+import static org.apache.felix.dm.ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED;
+import static org.apache.felix.dm.ComponentState.TRACKING_OPTIONAL;
+import static org.apache.felix.dm.ComponentState.WAITING_FOR_REQUIRED;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDeclaration;
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.context.ComponentContext;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.EventType;
+import org.apache.felix.dm.context.Event;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+
+/**
+ * Dependency Manager Component implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ComponentImpl implements Component, ComponentContext, ComponentDeclaration {
+ private static final ServiceRegistration NULL_REGISTRATION = (ServiceRegistration) Proxy
+ .newProxyInstance(ComponentImpl.class.getClassLoader(),
+ new Class[] { ServiceRegistration.class },
+ new DefaultNullObject());
+ private static final Class<?>[] VOID = new Class[] {};
+ private volatile Executor m_executor = new SerialExecutor(new Logger(null));
+ private ComponentState m_state = ComponentState.INACTIVE;
+ private final CopyOnWriteArrayList<DependencyContext> m_dependencies = new CopyOnWriteArrayList<>();
+ private final List<ComponentStateListener> m_listeners = new CopyOnWriteArrayList<>();
+ private boolean m_isStarted;
+ private final Logger m_logger;
+ private final BundleContext m_context;
+ private final DependencyManager m_manager;
+ private Object m_componentDefinition;
+ private Object m_componentInstance;
+ private volatile Object m_serviceName;
+ private volatile Dictionary<Object, Object> m_serviceProperties;
+ private volatile ServiceRegistration m_registration;
+ private final Map<Class<?>, Boolean> m_autoConfig = new ConcurrentHashMap<>();
+ private final Map<Class<?>, String> m_autoConfigInstance = new ConcurrentHashMap<>();
+ private final Map<String, Long> m_stopwatch = new ConcurrentHashMap<>();
+ private final long m_id;
+ private static AtomicLong m_idGenerator = new AtomicLong();
+ // Holds all the services of a given dependency context. Caution: the last entry in the skiplist is the highest ranked service.
+ private final Map<DependencyContext, ConcurrentSkipListSet<Event>> m_dependencyEvents = new HashMap<>();
+ private final AtomicBoolean m_active = new AtomicBoolean(false);
+
+ public Component setDebug(String debugKey) {
+ // Force debug level in our logger
+ m_logger.setEnabledLevel(LogService.LOG_DEBUG);
+ m_logger.setDebugKey(debugKey);
+ return this;
+ }
+
+ // configuration (static)
+ private volatile String m_callbackInit;
+ private volatile String m_callbackStart;
+ private volatile String m_callbackStop;
+ private volatile String m_callbackDestroy;
+ private volatile Object m_callbackInstance;
+
+ // instance factory
+ private volatile Object m_instanceFactory;
+ private volatile String m_instanceFactoryCreateMethod;
+
+ // composition manager
+ private volatile Object m_compositionManager;
+ private volatile String m_compositionManagerGetMethod;
+ private volatile Object m_compositionManagerInstance;
+ private final Bundle m_bundle;
+
+ static class SCDImpl implements ComponentDependencyDeclaration {
+ private final String m_name;
+ private final int m_state;
+ private final String m_type;
+
+ public SCDImpl(String name, int state, String type) {
+ m_name = name;
+ m_state = state;
+ m_type = type;
+ }
+
+ public String getName() {
+ return m_name;
+ }
+
+ public String getSimpleName() {
+ return m_name;
+ }
+
+ public String getFilter() {
+ return null;
+ }
+
+ public int getState() {
+ return m_state;
+ }
+
+ public String getType() {
+ return m_type;
+ }
+ }
+
+ public ComponentImpl() {
+ this(null, null, new Logger(null));
+ }
+
+ public ComponentImpl(BundleContext context, DependencyManager manager, Logger logger) {
+ m_context = context;
+ m_bundle = context != null ? context.getBundle() : null;
+ m_manager = manager;
+ m_logger = logger;
+ m_autoConfig.put(BundleContext.class, Boolean.TRUE);
+ m_autoConfig.put(ServiceRegistration.class, Boolean.TRUE);
+ m_autoConfig.put(DependencyManager.class, Boolean.TRUE);
+ m_autoConfig.put(Component.class, Boolean.TRUE);
+ m_callbackInit = "init";
+ m_callbackStart = "start";
+ m_callbackStop = "stop";
+ m_callbackDestroy = "destroy";
+ m_id = m_idGenerator.getAndIncrement();
+ }
+
+ @Override
+ public Component add(final Dependency ... dependencies) {
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ List<DependencyContext> instanceBoundDeps = new ArrayList<>();
+ for (Dependency d : dependencies) {
+ DependencyContext dc = (DependencyContext) d;
+ if (dc.getComponentContext() != null) {
+ m_logger.err("%s can't be added to %s (dependency already added to another component).", dc,
+ ComponentImpl.this);
+ continue;
+ }
+ m_dependencyEvents.put(dc, new ConcurrentSkipListSet<Event>());
+ m_dependencies.add(dc);
+ dc.setComponentContext(ComponentImpl.this);
+ if (!(m_state == ComponentState.INACTIVE)) {
+ dc.setInstanceBound(true);
+ instanceBoundDeps.add(dc);
+ }
+ }
+ startDependencies(instanceBoundDeps);
+ handleChange();
+ }
+ });
+ return this;
+ }
+
+ @Override
+ public Component remove(final Dependency d) {
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ DependencyContext dc = (DependencyContext) d;
+ // First remove this dependency from the dependency list
+ m_dependencies.remove(d);
+ // Now we can stop the dependency (our component won't be deactivated, it will only be unbound with
+ // the removed dependency).
+ if (!(m_state == ComponentState.INACTIVE)) {
+ dc.stop();
+ }
+ // Finally, cleanup the dependency events.
+ m_dependencyEvents.remove(d);
+ handleChange();
+ }
+ });
+ return this;
+ }
+
+ public void start() {
+ if (m_active.compareAndSet(false, true)) {
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ m_isStarted = true;
+ handleChange();
+ }
+ });
+ }
+ }
+
+ public void stop() {
+ if (m_active.compareAndSet(true, false)) {
+ Executor executor = getExecutor();
+
+ // First, declare the task that will stop our component in our executor.
+ final Runnable stopTask = new Runnable() {
+ @Override
+ public void run() {
+ m_isStarted = false;
+ handleChange();
+ }
+ };
+
+ // Now, we have to schedule our stopTask in our component executor. But we have to handle a special case:
+ // if the component bundle is stopping *AND* if the executor is a parallel dispatcher, then we want
+ // to invoke our stopTask synchronously, in order to make sure that the bundle context is valid while our
+ // component is being deactivated (if we stop the component asynchronously, the bundle context may be invalidated
+ // before our component is stopped, and we don't want to be in this situation).
+
+ boolean stopping = m_bundle != null /* null only in tests env */ && m_bundle.getState() == Bundle.STOPPING;
+ if (stopping && executor instanceof DispatchExecutor) {
+ ((DispatchExecutor) executor).execute(stopTask, false /* try to execute synchronously, not using threadpool */);
+ } else {
+ executor.execute(stopTask);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Component setInterface(String serviceName, Dictionary<?, ?> properties) {
+ ensureNotActive();
+ m_serviceName = serviceName;
+ m_serviceProperties = (Dictionary<Object, Object>) properties;
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Component setInterface(String[] serviceName, Dictionary<?, ?> properties) {
+ ensureNotActive();
+ m_serviceName = serviceName;
+ m_serviceProperties = (Dictionary<Object, Object>) properties;
+ return this;
+ }
+
+ @Override
+ public void handleEvent(final DependencyContext dc, final EventType type, final Event... event) {
+ // since this method can be invoked by anyone from any thread, we need to
+ // pass on the event to a runnable that we execute using the component's
+ // executor
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ switch (type) {
+ case ADDED:
+ handleAdded(dc, event[0]);
+ break;
+ case CHANGED:
+ handleChanged(dc, event[0]);
+ break;
+ case REMOVED:
+ handleRemoved(dc, event[0]);
+ break;
+ case SWAPPED:
+ handleSwapped(dc, event[0], event[1]);
+ break;
+ }
+ }
+ });
+ }
+
+ @Override
+ public Event getDependencyEvent(DependencyContext dc) {
+ ConcurrentSkipListSet<Event> events = m_dependencyEvents.get(dc);
+ return events.size() > 0 ? events.last() : null;
+ }
+
+ @Override
+ public Set<Event> getDependencyEvents(DependencyContext dc) {
+ return m_dependencyEvents.get(dc);
+ }
+
+ public Component setAutoConfig(Class<?> clazz, boolean autoConfig) {
+ m_autoConfig.put(clazz, Boolean.valueOf(autoConfig));
+ return this;
+ }
+
+ public Component setAutoConfig(Class<?> clazz, String instanceName) {
+ m_autoConfig.put(clazz, Boolean.valueOf(instanceName != null));
+ m_autoConfigInstance.put(clazz, instanceName);
+ return this;
+ }
+
+ public boolean getAutoConfig(Class<?> clazz) {
+ Boolean result = (Boolean) m_autoConfig.get(clazz);
+ return (result != null && result.booleanValue());
+ }
+
+ public String getAutoConfigInstance(Class<?> clazz) {
+ return (String) m_autoConfigInstance.get(clazz);
+ }
+
+ private void handleAdded(DependencyContext dc, Event e) {
+ if (! m_isStarted) {
+ return;
+ }
+ m_logger.debug("handleAdded %s", e);
+
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ dependencyEvents.add(e);
+ dc.setAvailable(true);
+
+ // Recalculate state changes. We only do this if the dependency is started. If the dependency is not started,
+ // it means it is actually starting. And in this case, we don't recalculate state changes now. We'll do it
+ // once all currently available services are found, and then after, we'll recalculate state change
+ // (see the startDependencies method).
+ // All this is done for two reasons:
+ // 1- optimization: it is preferable to recalculate state changes once we know about all currently available dependency services
+ // (after the tracker has returned from its open method).
+ // 2- This also allows to determine the list of currently available dependency services from within the component start method callback
+ // (this will be extremely useful when porting the Felix SCR on top of DM4).
+
+ if (dc.isStarted()) {
+ switch (m_state) {
+ case WAITING_FOR_REQUIRED:
+ if (dc.isRequired())
+ handleChange();
+ break;
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ if (!dc.isInstanceBound()) {
+ if (dc.isRequired()) {
+ dc.invokeCallback(EventType.ADDED, e);
+ }
+ updateInstance(dc, e, false, true);
+ }
+ else {
+ if (dc.isRequired())
+ handleChange();
+ }
+ break;
+ case TRACKING_OPTIONAL:
+ dc.invokeCallback(EventType.ADDED, e);
+ updateInstance(dc, e, false, true);
+ break;
+ default:
+ }
+ }
+ }
+
+ private void handleChanged(final DependencyContext dc, final Event e) {
+ if (! m_isStarted) {
+ return;
+ }
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ dependencyEvents.remove(e);
+ dependencyEvents.add(e);
+
+ if (dc.isStarted()) {
+ switch (m_state) {
+ case TRACKING_OPTIONAL:
+ dc.invokeCallback(EventType.CHANGED, e);
+ updateInstance(dc, e, true, false);
+ break;
+
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ if (!dc.isInstanceBound()) {
+ dc.invokeCallback(EventType.CHANGED, e);
+ updateInstance(dc, e, true, false);
+ }
+ break;
+ default:
+ // noop
+ }
+ }
+ }
+
+ private void handleRemoved(DependencyContext dc, Event e) {
+ if (! m_isStarted) {
+ return;
+ }
+ // Check if the dependency is still available.
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ int size = dependencyEvents.size();
+ if (dependencyEvents.contains(e)) {
+ size--; // the dependency is currently registered and is about to be removed.
+ }
+ dc.setAvailable(size > 0);
+
+ // If the dependency is now unavailable, we have to recalculate state change. This will result in invoking the
+ // "removed" callback with the removed dependency (which we have not yet removed from our dependency events list.).
+ // But we don't recalculate the state if the dependency is not started (if not started, it means that it is currently starting,
+ // and the tracker is detecting a removed service).
+ if (size == 0 && dc.isStarted()) {
+ handleChange();
+ }
+
+ // Now, really remove the dependency event.
+ dependencyEvents.remove(e);
+
+ // Only check if the component instance has to be updated if the dependency is really started.
+ if (dc.isStarted()) {
+ // Depending on the state, we possible have to invoke the callbacks and update the component instance.
+ switch (m_state) {
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ if (!dc.isInstanceBound()) {
+ if (dc.isRequired()) {
+ dc.invokeCallback(EventType.REMOVED, e);
+ }
+ updateInstance(dc, e, false, false);
+ }
+ break;
+ case TRACKING_OPTIONAL:
+ dc.invokeCallback(EventType.REMOVED, e);
+ updateInstance(dc, e, false, false);
+ break;
+ default:
+ }
+ }
+ }
+
+ private void handleSwapped(DependencyContext dc, Event oldEvent, Event newEvent) {
+ if (! m_isStarted) {
+ return;
+ }
+ Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
+ dependencyEvents.remove(oldEvent);
+ dependencyEvents.add(newEvent);
+
+ if (dc.isStarted()) {
+ // Depending on the state, we possible have to invoke the callbacks and update the component instance.
+ switch (m_state) {
+ case WAITING_FOR_REQUIRED:
+ // No need to swap, we don't have yet injected anything
+ break;
+ case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
+ // Only swap *non* instance-bound dependencies
+ if (!dc.isInstanceBound()) {
+ if (dc.isRequired()) {
+ dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
+ }
+ }
+ break;
+ case TRACKING_OPTIONAL:
+ dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
+ break;
+ default:
+ }
+ }
+ }
+
+ private void handleChange() {
+ m_logger.debug("handleChanged");
+ try {
+ ComponentState oldState;
+ ComponentState newState;
+ do {
+ oldState = m_state;
+ newState = calculateNewState(oldState);
+ m_logger.debug("%s -> %s", oldState, newState);
+ m_state = newState;
+ } while (performTransition(oldState, newState));
+ } finally {
+ m_logger.debug("end handling change.");
+ }
+ }
+
+ /** Based on the current state, calculate the new state. */
+ private ComponentState calculateNewState(ComponentState currentState) {
+ if (currentState == INACTIVE) {
+ if (m_isStarted) {
+ return WAITING_FOR_REQUIRED;
+ }
+ }
+ if (currentState == WAITING_FOR_REQUIRED) {
+ if (!m_isStarted) {
+ return INACTIVE;
+ }
+ if (allRequiredAvailable()) {
+ return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
+ }
+ }
+ if (currentState == INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
+ if (m_isStarted && allRequiredAvailable()) {
+ if (allInstanceBoundAvailable()) {
+ return TRACKING_OPTIONAL;
+ }
+ return currentState;
+ }
+ return WAITING_FOR_REQUIRED;
+ }
+ if (currentState == TRACKING_OPTIONAL) {
+ if (m_isStarted && allRequiredAvailable() && allInstanceBoundAvailable()) {
+ return currentState;
+ }
+ return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
+ }
+ return currentState;
+ }
+
+ /** Perform all the actions associated with state transitions. Returns true if a transition was performed. */
+ private boolean performTransition(ComponentState oldState, ComponentState newState) {
+// System.out.println("transition from " + oldState + " to " + newState);
+ if (oldState == ComponentState.INACTIVE && newState == ComponentState.WAITING_FOR_REQUIRED) {
+ startDependencies(m_dependencies);
+ notifyListeners(newState);
+ return true;
+ }
+ if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
+ instantiateComponent();
+ invokeAutoConfigDependencies();
+ invokeAddRequiredDependencies();
+ ComponentState stateBeforeCallingInit = m_state;
+ invoke(m_callbackInit);
+ if (stateBeforeCallingInit == m_state) {
+ notifyListeners(newState); // init did not change current state, we can notify about this new state
+ }
+ return true;
+ }
+ if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.TRACKING_OPTIONAL) {
+ invokeAutoConfigInstanceBoundDependencies();
+ invokeAddRequiredInstanceBoundDependencies();
+ invoke(m_callbackStart);
+ invokeAddOptionalDependencies();
+ registerService();
+ notifyListeners(newState);
+ return true;
+ }
+ if (oldState == ComponentState.TRACKING_OPTIONAL && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
+ unregisterService();
+ invokeRemoveOptionalDependencies();
+ invoke(m_callbackStop);
+ invokeRemoveInstanceBoundDependencies();
+ notifyListeners(newState);
+ return true;
+ }
+ if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.WAITING_FOR_REQUIRED) {
+ invoke(m_callbackDestroy);
+ invokeRemoveRequiredDependencies();
+ notifyListeners(newState);
+ if (! someDependenciesNeedInstance()) {
+ destroyComponent();
+ }
+ return true;
+ }
+ if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INACTIVE) {
+ stopDependencies();
+ destroyComponent();
+ notifyListeners(newState);
+ return true;
+ }
+ return false;
+ }
+
+ private boolean allRequiredAvailable() {
+ boolean available = true;
+ for (DependencyContext d : m_dependencies) {
+ if (d.isRequired() && !d.isInstanceBound()) {
+ if (!d.isAvailable()) {
+ available = false;
+ break;
+ }
+ }
+ }
+ return available;
+ }
+
+ private boolean allInstanceBoundAvailable() {
+ boolean available = true;
+ for (DependencyContext d : m_dependencies) {
+ if (d.isRequired() && d.isInstanceBound()) {
+ if (!d.isAvailable()) {
+ available = false;
+ break;
+ }
+ }
+ }
+ return available;
+ }
+
+ private boolean someDependenciesNeedInstance() {
+ for (DependencyContext d : m_dependencies) {
+ if (d.needsInstance()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Updates the component instance(s).
+ * @param dc the dependency context for the updating dependency service
+ * @param event the event holding the updating service (service + properties)
+ * @param update true if dependency service properties are updating, false if not. If false, it means
+ * that a dependency service is being added or removed. (see the "add" flag).
+ * @param add true if the dependency service has been added, false if it has been removed. This flag is
+ * ignored if the "update" flag is true (because the dependency properties are just being updated).
+ */
+ private void updateInstance(DependencyContext dc, Event event, boolean update, boolean add) {
+ if (dc.isAutoConfig()) {
+ updateImplementation(dc.getAutoConfigType(), dc, dc.getAutoConfigName(), event, update, add);
+ }
+ if (dc.isPropagated() && m_registration != null) {
+ m_registration.setProperties(calculateServiceProperties());
+ }
+ }
+
+ private void startDependencies(List<DependencyContext> dependencies) {
+ // Start first optional dependencies first.
+ m_logger.debug("startDependencies.");
+ List<DependencyContext> requiredDeps = new ArrayList<>();
+ for (DependencyContext d : dependencies) {
+ if (d.isRequired()) {
+ requiredDeps.add(d);
+ continue;
+ }
+ if (d.needsInstance()) {
+ instantiateComponent();
+ }
+ d.start();
+ }
+ // now, start required dependencies.
+ for (DependencyContext d : requiredDeps) {
+ if (d.needsInstance()) {
+ instantiateComponent();
+ }
+ d.start();
+ }
+ // The started dependencies has probably called our handleAdded method: we now have to run our state machine.
+ handleChange();
+ }
+
+ private void stopDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ d.stop();
+ }
+ }
+
+ private void registerService() {
+ if (m_context != null && m_serviceName != null) {
+ ServiceRegistrationImpl wrapper = new ServiceRegistrationImpl();
+ m_registration = wrapper;
+ autoConfigureImplementation(ServiceRegistration.class, m_registration);
+
+ // service name can either be a string or an array of strings
+ ServiceRegistration registration;
+
+ // determine service properties
+ Dictionary<?,?> properties = calculateServiceProperties();
+
+ // register the service
+ try {
+ if (m_serviceName instanceof String) {
+ registration = m_context.registerService((String) m_serviceName, m_componentInstance, properties);
+ }
+ else {
+ registration = m_context.registerService((String[]) m_serviceName, m_componentInstance, properties);
+ }
+ wrapper.setServiceRegistration(registration);
+ }
+ catch (IllegalArgumentException iae) {
+ m_logger.log(Logger.LOG_ERROR, "Could not register service " + m_componentInstance, iae);
+ // set the registration to an illegal state object, which will make all invocations on this
+ // wrapper fail with an ISE (which also occurs when the SR becomes invalid)
+ wrapper.setIllegalState();
+ }
+ }
+ }
+
+ private void unregisterService() {
+ if (m_serviceName != null && m_registration != null) {
+ try {
+ if (m_bundle != null && m_bundle.getState() == Bundle.ACTIVE) {
+ m_registration.unregister();
+ }
+ } catch (IllegalStateException e) { /* Should we really log this ? */}
+ autoConfigureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
+ m_registration = null;
+ }
+ }
+
+ private Dictionary<Object, Object> calculateServiceProperties() {
+ Dictionary<Object, Object> properties = new Hashtable<>();
+ for (int i = 0; i < m_dependencies.size(); i++) {
+ DependencyContext d = (DependencyContext) m_dependencies.get(i);
+ if (d.isPropagated() && d.isAvailable()) {
+ Dictionary<Object, Object> dict = d.getProperties();
+ addTo(properties, dict);
+ }
+ }
+ // our service properties must not be overriden by propagated dependency properties, so we add our service
+ // properties after having added propagated dependency properties.
+ addTo(properties, m_serviceProperties);
+ if (properties.size() == 0) {
+ properties = null;
+ }
+ return properties;
+ }
+
+ private void addTo(Dictionary<Object, Object> properties, Dictionary<Object, Object> additional) {
+ if (properties == null) {
+ throw new IllegalArgumentException("Dictionary to add to cannot be null.");
+ }
+ if (additional != null) {
+ Enumeration<Object> e = additional.keys();
+ while (e.hasMoreElements()) {
+ Object key = e.nextElement();
+ properties.put(key, additional.get(key));
+ }
+ }
+ }
+
+ private void instantiateComponent() {
+ m_logger.debug("instantiating component.");
+
+ // TODO add more complex factory instantiations of one or more components in a composition here
+ if (m_componentInstance == null) {
+ if (m_componentDefinition instanceof Class) {
+ try {
+ m_componentInstance = createInstance((Class<?>) m_componentDefinition);
+ }
+ catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not instantiate class " + m_componentDefinition, e);
+ }
+ }
+ else {
+ if (m_instanceFactoryCreateMethod != null) {
+ Object factory = null;
+ if (m_instanceFactory != null) {
+ if (m_instanceFactory instanceof Class) {
+ try {
+ factory = createInstance((Class<?>) m_instanceFactory);
+ }
+ catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not create factory instance of class " + m_instanceFactory + ".", e);
+ }
+ }
+ else {
+ factory = m_instanceFactory;
+ }
+ }
+ else {
+ // TODO review if we want to try to default to something if not specified
+ // for now the JavaDoc of setFactory(method) reflects the fact that we need
+ // to review it
+ }
+ if (factory == null) {
+ m_logger.log(Logger.LOG_ERROR, "Factory cannot be null.");
+ }
+ else {
+ try {
+ m_componentInstance = InvocationUtil.invokeMethod(factory, factory.getClass(), m_instanceFactoryCreateMethod, new Class[][] {{}}, new Object[][] {{}}, false);
+ }
+ catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not create service instance using factory " + factory + " method " + m_instanceFactoryCreateMethod + ".", e);
+ }
+ }
+ }
+ }
+
+ if (m_componentInstance == null) {
+ m_componentInstance = m_componentDefinition;
+ }
+
+ // configure the bundle context
+ autoConfigureImplementation(BundleContext.class, m_context);
+ autoConfigureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
+ autoConfigureImplementation(DependencyManager.class, m_manager);
+ autoConfigureImplementation(Component.class, this);
+ }
+ }
+
+ private void destroyComponent() {
+ m_componentInstance = null;
+ }
+
+ private void invokeAddRequiredDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (d.isRequired() && !d.isInstanceBound()) {
+ for (Event e : m_dependencyEvents.get(d)) {
+ d.invokeCallback(EventType.ADDED, e);
+ }
+ }
+ }
+ }
+
+ private void invokeAutoConfigDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (d.isAutoConfig() && !d.isInstanceBound()) {
+ configureImplementation(d.getAutoConfigType(), d, d.getAutoConfigName());
+ }
+ }
+ }
+
+ private void invokeAutoConfigInstanceBoundDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (d.isAutoConfig() && d.isInstanceBound()) {
+ configureImplementation(d.getAutoConfigType(), d, d.getAutoConfigName());
+ }
+ }
+ }
+
+ private void invokeAddRequiredInstanceBoundDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (d.isRequired() && d.isInstanceBound()) {
+ for (Event e : m_dependencyEvents.get(d)) {
+ d.invokeCallback(EventType.ADDED, e);
+ }
+ }
+ }
+ }
+
+ private void invokeAddOptionalDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (! d.isRequired()) {
+ for (Event e : m_dependencyEvents.get(d)) {
+ d.invokeCallback(EventType.ADDED, e);
+ }
+ }
+ }
+ }
+
+ private void invokeRemoveRequiredDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (!d.isInstanceBound() && d.isRequired()) {
+ for (Event e : m_dependencyEvents.get(d)) {
+ d.invokeCallback(EventType.REMOVED, e);
+ }
+ }
+ }
+ }
+
+ private void invokeRemoveOptionalDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (! d.isRequired()) {
+ for (Event e : m_dependencyEvents.get(d)) {
+ d.invokeCallback(EventType.REMOVED, e);
+ }
+ }
+ }
+ }
+
+ private void invokeRemoveInstanceBoundDependencies() {
+ for (DependencyContext d : m_dependencies) {
+ if (d.isInstanceBound()) {
+ for (Event e : m_dependencyEvents.get(d)) {
+ d.invokeCallback(EventType.REMOVED, e);
+ }
+ }
+ }
+ }
+
+ private void invoke(String name) {
+ if (name != null) {
+ // if a callback instance was specified, look for the method there, if not,
+ // ask the service for its composition instances
+ Object[] instances = m_callbackInstance != null ? new Object[] { m_callbackInstance } : getCompositionInstances();
+
+ long t1 = System.nanoTime();
+ try {
+ invokeCallbackMethod(instances, name,
+ new Class[][] {{ Component.class }, {}},
+ new Object[][] {{ this }, {}},
+ false);
+ } finally {
+ long t2 = System.nanoTime();
+ m_stopwatch.put(name, t2 - t1);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T getInstance() {
+ Object[] instances = getCompositionInstances();
+ return instances.length == 0 ? null : (T) instances[0];
+ }
+
+ public Object[] getInstances() {
+ return getCompositionInstances();
+ }
+
+ public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures, Object[][] parameters) {
+ invokeCallbackMethod(instances, methodName, signatures, parameters, true);
+ }
+
+ public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures,
+ Object[][] parameters, boolean logIfNotFound) {
+ boolean callbackFound = false;
+ for (int i = 0; i < instances.length; i++) {
+ try {
+ InvocationUtil.invokeCallbackMethod(instances[i], methodName, signatures, parameters);
+ callbackFound |= true;
+ }
+ catch (NoSuchMethodException e) {
+ // if the method does not exist, ignore it
+ }
+ catch (InvocationTargetException e) {
+ // the method itself threw an exception, log that
+ m_logger.log(Logger.LOG_ERROR, "Invocation of '" + methodName + "' failed.", e.getCause());
+ }
+ catch (Throwable e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not invoke '" + methodName + "'.", e);
+ }
+ }
+
+ // If the callback is not found, we don't log if the method is on an AbstractDecorator.
+ // (Aspect or Adapter are not interested in user dependency callbacks)
+ if (logIfNotFound && ! callbackFound && ! (getInstance() instanceof AbstractDecorator)) {
+ if (m_logger == null) {
+ System.out.println("Callback \"" + methodName + "\" not found on componnent instances "
+ + Arrays.toString(getInstances()));
+ } else {
+ m_logger.log(LogService.LOG_ERROR, "Callback \"" + methodName + "\" callback not found on componnent instances "
+ + Arrays.toString(getInstances()));
+ }
+
+ }
+ }
+
+ private Object createInstance(Class<?> clazz) throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ Constructor<?> constructor = clazz.getConstructor(VOID);
+ constructor.setAccessible(true);
+ return constructor.newInstance();
+ }
+
+ private void notifyListeners(ComponentState state) {
+ for (ComponentStateListener l : m_listeners) {
+ l.changed(this, state);
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return m_state == TRACKING_OPTIONAL;
+ }
+
+ @Override
+ public boolean isActive() {
+ return m_active.get();
+ }
+
+ @Override
+ public Component add(final ComponentStateListener l) {
+ m_listeners.add(l);
+ return this;
+ }
+
+ @Override
+ public Component remove(ComponentStateListener l) {
+ m_listeners.remove(l);
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<DependencyContext> getDependencies() {
+ return (List<DependencyContext>) m_dependencies.clone();
+ }
+
+ @Override
+ public Component setImplementation(Object implementation) {
+ m_componentDefinition = implementation;
+ return this;
+ }
+
+ private void autoConfigureImplementation(Class<?> clazz, Object instance) {
+ if (((Boolean) m_autoConfig.get(clazz)).booleanValue()) {
+ configureImplementation(clazz, instance, (String) m_autoConfigInstance.get(clazz));
+ }
+ }
+
+ /**
+ * Configure a field in the service implementation. The service implementation
+ * is searched for fields that have the same type as the class that was specified
+ * and for each of these fields, the specified instance is filled in.
+ *
+ * @param clazz the class to search for
+ * @param instance the object to fill in the implementation class(es) field
+ * @param instanceName the name of the instance to fill in, or <code>null</code> if not used
+ */
+ private void configureImplementation(Class<?> clazz, Object instance, String fieldName) {
+ Object[] targets = getInstances();
+ if (! FieldUtil.injectField(targets, fieldName, clazz, instance, m_logger) && fieldName != null) {
+ // If the target is an abstract decorator (i.e: an adapter, or an aspect), we must not log warnings
+ // if field has not been injected.
+ if (! (getInstance() instanceof AbstractDecorator)) {
+ m_logger.log(Logger.LOG_ERROR, "Could not inject " + instance + " to field \"" + fieldName
+ + "\" at any of the following component instances: " + Arrays.toString(targets));
+ }
+ }
+ }
+
+ private void configureImplementation(Class<?> clazz, DependencyContext dc, String fieldName) {
+ Object[] targets = getInstances();
+ if (! FieldUtil.injectDependencyField(targets, fieldName, clazz, dc, m_logger) && fieldName != null) {
+ // If the target is an abstract decorator (i.e: an adapter, or an aspect), we must not log warnings
+ // if field has not been injected.
+ if (! (getInstance() instanceof AbstractDecorator)) {
+ m_logger.log(Logger.LOG_ERROR, "Could not inject dependency " + clazz.getName() + " to field \""
+ + fieldName + "\" at any of the following component instances: " + Arrays.toString(targets));
+ }
+ }
+ }
+
+ /**
+ * Update the component instances.
+ *
+ * @param clazz the class of the dependency service to inject in the component instances
+ * @param dc the dependency context for the updating dependency service
+ * @param fieldName the component instances fieldname to fill in with the updated dependency service
+ * @param event the event holding the updating service (service + properties)
+ * @param update true if dependency service properties are updating, false if not. If false, it means
+ * that a dependency service is being added or removed. (see the "add" flag).
+ * @param add true if the dependency service has been added, false if it has been removed. This flag is
+ * ignored if the "update" flag is true (because the dependency properties are just being updated).
+ */
+ private void updateImplementation(Class<?> clazz, DependencyContext dc, String fieldName, Event event, boolean update,
+ boolean add)
+ {
+ Object[] targets = getInstances();
+ FieldUtil.updateDependencyField(targets, fieldName, update, add, clazz, event, dc, m_logger);
+ }
+
+ @Override
+ public ServiceRegistration getServiceRegistration() {
+ return m_registration;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <K,V> Dictionary<K, V> getServiceProperties() {
+ if (m_serviceProperties != null) {
+ // Applied patch from FELIX-4304
+ Hashtable<Object, Object> serviceProperties = new Hashtable<>();
+ addTo(serviceProperties, m_serviceProperties);
+ return (Dictionary<K, V>) serviceProperties;
+ }
+ return null;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public Component setServiceProperties(final Dictionary<?, ?> serviceProperties) {
+ getExecutor().execute(new Runnable() {
+ @Override
+ public void run() {
+ Dictionary<Object, Object> properties = null;
+ m_serviceProperties = (Dictionary<Object, Object>) serviceProperties;
+ if ((m_registration != null) && (m_serviceName != null)) {
+ properties = calculateServiceProperties();
+ m_registration.setProperties(properties);
+ }
+ }
+ });
+ return this;
+ }
+
+ public Component setCallbacks(String init, String start, String stop, String destroy) {
+ ensureNotActive();
+ m_callbackInit = init;
+ m_callbackStart = start;
+ m_callbackStop = stop;
+ m_callbackDestroy = destroy;
+ return this;
+ }
+
+ public Component setCallbacks(Object instance, String init, String start, String stop, String destroy) {
+ ensureNotActive();
+ m_callbackInstance = instance;
+ m_callbackInit = init;
+ m_callbackStart = start;
+ m_callbackStop = stop;
+ m_callbackDestroy = destroy;
+ return this;
+ }
+
+ @Override
+ public Component setFactory(Object factory, String createMethod) {
+ ensureNotActive();
+ m_instanceFactory = factory;
+ m_instanceFactoryCreateMethod = createMethod;
+ return this;
+ }
+
+ @Override
+ public Component setFactory(String createMethod) {
+ return setFactory(null, createMethod);
+ }
+
+ @Override
+ public Component setComposition(Object instance, String getMethod) {
+ ensureNotActive();
+ m_compositionManager = instance;
+ m_compositionManagerGetMethod = getMethod;
+ return this;
+ }
+
+ @Override
+ public Component setComposition(String getMethod) {
+ return setComposition(null, getMethod);
+ }
+
+ private Object[] getCompositionInstances() {
+ Object[] instances = null;
+ if (m_compositionManagerGetMethod != null) {
+ if (m_compositionManager != null) {
+ m_compositionManagerInstance = m_compositionManager;
+ }
+ else {
+ m_compositionManagerInstance = m_componentInstance;
+ }
+ if (m_compositionManagerInstance != null) {
+ try {
+ instances = (Object[]) InvocationUtil.invokeMethod(m_compositionManagerInstance, m_compositionManagerInstance.getClass(), m_compositionManagerGetMethod, new Class[][] {{}}, new Object[][] {{}}, false);
+ }
+ catch (Exception e) {
+ m_logger.log(Logger.LOG_ERROR, "Could not obtain instances from the composition manager.", e);
+ instances = m_componentInstance == null ? new Object[] {} : new Object[] { m_componentInstance };
+ }
+ }
+ }
+ else {
+ instances = m_componentInstance == null ? new Object[] {} : new Object[] { m_componentInstance };
+ }
+ return instances;
+ }
+
+ @Override
+ public DependencyManager getDependencyManager() {
+ return m_manager;
+ }
+
+ public ComponentDependencyDeclaration[] getComponentDependencies() {
+ List<DependencyContext> deps = getDependencies();
+ if (deps != null) {
+ ComponentDependencyDeclaration[] result = new ComponentDependencyDeclaration[deps.size()];
+ for (int i = 0; i < result.length; i++) {
+ DependencyContext dep = (DependencyContext) deps.get(i);
+ if (dep instanceof ComponentDependencyDeclaration) {
+ result[i] = (ComponentDependencyDeclaration) dep;
+ }
+ else {
+ result[i] = new SCDImpl(dep.toString(), (dep.isAvailable() ? 1 : 0) + (dep.isRequired() ? 2 : 0), dep.getClass().getName());
+ }
+ }
+ return result;
+ }
+ return null;
+ }
+
+ public String getName() {
+ StringBuffer sb = new StringBuffer();
+ Object serviceName = m_serviceName;
+ if (serviceName instanceof String[]) {
+ String[] names = (String[]) serviceName;
+ for (int i = 0; i < names.length; i++) {
+ if (i > 0) {
+ sb.append(", ");
+ }
+ sb.append(names[i]);
+ }
+ appendProperties(sb);
+ } else if (serviceName instanceof String) {
+ sb.append(serviceName.toString());
+ appendProperties(sb);
+ } else {
+ Object implementation = m_componentDefinition;
+ if (implementation != null) {
+ if (implementation instanceof Class) {
+ sb.append(((Class<?>) implementation).getName());
+ } else {
+ // If the implementation instance does not override "toString", just display
+ // the class name, else display the component using its toString method
+ try {
+ Method m = implementation.getClass().getMethod("toString", new Class[0]);
+ if (m.getDeclaringClass().equals(Object.class)) {
+ sb.append(implementation.getClass().getName());
+ } else {
+ sb.append(implementation.toString());
+ }
+ } catch (java.lang.NoSuchMethodException e) {
+ // Just display the class name
+ sb.append(implementation.getClass().getName());
+ }
+ }
+ } else {
+ sb.append(super.toString());
+ }
+ }
+ return sb.toString();
+ }
+
+ private void appendProperties(StringBuffer result) {
+ Dictionary<Object, Object> properties = calculateServiceProperties();
+ if (properties != null) {
+ result.append("(");
+ Enumeration<?> enumeration = properties.keys();
+ while (enumeration.hasMoreElements()) {
+ Object key = enumeration.nextElement();
+ result.append(key.toString());
+ result.append('=');
+ Object value = properties.get(key);
+ if (value instanceof String[]) {
+ String[] values = (String[]) value;
+ result.append('{');
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ result.append(',');
+ }
+ result.append(values[i].toString());
+ }
+ result.append('}');
+ }
+ else {
+ result.append(value.toString());
+ }
+ if (enumeration.hasMoreElements()) {
+ result.append(',');
+ }
+ }
+ result.append(")");
+ }
+ }
+
+ @Override
+ public BundleContext getBundleContext() {
+ return m_context;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return m_bundle;
+ }
+
+ public long getId() {
+ return m_id;
+ }
+
+ public String getClassName() {
+ Object serviceInstance = m_componentInstance;
+ if (serviceInstance != null) {
+ return serviceInstance.getClass().getName();
+ }
+
+ Object implementation = m_componentDefinition;
+ if (implementation != null) {
+ if (implementation instanceof Class) {
+ return ((Class<?>) implementation).getName();
+ }
+ return implementation.getClass().getName();
+ }
+
+ Object instanceFactory = m_instanceFactory;
+ if (instanceFactory != null) {
+ return instanceFactory.getClass().getName();
+ } else {
+ // unexpected.
+ return ComponentImpl.class.getName();
+ }
+ }
+
+ public String[] getServices() {
+ if (m_serviceName instanceof String[]) {
+ return (String[]) m_serviceName;
+ } else if (m_serviceName instanceof String) {
+ return new String[] { (String) m_serviceName };
+ } else {
+ return null;
+ }
+ }
+
+ public int getState() {
+ return (isAvailable() ? ComponentDeclaration.STATE_REGISTERED : ComponentDeclaration.STATE_UNREGISTERED);
+ }
+
+ public void ensureNotActive() {
+ if (m_active.get()) {
+ throw new IllegalStateException("Can't modify an already started component.");
+ }
+ }
+
+ public ComponentDeclaration getComponentDeclaration() {
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ if (m_logger.getDebugKey() != null) {
+ return m_logger.getDebugKey();
+ }
+ return getClassName();
+ }
+
+ @Override
+ public void setThreadPool(Executor threadPool) {
+ ensureNotActive();
+ m_executor = new DispatchExecutor(threadPool, m_logger);
+ }
+
+ @Override
+ public Logger getLogger() {
+ return m_logger;
+ }
+
+ @Override
+ public Map<String, Long> getCallbacksTime() {
+ return m_stopwatch;
+ }
+
+ private Executor getExecutor() {
+ return m_executor;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java
new file mode 100644
index 0000000..eb2c0ea
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ComponentScheduler.java
@@ -0,0 +1,149 @@
+/*
+ * 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.dm.impl;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDeclaration;
+import org.apache.felix.dm.ComponentExecutorFactory;
+import org.apache.felix.dm.context.ComponentContext;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.component.ComponentFactory;
+
+/**
+ * The Dependency Manager delegates all components addition/removal to this class.
+ * If a ComponentExecutorFactory is registered in the OSGi registry, this class will use it to get an
+ * Executor used for components management and lifecycle callbacks.
+ *
+ * @see {@link ComponentFactory}
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ComponentScheduler {
+ private final static ComponentScheduler m_instance = new ComponentScheduler();
+ private final static String PARALLEL = "org.apache.felix.dependencymanager.parallel";
+ private volatile ComponentExecutorFactory m_componentExecutorFactory;
+ private final Executor m_serial = new SerialExecutor(null);
+ private ConcurrentMap<Component, Component> m_pending = new ConcurrentHashMap<>();
+
+ public static ComponentScheduler instance() {
+ return m_instance;
+ }
+
+ protected void bind(final ComponentExecutorFactory componentExecutorFactory) {
+ m_componentExecutorFactory = componentExecutorFactory;
+ m_serial.execute(new Runnable() {
+ @Override
+ public void run() {
+ for (Component c : m_pending.keySet()) {
+ createComponentExecutor(m_componentExecutorFactory, c);
+ ((ComponentContext) c).start();
+ }
+ m_pending.clear();
+ }
+ });
+ }
+
+ protected void unbind(ComponentExecutorFactory threadPool) {
+ m_componentExecutorFactory = null;
+ }
+
+ public void add(final Component c) {
+ if (mayStartNow(c)) {
+ ((ComponentContext) c).start();
+ }
+ else {
+ // The component requires a threadpool: delay execution until one is available.
+ m_serial.execute(new Runnable() {
+ @Override
+ public void run() {
+ ComponentExecutorFactory execFactory = m_componentExecutorFactory;
+ if (execFactory == null) {
+ m_pending.put(c, c);
+ }
+ else {
+ createComponentExecutor(execFactory, c);
+ ((ComponentContext) c).start();
+ }
+ }
+ });
+ }
+ }
+
+ public void remove(final Component c) {
+ m_pending.remove(c);
+ ((ComponentContext) c).stop();
+ }
+
+ private boolean mayStartNow(Component c) {
+ ComponentExecutorFactory execFactory = m_componentExecutorFactory;
+ BundleContext ctx = c.getDependencyManager().getBundleContext();
+ String parallel = ctx.getProperty(PARALLEL);
+
+ if (execFactory == null) {
+ // No ComponentExecutorFactory available. If a "parallel" OSGi system property is specified,
+ // we have to wait for a ComponentExecutorFactory servoce if the component class name is matching one of the
+ // prefixes specified in the "parallel" system property.
+ if (parallel != null && requiresThreadPool(c, parallel)) {
+ return false; // wait for a threadpool
+ } else {
+ return true; // no threadpool required, start the component now, synchronously
+ }
+ }
+ else {
+ // A threadpool is there. If the "parallel" OSGi system property is not specified, we can start the component
+ // now and we'll use the threadpool for it.
+ // But if the "parallel" system property is specified, the component will use the threadpool only if it's
+ // classname is starting with one of the prefixes specified in the property.
+ if (parallel == null || requiresThreadPool(c, parallel)) {
+ ((ComponentContext) c).setThreadPool(execFactory.getExecutorFor(c));
+ }
+ return true; // start the component now, possibly using the threadpool (see above).
+ }
+ }
+
+ private boolean requiresThreadPool(Component c, String parallel) {
+ // The component declared from our DM Activator can not be parallel.
+ ComponentDeclaration decl = c.getComponentDeclaration();
+ if (ComponentScheduler.class.getName().equals(decl.getName())) {
+ return false;
+ }
+
+ for (String prefix : parallel.trim().split(",")) {
+ prefix = prefix.trim();
+ boolean not = prefix.startsWith("!");
+ if (not) {
+ prefix = prefix.substring(1).trim();
+ }
+ if ("*".equals(prefix) || c.getComponentDeclaration().getClassName().startsWith(prefix)) {
+ return !not;
+ }
+ }
+ return false;
+ }
+
+ private void createComponentExecutor(ComponentExecutorFactory execFactory, Component c) {
+ Executor exec = execFactory.getExecutorFor(c);
+ if (exec != null) {
+ ((ComponentContext) c).setThreadPool(exec);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
new file mode 100644
index 0000000..db93df2
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationDependencyImpl.java
@@ -0,0 +1,311 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Dictionary;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.felix.dm.ConfigurationDependency;
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.PropertyMetaData;
+import org.apache.felix.dm.context.AbstractDependency;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.context.EventType;
+import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+/**
+ * Implementation for a configuration dependency.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ConfigurationDependencyImpl extends AbstractDependency<ConfigurationDependency> implements ConfigurationDependency, ManagedService {
+ private Dictionary<String, Object> m_settings;
+ private String m_pid;
+ private ServiceRegistration m_registration;
+ private MetaTypeProviderImpl m_metaType;
+ private final AtomicBoolean m_updateInvokedCache = new AtomicBoolean();
+ private final Logger m_logger;
+ private final BundleContext m_context;
+
+ public ConfigurationDependencyImpl() {
+ this(null, null);
+ }
+
+ public ConfigurationDependencyImpl(BundleContext context, Logger logger) {
+ m_context = context;
+ m_logger = logger;
+ setRequired(true);
+ setCallback("updated");
+ }
+
+ public ConfigurationDependencyImpl(ConfigurationDependencyImpl prototype) {
+ super(prototype);
+ m_context = prototype.m_context;
+ m_pid = prototype.m_pid;
+ m_logger = prototype.m_logger;
+ m_metaType = prototype.m_metaType != null ? new MetaTypeProviderImpl(prototype.m_metaType, this, null) : null;
+ }
+
+ @Override
+ public Class<?> getAutoConfigType() {
+ return null; // we don't support auto config mode.
+ }
+
+ @Override
+ public DependencyContext createCopy() {
+ return new ConfigurationDependencyImpl(this);
+ }
+
+ public ConfigurationDependencyImpl setCallback(String callback) {
+ super.setCallbacks(callback, null);
+ return this;
+ }
+
+ public ConfigurationDependencyImpl setCallback(Object instance, String callback) {
+ super.setCallbacks(instance, callback, null);
+ return this;
+ }
+
+ @Override
+ public boolean needsInstance() {
+ return m_callbackInstance == null;
+ }
+
+ @Override
+ public void start() {
+ BundleContext context = m_component.getBundleContext();
+ if (context != null) { // If null, we are in a test environment
+ Properties props = new Properties();
+ props.put(Constants.SERVICE_PID, m_pid);
+ ManagedService ms = this;
+ if (m_metaType != null) {
+ ms = m_metaType;
+ }
+ m_registration = context.registerService(ManagedService.class.getName(), ms, props);
+ }
+ super.start();
+ }
+
+ @Override
+ public void stop() {
+ if (m_registration != null) {
+ try {
+ m_registration.unregister();
+ } catch (IllegalStateException e) {}
+ m_registration = null;
+ }
+ super.stop();
+ }
+
+ public ConfigurationDependency setPid(String pid) {
+ ensureNotActive();
+ m_pid = pid;
+ return this;
+ }
+
+ @Override
+ public String getSimpleName() {
+ return m_pid;
+ }
+
+ @Override
+ public String getFilter() {
+ return null;
+ }
+
+ public String getType() {
+ return "configuration";
+ }
+
+ public ConfigurationDependency add(PropertyMetaData properties)
+ {
+ createMetaTypeImpl();
+ m_metaType.add(properties);
+ return this;
+ }
+
+ public ConfigurationDependency setDescription(String description)
+ {
+ createMetaTypeImpl();
+ m_metaType.setDescription(description);
+ return this;
+ }
+
+ public ConfigurationDependency setHeading(String heading)
+ {
+ createMetaTypeImpl();
+ m_metaType.setName(heading);
+ return this;
+ }
+
+ public ConfigurationDependency setLocalization(String path)
+ {
+ createMetaTypeImpl();
+ m_metaType.setLocalization(path);
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ if (m_settings == null) {
+ throw new IllegalStateException("cannot find configuration");
+ }
+ return m_settings;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Override
+ public void updated(Dictionary settings) throws ConfigurationException {
+ m_updateInvokedCache.set(false);
+ Dictionary<String, Object> oldSettings = null;
+ synchronized (this) {
+ oldSettings = m_settings;
+ }
+
+ if (oldSettings == null && settings == null) {
+ // CM has started but our configuration is not still present in the CM database: ignore
+ return;
+ }
+
+ // If this is initial settings, or a configuration update, we handle it synchronously.
+ // We'll conclude that the dependency is available only if invoking updated did not cause
+ // any ConfigurationException.
+ if (settings != null) {
+ Object[] instances = m_component.getInstances();
+ if (instances != null) {
+ try {
+ invokeUpdated(settings);
+ } catch (ConfigurationException e) {
+ logConfigurationException(e);
+ throw e;
+ }
+ }
+ }
+
+ // At this point, we have accepted the configuration.
+ synchronized (this) {
+ m_settings = settings;
+ }
+
+ if ((oldSettings == null) && (settings != null)) {
+ // Notify the component that our dependency is available.
+ m_component.handleEvent(this, EventType.ADDED, new ConfigurationEventImpl(m_pid, settings));
+ }
+ else if ((oldSettings != null) && (settings != null)) {
+ // Notify the component that our dependency has changed.
+ m_component.handleEvent(this, EventType.CHANGED, new ConfigurationEventImpl(m_pid, settings));
+ }
+ else if ((oldSettings != null) && (settings == null)) {
+ // Notify the component that our dependency has been removed.
+ // Notice that the component will be stopped, and then all required dependencies will be unbound
+ // (including our configuration dependency).
+ m_component.handleEvent(this, EventType.REMOVED, new ConfigurationEventImpl(m_pid, oldSettings));
+ }
+ }
+
+ @Override
+ public void invokeCallback(EventType type, Event ... event) {
+ switch (type) {
+ case ADDED:
+ try {
+ invokeUpdated(m_settings);
+ } catch (ConfigurationException e) {
+ logConfigurationException(e);
+ }
+ break;
+ case CHANGED:
+ // We already did that synchronously, from our updated method
+ break;
+ case REMOVED:
+ // The state machine is stopping us. We have to invoke updated(null).
+ try {
+ m_updateInvokedCache.set(false);
+ invokeUpdated(null);
+ } catch (ConfigurationException e) {
+ logConfigurationException(e);
+ } finally {
+ // Reset for the next time the state machine calls invokeAdd
+ m_updateInvokedCache.set(false);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void invokeUpdated(Dictionary<?,?> settings) throws ConfigurationException {
+ if (m_updateInvokedCache.compareAndSet(false, true)) {
+ Object[] instances = super.getInstances(); // either the callback instance or the component instances
+ if (instances != null) {
+ for (int i = 0; i < instances.length; i++) {
+ try {
+ InvocationUtil.invokeCallbackMethod(instances[i],
+ m_add, new Class[][] {
+ { Dictionary.class }, {} },
+ new Object[][] { { settings }, {} });
+ }
+
+ catch (InvocationTargetException e) {
+ // The component has thrown an exception during it's
+ // callback invocation.
+ if (e.getTargetException() instanceof ConfigurationException) {
+ // the callback threw an OSGi
+ // ConfigurationException: just re-throw it.
+ throw (ConfigurationException) e
+ .getTargetException();
+ } else {
+ // wrap the callback exception into a
+ // ConfigurationException.
+ throw new ConfigurationException(null,
+ "Configuration update failed",
+ e.getTargetException());
+ }
+ } catch (NoSuchMethodException e) {
+ // if the method does not exist, ignore it
+ } catch (Throwable t) {
+ // wrap any other exception as a ConfigurationException.
+ throw new ConfigurationException(null,
+ "Configuration update failed", t);
+ }
+ }
+ }
+ }
+ }
+
+ private synchronized void createMetaTypeImpl() {
+ if (m_metaType == null) {
+ m_metaType = new MetaTypeProviderImpl(m_pid, m_context, m_logger, this, null);
+ }
+ }
+
+ private void logConfigurationException(ConfigurationException e) {
+ if (m_logger != null) {
+ m_logger.log(Logger.LOG_ERROR, "Got exception while handling configuration update for pid " + m_pid, e);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationEventImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationEventImpl.java
new file mode 100644
index 0000000..c5f64d5
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ConfigurationEventImpl.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.dm.impl;
+
+import java.util.Dictionary;
+
+import org.apache.felix.dm.context.Event;
+
+/**
+ * Implementation for a configuration event.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ConfigurationEventImpl extends Event {
+ private final String m_pid;
+
+ public ConfigurationEventImpl(String pid, Dictionary<String, Object> conf) {
+ super(conf);
+ m_pid = pid;
+ }
+
+ public String getPid() {
+ return m_pid;
+ }
+
+ @Override
+ public int compareTo(Event other) {
+ return m_pid.compareTo(((ConfigurationEventImpl) other).m_pid);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ return getEvent();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/DefaultNullObject.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/DefaultNullObject.java
new file mode 100644
index 0000000..eede17d
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/DefaultNullObject.java
@@ -0,0 +1,71 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+
+
+/**
+ * Default null object implementation. Uses a dynamic proxy. Null objects are used
+ * as placeholders for services that are not available.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class DefaultNullObject implements InvocationHandler {
+ private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
+ private static final Byte DEFAULT_BYTE = new Byte((byte) 0);
+ private static final Short DEFAULT_SHORT = new Short((short) 0);
+ private static final Integer DEFAULT_INT = new Integer(0);
+ private static final Long DEFAULT_LONG = new Long(0);
+ private static final Float DEFAULT_FLOAT = new Float(0.0f);
+ private static final Double DEFAULT_DOUBLE = new Double(0.0);
+
+ /**
+ * Invokes a method on this null object. The method will return a default
+ * value without doing anything.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Class<?> returnType = method.getReturnType();
+ if (returnType.equals(Boolean.class) || returnType.equals(Boolean.TYPE)) {
+ return DEFAULT_BOOLEAN;
+ }
+ else if (returnType.equals(Byte.class) || returnType.equals(Byte.TYPE)) {
+ return DEFAULT_BYTE;
+ }
+ else if (returnType.equals(Short.class) || returnType.equals(Short.TYPE)) {
+ return DEFAULT_SHORT;
+ }
+ else if (returnType.equals(Integer.class) || returnType.equals(Integer.TYPE)) {
+ return DEFAULT_INT;
+ }
+ else if (returnType.equals(Long.class) || returnType.equals(Long.TYPE)) {
+ return DEFAULT_LONG;
+ }
+ else if (returnType.equals(Float.class) || returnType.equals(Float.TYPE)) {
+ return DEFAULT_FLOAT;
+ }
+ else if (returnType.equals(Double.class) || returnType.equals(Double.TYPE)) {
+ return DEFAULT_DOUBLE;
+ }
+ else {
+ return null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/DispatchExecutor.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/DispatchExecutor.java
new file mode 100644
index 0000000..a780231
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/DispatchExecutor.java
@@ -0,0 +1,187 @@
+/*
+ * 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.dm.impl;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.felix.dm.Logger;
+import org.osgi.service.log.LogService;
+
+/**
+ * A DispatchExecutor is a queue that can execute FIFO tasks in a shared threadpool configured for the dispatcher.
+ * Each task scheduled in a given DispatchExecutor will be executed serially in FIFO order; and multiple
+ * DispatchExecutor instances may each run concurrently with respect to each other.
+ * <p>
+ *
+ * This class also supports synchronous scheduling, like the @link {@link SerialExecutor} class; and in this case,
+ * only one caller thread will execute the tasks scheduled in the DispatchQueue (and the internal
+ * threadpool won't be used).
+ *
+ * <p>
+ *
+ * This class is <b>lock free</b> by design and ensures <b>"safe object publication"</b> between scheduling threads and
+ * actual executing thread: if one thread T1 schedules a task, but another thread T2 actually
+ * executes it, then all the objects from the T1 thread will be "safely published" to the executing T2 thread.
+ * Safe publication is ensured because we are using a ConcurrentLinkedQueue, and volatile attributes.
+ * (see [1], chapter 3.5.3 (Safe publication idioms).
+ *
+ * [1] Java Concurrency In Practice, Addison Wesley
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class DispatchExecutor implements Executor, Runnable {
+ /**
+ * The threadpool used for the execution of the tasks that are scheduled in this queue.
+ */
+ private final Executor m_threadPool;
+
+ /**
+ * List of tasks scheduled in our queue.
+ */
+ protected final ConcurrentLinkedQueue<Runnable> m_tasks = new ConcurrentLinkedQueue<>();
+
+ /**
+ * Marker used to remember the id of the thread currently executing this dispatch queue.
+ */
+ private volatile Thread m_executingThread;
+
+ /**
+ * Flag telling if this dispatch queue is already scheduled for execution in the threadpool.
+ */
+ private final AtomicBoolean m_scheduled = new AtomicBoolean();
+
+ /**
+ * Logger used to log exceptions thrown by scheduled tasks.
+ */
+ private final Logger m_logger;
+
+ /**
+ * Creates a new DispatchQueue, which can be executed within a fixed thread pool. Multiple queue
+ * can be executed concurrently, but all runnables scheduled in a given queue will be executed serially,
+ * in FIFO order.
+ *
+ * @param threadPool the executor (typically a threadpool) used to execute this DispatchExecutor.
+ * @param logger the Logger used when errors are taking place
+ */
+ public DispatchExecutor(Executor threadPool, Logger logger) {
+ m_logger = logger;
+ m_threadPool = threadPool;
+ }
+
+ /**
+ * Enqueues a task for later execution. You must call {@link #execute()} in order
+ * to trigger the actual execution of all scheduled tasks (in FIFO order).
+ */
+ public void schedule(Runnable task) {
+ m_tasks.add(task);
+ }
+
+ /**
+ * Submits a task in this queue, and schedule the execution of this DispatchQueue in the threadpool.
+ * The task is immediately executed (inline execution) if the queue is currently being executed by
+ * the current thread.
+ */
+ public void execute(Runnable task) {
+ execute(task, true);
+ }
+
+ /**
+ * Schedules a task in this queue.
+ * If the queue is currently being executed by the current thread, then the task is executed immediately.
+ * @tasks the task to schedule
+ * @threadpool true if the queue should be executed in the threadpool, false if the queue must be executed by
+ * only one caller thread.
+ */
+ public void execute(Runnable task, boolean threadpool) {
+ Thread currThread = Thread.currentThread();
+ if (m_executingThread == currThread) {
+ runTask(task);
+ } else {
+ schedule(task);
+ execute(threadpool);
+ }
+ }
+
+ /**
+ * Schedules the execution of this DispatchQueue in the threadpool.
+ */
+ public void execute() {
+ execute(true);
+ }
+
+ /**
+ * Schedules the execution of this DispatchQueue in the threadpool, or from a single caller thread.
+ *
+ * @param threadpool true means the DispatchQueue is executed in the threadpool, false means the queue is executed from the
+ * caller thread.
+ */
+ public void execute(boolean threadpool) {
+ if (m_scheduled.compareAndSet(false, true)) { // schedules our run method in the tpool.
+ try {
+ if (threadpool) {
+ m_threadPool.execute(this);
+ } else {
+ run(); // run all queue tasks from the caller thread
+ }
+ } catch (RejectedExecutionException e) {
+ // The threadpool seems stopped (maybe the framework is being stopped). Anyway, just execute our tasks
+ // from the current thread.
+ run();
+ }
+ }
+ }
+
+ /**
+ * Run all tasks scheduled in this queue, in FIFO order. This method may be executed either in the threadpool, or from
+ * the caller thread.
+ */
+ @Override
+ public void run() {
+ try {
+ // We do a memory barrier in order to ensure consistent per-thread
+ // memory visibility
+ m_executingThread = Thread.currentThread();
+ Runnable task;
+ while ((task = m_tasks.poll()) != null) {
+ runTask(task);
+ }
+ } finally {
+ m_scheduled.set(false);
+ m_executingThread = null;
+ if (m_tasks.peek() != null) {
+ execute();
+ }
+ }
+ }
+
+ /**
+ * Runs a given task
+ * @param task the task to execute
+ */
+ private void runTask(Runnable task) {
+ try {
+ task.run();
+ } catch (Throwable t) {
+ m_logger.log(LogService.LOG_ERROR, "Error processing tasks", t);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FactoryConfigurationAdapterImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FactoryConfigurationAdapterImpl.java
new file mode 100644
index 0000000..f96214a
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FactoryConfigurationAdapterImpl.java
@@ -0,0 +1,302 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.PropertyMetaData;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.metatype.MetaTypeProvider;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * Factory configuration adapter service implementation. This class extends the FilterService in order to catch
+ * some Service methods for configuring actual adapter service implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FactoryConfigurationAdapterImpl extends FilterComponent {
+ // Our Managed Service Factory PID
+ protected final String m_factoryPid;
+
+ // Our logger
+ protected final Logger m_logger;
+
+ public FactoryConfigurationAdapterImpl(DependencyManager dm, String factoryPid, String update, boolean propagate) {
+ super(dm.createComponent()); // This service will be filtered by our super class, allowing us to take control.
+ m_factoryPid = factoryPid;
+ m_logger = ((ComponentImpl) m_component).getLogger();
+
+ Hashtable<String, Object> props = new Hashtable<>();
+ props.put(Constants.SERVICE_PID, factoryPid);
+ m_component
+ .setInterface(ManagedServiceFactory.class.getName(), props)
+ .setImplementation(new AdapterImpl(update, propagate))
+ .setCallbacks("init", null, "stop", null);
+ }
+
+ public FactoryConfigurationAdapterImpl(DependencyManager dm, String factoryPid, String update, boolean propagate,
+ BundleContext bctx, Logger logger, String heading, String description, String localization, PropertyMetaData[] properyMetaData) {
+ super(dm.createComponent()); // This service will be filtered by our super class, allowing us to take control.
+ m_factoryPid = factoryPid;
+ m_logger = logger;
+ Hashtable<String, Object> props = new Hashtable<>();
+ props.put(Constants.SERVICE_PID, factoryPid);
+ m_component
+ .setInterface(ManagedServiceFactory.class.getName(), props)
+ .setImplementation(new MetaTypeAdapterImpl(update, propagate,
+ bctx, logger, heading, description,
+ localization, properyMetaData))
+ .setCallbacks("init", null, "stop", null);
+ }
+
+ public String getName() {
+ return "Adapter for factory pid " + m_factoryPid;
+ }
+
+ /**
+ * Creates, updates, or removes a service, when a ConfigAdmin factory configuration is created/updated or deleted.
+ */
+ public class AdapterImpl extends AbstractDecorator implements ManagedServiceFactory {
+ // The adapter "update" method used to provide the configuration
+ protected final String m_update;
+
+ // Tells if the CM config must be propagated along with the adapter service properties
+ protected final boolean m_propagate;
+
+ /**
+ * Creates a new CM factory configuration adapter.
+ *
+ * @param factoryPid
+ * @param updateMethod
+ * @param adapterInterface
+ * @param adapterImplementation
+ * @param adapterProperties
+ * @param propagate
+ */
+ public AdapterImpl(String updateMethod, boolean propagate) {
+ m_update = updateMethod;
+ m_propagate = propagate;
+ }
+
+ /**
+ * Returns the managed service factory name.
+ */
+ public String getName() {
+ return m_factoryPid;
+ }
+
+ /**
+ * Method called from our superclass, when we need to create a service.
+ */
+ @SuppressWarnings("unchecked")
+ public Component createService(Object[] properties) {
+ Dictionary<String, ?> settings = (Dictionary<String, ?>) properties[0];
+ Component newService = m_manager.createComponent();
+ Object impl = null;
+
+ try {
+ if (m_serviceImpl != null) {
+ impl = (m_serviceImpl instanceof Class) ? ((Class<?>) m_serviceImpl).newInstance() : m_serviceImpl;
+ }
+ else {
+ impl = instantiateFromFactory(m_factory, m_factoryCreateMethod);
+ }
+ InvocationUtil.invokeCallbackMethod(impl, m_update,
+ new Class[][] {{ Dictionary.class }, {}},
+ new Object[][] {{ settings }, {}});
+ }
+
+ catch (Throwable t) {
+ handleException(t);
+ }
+
+ // Merge adapter service properties, with CM settings
+ Dictionary<String, Object> serviceProperties = getServiceProperties(settings);
+ newService.setInterface(m_serviceInterfaces, serviceProperties);
+ newService.setImplementation(impl);
+ newService.setComposition(m_compositionInstance, m_compositionMethod); // if not set, no effect
+ newService.setCallbacks(m_callbackObject, m_init, m_start, m_stop, m_destroy); // if not set, no effect
+ configureAutoConfigState(newService, m_component);
+
+ for (DependencyContext dc : m_component.getDependencies()) {
+ newService.add((Dependency) dc.createCopy());
+ }
+
+ for (int i = 0; i < m_stateListeners.size(); i ++) {
+ newService.add(m_stateListeners.get(i));
+ }
+
+ return newService;
+ }
+
+ /**
+ * Method called from our superclass, when we need to update a Service, because
+ * the configuration has changed.
+ */
+ @SuppressWarnings("unchecked")
+ public void updateService(Object[] properties) {
+ Dictionary<String, ?> cmSettings = (Dictionary<String, ?>) properties[0];
+ Component service = (Component) properties[1];
+ Object impl = service.getInstances()[0];
+
+ try {
+ InvocationUtil.invokeCallbackMethod(impl, m_update,
+ new Class[][] {{ Dictionary.class }, {}},
+ new Object[][] {{ cmSettings }, {}});
+ if (m_serviceInterfaces != null && m_propagate == true) {
+ Dictionary<String, ?> serviceProperties = getServiceProperties(cmSettings);
+ service.setServiceProperties(serviceProperties);
+ }
+ }
+
+ catch (Throwable t) {
+ handleException(t);
+ }
+ }
+
+ /**
+ * Merge CM factory configuration setting with the adapter service properties. The private CM factory configuration
+ * settings are ignored. A CM factory configuration property is private if its name starts with a dot (".").
+ *
+ * @param adapterProperties
+ * @param settings
+ * @return
+ */
+ private Dictionary<String, Object> getServiceProperties(Dictionary<String, ?> settings) {
+ Dictionary<String, Object> props = new Hashtable<>();
+
+ // Add adapter Service Properties
+ if (m_serviceProperties != null) {
+ Enumeration<String> keys = m_serviceProperties.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ Object val = m_serviceProperties.get(key);
+ props.put(key, val);
+ }
+ }
+
+ if (m_propagate) {
+ // Add CM setting into adapter service properties.
+ // (CM setting will override existing adapter service properties).
+ Enumeration<String> keys = settings.keys();
+ while (keys.hasMoreElements()) {
+ String key = keys.nextElement();
+ if (! key.toString().startsWith(".")) {
+ // public properties are propagated
+ Object val = settings.get(key);
+ props.put(key, val);
+ }
+ }
+ }
+
+
+ return props;
+ }
+
+ private Object instantiateFromFactory(Object mFactory, String mFactoryCreateMethod) {
+ Object factory = null;
+ if (m_factory instanceof Class) {
+ try {
+ factory = createInstance((Class<?>) m_factory);
+ }
+ catch (Throwable t) {
+ handleException(t);
+ }
+ }
+ else {
+ factory = m_factory;
+ }
+
+ try {
+ return InvocationUtil.invokeMethod(factory, factory.getClass(), m_factoryCreateMethod, new Class[][] { {} }, new Object[][] { {} }, false);
+ }
+ catch (Throwable t) {
+ handleException(t);
+ return null;
+ }
+ }
+
+ private Object createInstance(Class<?> clazz) throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException {
+ Constructor<?> constructor = clazz.getConstructor(new Class[] {});
+ constructor.setAccessible(true);
+ return clazz.newInstance();
+ }
+
+ private void handleException(Throwable t) {
+ m_logger.log(Logger.LOG_ERROR, "Got exception while handling configuration update for factory pid " + m_factoryPid, t);
+ if (t instanceof InvocationTargetException) {
+ // Our super class will check if the target exception is itself a ConfigurationException.
+ // In this case, it will simply re-thrown.
+ throw new RuntimeException(((InvocationTargetException) t).getTargetException());
+ }
+ else if (t instanceof RuntimeException) {
+ throw (RuntimeException) t;
+ }
+ else {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+
+
+ /**
+ * Extends AdapterImpl for MetaType support.
+ */
+ class MetaTypeAdapterImpl extends AdapterImpl implements MetaTypeProvider {
+ // Our MetaType Provider for describing our properties metadata
+ private final MetaTypeProviderImpl m_metaType;
+
+ public MetaTypeAdapterImpl(String updateMethod, boolean propagate,
+ BundleContext bctx, Logger logger, String heading,
+ String description, String localization,
+ PropertyMetaData[] properyMetaData) {
+ super(updateMethod, propagate);
+ m_metaType = new MetaTypeProviderImpl(m_factoryPid, bctx, logger, null, this);
+ m_metaType.setName(heading);
+ m_metaType.setDescription(description);
+ if (localization != null) {
+ m_metaType.setLocalization(localization);
+ }
+ for (int i = 0; i < properyMetaData.length; i++) {
+ m_metaType.add(properyMetaData[i]);
+ }
+ }
+
+ public String[] getLocales() {
+ return m_metaType.getLocales();
+ }
+
+ public ObjectClassDefinition getObjectClassDefinition(String id, String locale) {
+ return m_metaType.getObjectClassDefinition(id, locale);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FieldUtil.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FieldUtil.java
new file mode 100644
index 0000000..dbaa123
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FieldUtil.java
@@ -0,0 +1,351 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.Event;
+
+/**
+ * Reflection Helper methods, used to inject autoconfig fields in component instances.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FieldUtil {
+ /**
+ * Callbacks for fields to be injected
+ */
+ private interface FieldFunction {
+ // Inject an updated service in the given field for the the given target.
+ void injectField(Field f, Object target);
+
+ // Inject an Iterable Field in the given target
+ void injectIterableField(Field f, Object target);
+
+ // Inject a Map field in the given target (key = dependency service, value = Dictionary with dependency service properties).
+ void injectMapField(Field f, Object target);
+ }
+
+ /**
+ * Injects some component instances (on a given field, if provided), with an object of a given class.
+ * @param targets the component instances to fill in
+ * @param fieldName the fieldname, or null. If null, the field must exaclty match the injected service classname.
+ * @param clazz the injected service class
+ * @param service the injected service
+ * @param logger the component logger.
+ */
+ public static boolean injectField(Object[] targets, String fieldName, Class<?> clazz, final Object service,
+ final Logger logger)
+ {
+ if (service == null) {
+ return true; // TODO why service can be null ?
+ }
+ return mapField(true, clazz, targets, fieldName, logger, new FieldFunction() {
+ public void injectField(Field f, Object target) {
+ try {
+ f.setAccessible(true);
+ f.set(target, service);
+ } catch (Throwable e) {
+ logger.log(Logger.LOG_ERROR, "Could not set field " + f + " in class "
+ + target.getClass().getName(), e);
+ }
+ }
+
+ public void injectIterableField(Field f, Object target) { // never called
+ }
+
+ public void injectMapField(Field f, Object target) { // never called
+ }
+ });
+ }
+
+ /**
+ * Injects a dependency service in some component instances.
+ * Here, we'll inject the dependency services in the component if the field is of the same type of the injected services,
+ * or if the field is a Collection of the injected service, or if the field is a Map<Injected Service class, Dictionary).
+ * @param targets the component instances to fill in
+ * @param fieldName the fieldname, or null. If null, the field must exaclty match the injected service classname.
+ * @param clazz the injected service class
+ * @param service the injected service
+ * @param logger the component logger.
+ */
+ public static boolean injectDependencyField(Object[] targets, String fieldName, Class<?> clazz,
+ final DependencyContext dc, final Logger logger)
+ {
+ final Event event = dc.getService();
+ if (event == null) {
+ return true; // TODO check why event can be null
+ }
+ return mapField(false, clazz, targets, fieldName, logger, new FieldFunction() {
+ public void injectField(Field f, Object target) {
+ try {
+ f.setAccessible(true);
+ f.set(target, event.getEvent());
+ } catch (Throwable e) {
+ logger.log(Logger.LOG_ERROR, "Could not set field " + f + " in class "
+ + target.getClass().getName(), e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void injectIterableField(Field f, Object target) {
+ f.setAccessible(true);
+
+ try {
+ Iterable<Object> iter = (Iterable<Object>) f.get(target);
+ if (iter == null) {
+ iter = new ConcurrentLinkedQueue<Object>();
+ f.set(target, iter);
+ }
+ dc.copyToCollection((Collection<Object>) iter);
+ } catch (Throwable e) {
+ logger.log(Logger.LOG_ERROR, "Could not set field " + f + " in class "
+ + target.getClass().getName(), e);
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public void injectMapField(Field f, Object target) {
+ f.setAccessible(true);
+ try {
+ Map<Object, Dictionary<?, ?>> map = (Map) f.get(target);
+ if (map == null) {
+ map = new ConcurrentHashMap<>();
+ f.set(target, map);
+ }
+ dc.copyToMap(map);
+ } catch (Throwable e) {
+ logger.log(Logger.LOG_ERROR, "Could not set field " + f + " in class "
+ + target.getClass().getName(), e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Adds, or removes, or update some component instances with an updated dependency service
+ * @param targets the component instances to fill in with the updated service
+ * @param fieldName the component instance fieldname
+ * @param update true if it's a dependency service update, false if the dependency service is added or removed
+ * @param add true if the dependency service has been added, false the dependency service has been removed.
+ * This flag is ignored if the "update" parameter is "true".
+ * @param clazz the clazz of the dependency service
+ * @param event the event holding the dependency service
+ * @param dc the dependency service context
+ * @param logger the logger used when problems occure.
+ */
+ public static void updateDependencyField(Object[] targets, String fieldName, final boolean update,
+ final boolean add, Class<?> clazz, final Event event, final DependencyContext dc, final Logger logger)
+ {
+ mapField(false, clazz, targets, fieldName, logger, new FieldFunction() {
+ public void injectField(Field f, Object target) {
+ try {
+ f.setAccessible(true);
+ f.set(target, dc.getService().getEvent());
+ } catch (Throwable e) {
+ logger.log(Logger.LOG_ERROR, "Could not set field " + f + " in class "
+ + target.getClass().getName(), e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public void injectIterableField(Field f, Object target) {
+ if (update) {
+ return;
+ }
+
+ f.setAccessible(true);
+
+ try {
+ Collection<Object> coll = (Collection<Object>) f.get(target);
+ if (add) {
+ coll.add(event.getEvent());
+ } else {
+ coll.remove(event.getEvent());
+ }
+ } catch (Throwable e) {
+ logger.log(Logger.LOG_ERROR, "Could not set field " + f + " in class "
+ + target.getClass().getName(), e);
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ public void injectMapField(Field f, Object target) {
+ f.setAccessible(true);
+
+ try {
+ Map<Object, Dictionary<?, ?>> map = (Map) f.get(target);
+ if (add) {
+ map.put(event.getEvent(), event.getProperties());
+ } else {
+ map.remove(event.getEvent());
+ }
+ } catch (Throwable e) {
+ logger.log(Logger.LOG_ERROR, "Could not set field " + f + " in class "
+ + target.getClass().getName(), e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Scans component instances for fields having either the same dependency service type, or being a
+ * Collection of the dependency service, or being a Map<Dependency Service class, Dictionary> (the Dictionary
+ * corresponds to the dependency service properties).
+ * @param strict true if we are only looking for fields having exactly the same type as the dependency service.
+ * In other words, if strict = true, we don't lookup for fields of "Collection" or "Map" types.
+ * @param clazz the dependency service class
+ * @param targets the component instances
+ * @param fieldName the component instances field name or null
+ * @param logger a logger used when exceptions are occuring
+ * @param func the callback used to notify when we find either a field with the same dependency service type, or
+ * with a "Collection" type, or with a "Map" type.
+ */
+ private static boolean mapField(boolean strict, Class<?> clazz, Object[] targets, String fieldName, Logger logger,
+ FieldFunction func)
+ {
+ boolean injected = false;
+ if (targets != null && clazz != null) {
+ for (int i = 0; i < targets.length; i++) {
+ Object target = targets[i];
+ Class<?> targetClass = target.getClass();
+ if (Proxy.isProxyClass(targetClass)) {
+ target = Proxy.getInvocationHandler(target);
+ targetClass = target.getClass();
+ }
+ while (targetClass != null) {
+ Field[] fields = targetClass.getDeclaredFields();
+ for (int j = 0; j < fields.length; j++) {
+ Field field = fields[j];
+ Class<?> fieldType = field.getType();
+
+ if (fieldName == null) {
+ // Field type class must match injected service type
+ if (fieldType.equals(clazz)) {
+ injected = true;
+ func.injectField(field, target);
+ } else if (!strict && mayInjectToIterable(clazz, field, true)) {
+ injected = true;
+ func.injectIterableField(field, target);
+ } else if (!strict && mayInjectToMap(clazz, field, true)) {
+ injected = true;
+ func.injectMapField(field, target);
+ }
+ } else if (field.getName().equals(fieldName)) {
+ // Field type may be a superclass of the service type
+ if (fieldType.isAssignableFrom(clazz)) {
+ injected = true;
+ func.injectField(field, target);
+ } else if (!strict && mayInjectToIterable(clazz, field, false)) {
+ injected = true;
+ func.injectIterableField(field, target);
+ } else if (!strict && mayInjectToMap(clazz, field, false)) {
+ injected = true;
+ func.injectMapField(field, target);
+ } else {
+ logger.log(
+ Logger.LOG_ERROR,
+ "Could not set field " + field + " in class " + target.getClass().getName()
+ + ": the type of the field type should be either assignable from "
+ + clazz.getName() + " or Collection, or Map");
+ }
+ }
+ }
+ targetClass = targetClass.getSuperclass();
+ }
+ }
+ }
+ return injected;
+ }
+
+ private static boolean mayInjectToIterable(Class<?> clazz, Field field, boolean strictClassEquality) {
+ Class<?> fieldType = field.getType();
+ if (Iterable.class.isAssignableFrom(fieldType)) {
+ ParameterizedType parameterType = (ParameterizedType) field.getGenericType();
+ if (parameterType == null) {
+ return false;
+ }
+ Type[] types = parameterType.getActualTypeArguments();
+ if (types == null || types.length == 0) {
+ return false;
+ }
+ if (types[0] instanceof Class<?>) {
+ Class<?> parameterizedTypeClass = (Class<?>) types[0];
+ return strictClassEquality ? parameterizedTypeClass.equals(clazz)
+ : parameterizedTypeClass.isAssignableFrom(clazz);
+ }
+ }
+ return false;
+ }
+
+ private static boolean mayInjectToMap(Class<?> clazz, Field field, boolean strictClassEquality) {
+ Class<?> fieldType = field.getType();
+ if (Map.class.isAssignableFrom(fieldType)) {
+ // The field must be a parameterized map (generics).
+ if (! (field.getGenericType() instanceof ParameterizedType)) {
+ return false;
+ }
+ ParameterizedType parameterType = (ParameterizedType) field.getGenericType();
+ if (parameterType == null) {
+ return false;
+ }
+
+ Type[] types = parameterType.getActualTypeArguments();
+ if (types == null || types.length < 2) {
+ return false;
+ }
+
+ // The map field generic key parameter must be "Class".
+ if (! (types[0] instanceof Class<?>)) {
+ return false;
+ }
+
+ // The map generic value parameter must be Dictionary, or Dictionary<String, ...>
+ if (types[1] instanceof Class<?>) {
+ // The map field is in the form "Map m_field<Class, Dictionary>"
+ Class<?> mapValueGenericType = (Class<?>) types[1];
+ if (! mapValueGenericType.equals(Dictionary.class)) {
+ return false;
+ }
+ } else if (types[1] instanceof ParameterizedType) {
+ // The map field is in the form "Map m_field<Class, Dictionary<String, ...>"
+ ParameterizedType mapValueGenericType = (ParameterizedType) types[1];
+ if (! mapValueGenericType.getRawType().equals(Dictionary.class)) {
+ return false;
+ }
+ }
+
+ Class<?> K = (Class<?>) types[0];
+ return strictClassEquality ? K.equals(clazz) : K.isAssignableFrom(clazz);
+ }
+ return false;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
new file mode 100644
index 0000000..33bf464
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/FilterComponent.java
@@ -0,0 +1,354 @@
+/*
+ * 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.dm.impl;
+
+import java.util.Dictionary;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executor;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDeclaration;
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.context.ComponentContext;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.context.EventType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * This class allows to filter a Component interface. All Aspect/Adapters extend this class
+ * in order to add functionality to the default Component implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FilterComponent implements Component, ComponentContext, ComponentDeclaration {
+ protected volatile ComponentImpl m_component;
+ protected volatile List<ComponentStateListener> m_stateListeners = new CopyOnWriteArrayList<>();
+ protected volatile String m_init = "init";
+ protected volatile String m_start = "start";
+ protected volatile String m_stop = "stop";
+ protected volatile String m_destroy = "destroy";
+ protected volatile Object m_callbackObject;
+ protected volatile Object m_compositionInstance;
+ protected volatile String m_compositionMethod;
+ protected volatile String[] m_serviceInterfaces;
+ protected volatile Object m_serviceImpl;
+ protected volatile Object m_factory;
+ protected volatile String m_factoryCreateMethod;
+ protected volatile Dictionary<String, Object> m_serviceProperties;
+
+ public FilterComponent(Component service) {
+ m_component = (ComponentImpl) service;
+ }
+
+ @Override
+ public String toString() {
+ return m_component.toString();
+ }
+
+ public Component add(Dependency ... dependencies) {
+ m_component.add(dependencies);
+ // Add the dependencies to all already instantiated services.
+ // If one dependency from the list is required, we have nothing to do, since our internal
+ // service will be stopped/restarted.
+ for (Dependency dependency : dependencies) {
+ if (((DependencyContext) dependency).isRequired()) {
+ return this;
+ }
+ }
+ // Ok, the list contains no required dependencies: add optionals dependencies in already instantiated services.
+ Object[] instances = m_component.getInstances();
+ if (instances.length > 0) {
+ AbstractDecorator ad = (AbstractDecorator) instances[0];
+ if (ad != null) {
+ ad.addDependency(dependencies);
+ }
+ }
+ return this;
+ }
+
+ public Component add(ComponentStateListener listener) {
+ m_stateListeners.add(listener);
+ // Add the listener to all already instantiated services.
+ Object[] instances = m_component.getInstances();
+ if (instances.length > 0) {
+ AbstractDecorator ad = (AbstractDecorator) instances[0];
+ if (ad != null) {
+ ad.addStateListener(listener);
+ }
+ }
+ return this;
+ }
+
+ public List<DependencyContext> getDependencies() {
+ return m_component.getDependencies();
+ }
+
+ public String getClassName() {
+ return m_component.getClassName();
+ }
+
+ @SuppressWarnings("unchecked")
+ public Dictionary<String, Object> getServiceProperties() {
+ return m_serviceProperties;
+ }
+
+ public ServiceRegistration getServiceRegistration() {
+ return m_component.getServiceRegistration();
+ }
+
+ public Component remove(Dependency dependency) {
+ m_component.remove(dependency);
+ // Remove the dependency (if optional) from all already instantiated services.
+ // If the dependency is required, our internal service will be stopped, so in this case
+ // we have nothing to do.
+ if (!((DependencyContext) dependency).isRequired())
+ {
+ Object[] instances = m_component.getInstances();
+ if (instances.length > 0) {
+ AbstractDecorator ad = (AbstractDecorator) instances[0];
+ if (ad != null) {
+ ad.removeDependency(dependency);
+ }
+ }
+ }
+ return this;
+ }
+
+ public Component remove(ComponentStateListener listener) {
+ m_stateListeners.remove(listener);
+ // Remove the listener from all already instantiated services.
+ Object[] instances = m_component.getInstances();
+ if (instances.length > 0) {
+ AbstractDecorator ad = (AbstractDecorator) instances[0];
+ if (ad != null) {
+ ad.removeStateListener(listener);
+ }
+ }
+ return this;
+ }
+
+ public Component setCallbacks(Object instance, String init, String start, String stop, String destroy) {
+ m_component.ensureNotActive();
+ m_callbackObject = instance;
+ m_init = init;
+ m_start = start;
+ m_stop = stop;
+ m_destroy = destroy;
+ return this;
+ }
+
+ public Component setCallbacks(String init, String start, String stop, String destroy) {
+ setCallbacks(null, init, start, stop, destroy);
+ return this;
+ }
+
+ public Component setComposition(Object instance, String getMethod) {
+ m_component.ensureNotActive();
+ m_compositionInstance = instance;
+ m_compositionMethod = getMethod;
+ return this;
+ }
+
+ public Component setComposition(String getMethod) {
+ m_component.ensureNotActive();
+ m_compositionMethod = getMethod;
+ return this;
+ }
+
+ public Component setFactory(Object factory, String createMethod) {
+ m_component.ensureNotActive();
+ m_factory = factory;
+ m_factoryCreateMethod = createMethod;
+ return this;
+ }
+
+ public Component setFactory(String createMethod) {
+ return setFactory(null, createMethod);
+ }
+
+ public Component setImplementation(Object implementation) {
+ m_component.ensureNotActive();
+ m_serviceImpl = implementation;
+ return this;
+ }
+
+ public Component setInterface(String serviceName, Dictionary<?, ?> properties) {
+ return setInterface(new String[] { serviceName }, properties);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Component setInterface(String[] serviceInterfaces, Dictionary<?, ?> properties) {
+ m_component.ensureNotActive();
+ if (serviceInterfaces != null) {
+ m_serviceInterfaces = new String[serviceInterfaces.length];
+ System.arraycopy(serviceInterfaces, 0, m_serviceInterfaces, 0, serviceInterfaces.length);
+ m_serviceProperties = (Dictionary<String, Object>) properties;
+ }
+ return this;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Component setServiceProperties(Dictionary<?, ?> serviceProperties) {
+ m_serviceProperties = (Dictionary<String, Object>) serviceProperties;
+ // Set the properties to all already instantiated services.
+ if (serviceProperties != null) {
+ Object[] instances = m_component.getInstances();
+ if (instances.length > 0) {
+ AbstractDecorator ad = (AbstractDecorator) instances[0];
+ if (ad != null) {
+ ad.setServiceProperties(serviceProperties);
+ }
+ }
+ }
+ return this;
+ }
+
+ public void start() {
+ m_component.start();
+ }
+
+ public void stop() {
+ m_component.stop();
+ }
+
+ public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures, Object[][] parameters) {
+ m_component.invokeCallbackMethod(instances, methodName, signatures, parameters);
+ }
+
+ public DependencyManager getDependencyManager() {
+ return m_component.getDependencyManager();
+ }
+
+ public Component setAutoConfig(Class<?> clazz, boolean autoConfig) {
+ m_component.setAutoConfig(clazz, autoConfig);
+ return this;
+ }
+
+ public Component setAutoConfig(Class<?> clazz, String instanceName) {
+ m_component.setAutoConfig(clazz, instanceName);
+ return this;
+ }
+
+ public boolean getAutoConfig(Class<?> clazz) {
+ return m_component.getAutoConfig(clazz);
+ }
+
+ public String getAutoConfigInstance(Class<?> clazz) {
+ return m_component.getAutoConfigInstance(clazz);
+ }
+
+ public ComponentDependencyDeclaration[] getComponentDependencies() {
+ return m_component.getComponentDependencies();
+ }
+
+ public String getName() {
+ return m_component.getName();
+ }
+
+ public int getState() {
+ return m_component.getState();
+ }
+
+ public long getId() {
+ return m_component.getId();
+ }
+
+ public String[] getServices() {
+ return m_component.getServices();
+ }
+
+ public BundleContext getBundleContext() {
+ return m_component.getBundleContext();
+ }
+
+ @Override
+ public boolean isActive() {
+ return m_component.isActive();
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return m_component.isAvailable();
+ }
+
+ @Override
+ public void handleEvent(DependencyContext dc, EventType type, Event ... e) {
+ m_component.handleEvent(dc, type, e);
+ }
+
+ @Override
+ public <T> T getInstance() {
+ return m_component.getInstance();
+ }
+
+ @Override
+ public Object[] getInstances() {
+ return m_component.getInstances();
+ }
+
+ @Override
+ public Event getDependencyEvent(DependencyContext dc) {
+ return m_component.getDependencyEvent(dc);
+ }
+
+ @Override
+ public Set<Event> getDependencyEvents(DependencyContext dc) {
+ return m_component.getDependencyEvents(dc);
+ }
+
+ public ComponentDeclaration getComponentDeclaration() {
+ return this;
+ }
+
+ @Override
+ public Component setDebug(String label) {
+ m_component.setDebug(label);
+ return this;
+ }
+
+ @Override
+ public void setThreadPool(Executor threadPool) {
+ m_component.setThreadPool(threadPool);
+ }
+
+ @Override
+ public Map<String, Long> getCallbacksTime() {
+ return m_component.getCallbacksTime();
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return m_component.getBundle();
+ }
+
+ @Override
+ public Logger getLogger() {
+ return m_component.getLogger();
+ }
+}
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/InvocationUtil.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/InvocationUtil.java
new file mode 100644
index 0000000..880eba2
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/InvocationUtil.java
@@ -0,0 +1,205 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Utility methods for invoking callbacks. Lookups of callbacks are accellerated by using a LRU cache.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class InvocationUtil {
+ private static final Map<Key, Method> m_methodCache;
+ static {
+ int size = 2048;
+ // TODO enable this again
+// try {
+// String value = System.getProperty(DependencyManager.METHOD_CACHE_SIZE);
+// if (value != null) {
+// size = Integer.parseInt(value);
+// }
+// }
+// catch (Exception e) {}
+ m_methodCache = new LRUMap(Math.max(size, 64));
+ }
+
+ /**
+ * Invokes a callback method on an instance. The code will search for a callback method with
+ * the supplied name and any of the supplied signatures in order, invoking the first one it finds.
+ *
+ * @param instance the instance to invoke the method on
+ * @param methodName the name of the method
+ * @param signatures the ordered list of signatures to look for
+ * @param parameters the parameter values to use for each potential signature
+ * @return whatever the method returns
+ * @throws NoSuchMethodException when no method could be found
+ * @throws IllegalArgumentException when illegal values for this methods arguments are supplied
+ * @throws IllegalAccessException when the method cannot be accessed
+ * @throws InvocationTargetException when the method that was invoked throws an exception
+ */
+ public static Object invokeCallbackMethod(Object instance, String methodName, Class<?>[][] signatures, Object[][] parameters) throws NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
+ Class<?> currentClazz = instance.getClass();
+ while (currentClazz != null && currentClazz != Object.class) {
+ try {
+ return invokeMethod(instance, currentClazz, methodName, signatures, parameters, false);
+ }
+ catch (NoSuchMethodException nsme) {
+ // ignore
+ }
+ currentClazz = currentClazz.getSuperclass();
+ }
+ throw new NoSuchMethodException(methodName);
+ }
+
+ /**
+ * Invoke a method on an instance.
+ *
+ * @param object the instance to invoke the method on
+ * @param clazz the class of the instance
+ * @param name the name of the method
+ * @param signatures the signatures to look for in order
+ * @param parameters the parameter values for the signatures
+ * @param isSuper <code>true</code> if this is a superclass and we should therefore not look for private methods
+ * @return whatever the method returns
+ * @throws NoSuchMethodException when no method could be found
+ * @throws IllegalArgumentException when illegal values for this methods arguments are supplied
+ * @throws IllegalAccessException when the method cannot be accessed
+ * @throws InvocationTargetException when the method that was invoked throws an exception
+ */
+ public static Object invokeMethod(Object object, Class<?> clazz, String name, Class<?>[][] signatures, Object[][] parameters, boolean isSuper) throws NoSuchMethodException, InvocationTargetException, IllegalArgumentException, IllegalAccessException {
+ if (object == null) {
+ throw new IllegalArgumentException("Instance cannot be null");
+ }
+ if (clazz == null) {
+ throw new IllegalArgumentException("Class cannot be null");
+ }
+
+ // if we're talking to a proxy here, dig one level deeper to expose the
+ // underlying invocation handler (we do the same for injecting instances)
+ if (Proxy.isProxyClass(clazz)) {
+ object = Proxy.getInvocationHandler(object);
+ clazz = object.getClass();
+ }
+
+ Method m = null;
+ for (int i = 0; i < signatures.length; i++) {
+ Class<?>[] signature = signatures[i];
+ m = getDeclaredMethod(clazz, name, signature, isSuper);
+ if (m != null) {
+ return m.invoke(object, parameters[i]);
+ }
+ }
+ throw new NoSuchMethodException(name);
+ }
+
+ private static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>[] signature, boolean isSuper) {
+ // first check our cache
+ Key key = new Key(clazz, name, signature);
+ Method m = null;
+ synchronized (m_methodCache) {
+ m = (Method) m_methodCache.get(key);
+ if (m != null) {
+ return m;
+ }
+ else if (m_methodCache.containsKey(key)) {
+ // the key is in our cache, it just happens to have a null value
+ return null;
+ }
+ }
+ // then do a lookup
+ try {
+ m = clazz.getDeclaredMethod(name, signature);
+ if (!(isSuper && Modifier.isPrivate(m.getModifiers()))) {
+ m.setAccessible(true);
+ }
+ }
+ catch (NoSuchMethodException e) {
+ }
+ synchronized (m_methodCache) {
+ m_methodCache.put(key, m);
+ }
+ return m;
+ }
+
+ public static class Key {
+ private final Class<?> m_clazz;
+ private final String m_name;
+ private final Class<?>[] m_signature;
+
+ public Key(Class<?> clazz, String name, Class<?>[] signature) {
+ m_clazz = clazz;
+ m_name = name;
+ m_signature = signature;
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((m_clazz == null) ? 0 : m_clazz.hashCode());
+ result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+ result = prime * result + Arrays.hashCode(m_signature);
+ return result;
+ }
+
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Key other = (Key) obj;
+ if (m_clazz == null) {
+ if (other.m_clazz != null)
+ return false;
+ }
+ else if (!m_clazz.equals(other.m_clazz))
+ return false;
+ if (m_name == null) {
+ if (other.m_name != null)
+ return false;
+ }
+ else if (!m_name.equals(other.m_name))
+ return false;
+ if (!Arrays.equals(m_signature, other.m_signature))
+ return false;
+ return true;
+ }
+ }
+
+ @SuppressWarnings("serial")
+ public static class LRUMap extends LinkedHashMap<Key, Method> {
+ private final int m_size;
+
+ public LRUMap(int size) {
+ m_size = size;
+ }
+
+ protected boolean removeEldestEntry(java.util.Map.Entry<Key, Method> eldest) {
+ return size() > m_size;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceAdapterImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceAdapterImpl.java
new file mode 100644
index 0000000..9bcc696
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceAdapterImpl.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.dm.impl;
+
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.ResourceDependency;
+import org.apache.felix.dm.context.DependencyContext;
+
+/**
+ * Resource adapter service implementation. This class extends the FilterService in order to catch
+ * some Service methods for configuring actual resource adapter service implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceAdapterImpl extends FilterComponent {
+ private Object m_callbackInstance = null;
+ private String m_callbackChanged = "changed";
+ private String m_callbackAdded = "setResource";
+ private final String m_resourceFilter;
+
+ /**
+ * Creates a new Resource Adapter Service implementation.
+ * @param dm the dependency manager used to create our internal adapter service
+ */
+ public ResourceAdapterImpl(DependencyManager dm, String resourceFilter, boolean propagate, Object callbackInstance, String callbackSet, String callbackChanged) {
+ super(dm.createComponent()); // This service will be filtered by our super class, allowing us to take control.
+ m_callbackInstance = callbackInstance;
+ m_callbackAdded = callbackSet;
+ m_callbackChanged = callbackChanged;
+ m_resourceFilter = resourceFilter;
+ m_component.setImplementation(new ResourceAdapterDecorator(propagate))
+ .add(dm.createResourceDependency()
+ .setFilter(resourceFilter)
+ .setAutoConfig(false)
+ .setCallbacks("added", "removed"))
+ .setCallbacks("init", null, "stop", null);
+ }
+
+ public ResourceAdapterImpl(DependencyManager dm, String resourceFilter, Object propagateCallbackInstance, String propagateCallbackMethod, Object callbackInstance, String callbackSet, String callbackChanged) {
+ super(dm.createComponent()); // This service will be filtered by our super class, allowing us to take control.
+ m_callbackInstance = callbackInstance;
+ m_callbackAdded = callbackSet;
+ m_callbackChanged = callbackChanged;
+ m_resourceFilter = resourceFilter;
+ m_component.setImplementation(new ResourceAdapterDecorator(propagateCallbackInstance, propagateCallbackMethod))
+ .add(dm.createResourceDependency()
+ .setFilter(resourceFilter)
+ .setAutoConfig(false)
+ .setCallbacks("added", "removed"))
+ .setCallbacks("init", null, "stop", null);
+ }
+
+ public String getName() {
+ return "Resource Adapter" + ((m_resourceFilter != null) ? " with filter " + m_resourceFilter : "");
+ }
+
+ public class ResourceAdapterDecorator extends AbstractDecorator {
+ private final boolean m_propagate;
+ private final Object m_propagateCallbackInstance;
+ private final String m_propagateCallbackMethod;
+
+ public ResourceAdapterDecorator(boolean propagate) {
+ this(propagate, null, null);
+ }
+
+ public ResourceAdapterDecorator(Object propagateCallbackInstance, String propagateCallbackMethod) {
+ this(true, propagateCallbackInstance, propagateCallbackMethod);
+ }
+
+ private ResourceAdapterDecorator(boolean propagate, Object propagateCallbackInstance, String propagateCallbackMethod) {
+ m_propagate = propagate;
+ m_propagateCallbackInstance = propagateCallbackInstance;
+ m_propagateCallbackMethod = propagateCallbackMethod;
+ }
+
+ public Component createService(Object[] properties) {
+ URL resource = (URL) properties[0];
+ Hashtable<String, Object> props = new Hashtable<>();
+ if (m_serviceProperties != null) {
+ Enumeration<String> e = m_serviceProperties.keys();
+ while (e.hasMoreElements()) {
+ String key = e.nextElement();
+ props.put(key, m_serviceProperties.get(key));
+ }
+ }
+ List<DependencyContext> dependencies = m_component.getDependencies();
+ // the first dependency is always the dependency on the resource, which
+ // will be replaced with a more specific dependency below
+ dependencies.remove(0);
+ ResourceDependency resourceDependency = m_manager.createResourceDependency()
+ .setResource(resource)
+ .setCallbacks(m_callbackInstance, m_callbackAdded, m_callbackChanged, null)
+ .setAutoConfig(m_callbackAdded == null)
+ .setRequired(true);
+ if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
+ resourceDependency.setPropagate(m_propagateCallbackInstance, m_propagateCallbackMethod);
+ } else {
+ resourceDependency.setPropagate(m_propagate);
+ }
+ Component service = m_manager.createComponent()
+ .setInterface(m_serviceInterfaces, props)
+ .setImplementation(m_serviceImpl)
+ .setFactory(m_factory, m_factoryCreateMethod) // if not set, no effect
+ .setComposition(m_compositionInstance, m_compositionMethod) // if not set, no effect
+ .setCallbacks(m_callbackObject, m_init, m_start, m_stop, m_destroy) // if not set, no effect
+ .add(resourceDependency);
+
+ configureAutoConfigState(service, m_component);
+
+ for (DependencyContext dc : dependencies) {
+ service.add((Dependency) dc.createCopy());
+ }
+
+ for (ComponentStateListener stateListener : m_stateListeners) {
+ service.add(stateListener);
+ }
+ return service;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceDependencyImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceDependencyImpl.java
new file mode 100644
index 0000000..effd588
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceDependencyImpl.java
@@ -0,0 +1,264 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDependencyDeclaration;
+import org.apache.felix.dm.ResourceDependency;
+import org.apache.felix.dm.ResourceHandler;
+import org.apache.felix.dm.context.AbstractDependency;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.context.EventType;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceDependencyImpl extends AbstractDependency<ResourceDependency> implements ResourceDependency, ResourceHandler, ComponentDependencyDeclaration {
+ private volatile ServiceRegistration m_registration;
+ private volatile String m_resourceFilter;
+ private volatile URL m_trackedResource;
+
+ public ResourceDependencyImpl() {
+ }
+
+ public ResourceDependencyImpl(ResourceDependencyImpl prototype) {
+ super(prototype);
+ m_resourceFilter = prototype.m_resourceFilter;
+ m_trackedResource = prototype.m_trackedResource;
+ }
+
+ @Override
+ public DependencyContext createCopy() {
+ return new ResourceDependencyImpl(this);
+ }
+
+ @Override
+ public void start() {
+ Dictionary<String, Object> props = null;
+ if (m_trackedResource != null) {
+ props = new Hashtable<>();
+ props.put(ResourceHandler.URL, m_trackedResource);
+ } else {
+ if (m_resourceFilter != null) {
+ props = new Hashtable<>();
+ props.put(ResourceHandler.FILTER, m_resourceFilter);
+ }
+ }
+ m_registration = m_component.getBundleContext().registerService(ResourceHandler.class.getName(), this, props);
+ super.start();
+ }
+
+ @Override
+ public void stop() {
+ m_registration.unregister();
+ m_registration = null;
+ super.stop();
+ }
+
+ public void added(URL resource) {
+ if (m_trackedResource == null || m_trackedResource.equals(resource)) {
+ getComponentContext().handleEvent(this, EventType.ADDED, new ResourceEventImpl(resource, null));
+ }
+ }
+
+ public void added(URL resource, Dictionary<?, ?> resourceProperties) {
+ if (m_trackedResource == null || m_trackedResource.equals(resource)) {
+ getComponentContext().handleEvent(this, EventType.ADDED, new ResourceEventImpl(resource, resourceProperties));
+ }
+ }
+
+ public void changed(URL resource) {
+ if (m_trackedResource == null || m_trackedResource.equals(resource)) {
+ m_component.handleEvent(this, EventType.CHANGED, new ResourceEventImpl(resource, null));
+ }
+ }
+
+ public void changed(URL resource, Dictionary<?, ?> resourceProperties) {
+ if (m_trackedResource == null || m_trackedResource.equals(resource)) {
+ m_component.handleEvent(this, EventType.CHANGED, new ResourceEventImpl(resource, resourceProperties));
+ }
+ }
+
+ public void removed(URL resource) {
+ if (m_trackedResource == null || m_trackedResource.equals(resource)) {
+ m_component.handleEvent(this, EventType.REMOVED, new ResourceEventImpl(resource, null));
+ }
+ }
+
+ public void removed(URL resource, Dictionary<?, ?> resourceProperties) {
+ if (m_trackedResource == null || m_trackedResource.equals(resource)) {
+ m_component.handleEvent(this, EventType.REMOVED, new ResourceEventImpl(resource, resourceProperties));
+ }
+ }
+
+ @Override
+ public void invokeCallback(EventType type, Event ... e) {
+ switch (type) {
+ case ADDED:
+ if (m_add != null) {
+ invoke(m_add, e[0]);
+ }
+ break;
+ case CHANGED:
+ if (m_change != null) {
+ invoke (m_change, e[0]);
+ }
+ break;
+ case REMOVED:
+ if (m_remove != null) {
+ invoke (m_remove, e[0]);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private void invoke(String method, Event e) {
+ ResourceEventImpl re = (ResourceEventImpl) e;
+ URL serviceInstance = re.getResource();
+ Dictionary<?,?> resourceProperties = re.getProperties();
+
+ m_component.invokeCallbackMethod(getInstances(), method,
+ new Class[][] {
+ { Component.class, URL.class, Dictionary.class },
+ { Component.class, URL.class },
+ { Component.class },
+ { URL.class, Dictionary.class },
+ { URL.class },
+ { Object.class },
+ {}},
+ new Object[][] {
+ { m_component, serviceInstance, resourceProperties },
+ { m_component, serviceInstance },
+ { m_component },
+ { serviceInstance, resourceProperties },
+ { serviceInstance },
+ { serviceInstance },
+ {}}
+ );
+
+ }
+
+ public ResourceDependency setResource(URL resource) {
+ m_trackedResource = resource;
+ return this;
+ }
+
+ public ResourceDependency setFilter(String resourceFilter) {
+ ensureNotActive();
+ m_resourceFilter = resourceFilter;
+ return this;
+ }
+
+ public ResourceDependency setFilter(String resourceFilter, String resourcePropertiesFilter) {
+ ensureNotActive();
+ m_resourceFilter = resourceFilter;
+ return this;
+ }
+
+ @Override
+ public Class<?> getAutoConfigType() {
+ return URL.class;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Dictionary<String, Object> getProperties() {
+ ResourceEventImpl re = (ResourceEventImpl) m_component.getDependencyEvent(this);
+ if (re != null) {
+ URL resource = re.getResource();
+ Dictionary<String, Object> resourceProperties = re.getProperties();
+ if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
+ try {
+ return (Dictionary<String, Object>) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, new Class[][] {{ URL.class }}, new Object[][] {{ resource }});
+ }
+ catch (InvocationTargetException e) {
+ m_component.getLogger().warn("Exception while invoking callback method", e.getCause());
+ }
+ catch (Throwable e) {
+ m_component.getLogger().warn("Exception while trying to invoke callback method", e);
+ }
+ throw new IllegalStateException("Could not invoke callback");
+ }
+ else {
+ Hashtable<String, Object> props = new Hashtable<>();
+ props.put(ResourceHandler.HOST, resource.getHost());
+ props.put(ResourceHandler.PATH, resource.getPath());
+ props.put(ResourceHandler.PROTOCOL, resource.getProtocol());
+ props.put(ResourceHandler.PORT, Integer.toString(resource.getPort()));
+ // add the custom resource properties
+ if (resourceProperties != null) {
+ Enumeration<String> properyKeysEnum = resourceProperties.keys();
+ while (properyKeysEnum.hasMoreElements()) {
+ String key = properyKeysEnum.nextElement();
+ if (!key.equals(ResourceHandler.HOST) &&
+ !key.equals(ResourceHandler.PATH) &&
+ !key.equals(ResourceHandler.PROTOCOL) &&
+ !key.equals(ResourceHandler.PORT)) {
+ props.put(key, resourceProperties.get(key).toString());
+ } else {
+ m_component.getLogger().warn(
+ "Custom resource property is overlapping with the default resource property for key: %s",
+ key);
+ }
+ }
+ }
+ return props;
+ }
+ }
+ else {
+ throw new IllegalStateException("cannot find resource");
+ }
+ }
+
+ @Override
+ public String getName() {
+ StringBuilder sb = new StringBuilder();
+ if (m_trackedResource != null) {
+ sb.append(m_trackedResource.toString());
+ }
+ if (m_resourceFilter != null) {
+ sb.append(m_resourceFilter);
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String getSimpleName() {
+ return m_trackedResource != null ? m_trackedResource.toString() : null;
+ }
+
+ @Override
+ public String getFilter() {
+ return m_resourceFilter;
+ }
+
+ @Override
+ public String getType() {
+ return "resource";
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceEventImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceEventImpl.java
new file mode 100644
index 0000000..b655bed
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ResourceEventImpl.java
@@ -0,0 +1,87 @@
+/*
+ * 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.dm.impl;
+
+import java.net.URL;
+import java.util.Dictionary;
+
+import org.apache.felix.dm.context.Event;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ResourceEventImpl extends Event {
+ final Dictionary<Object, Object> m_resourceProperties;
+
+ @SuppressWarnings("unchecked")
+ public ResourceEventImpl(URL resource, Dictionary<?, ?> resourceProperties) {
+ super(resource);
+ m_resourceProperties = (Dictionary<Object, Object>) resourceProperties;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <K,V> Dictionary<K,V> getProperties() {
+ return (Dictionary<K, V>) ((Dictionary<K,V>) m_resourceProperties == null ? EMPTY_PROPERTIES : m_resourceProperties);
+ }
+
+ public URL getResource() {
+ return getEvent();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ResourceEventImpl) {
+ ResourceEventImpl r1 = this;
+ ResourceEventImpl r2 = (ResourceEventImpl) obj;
+ boolean match = r1.getResource().equals(r2.getResource());
+ if (match) {
+ Dictionary<?,?> d1 = getProperties();
+ Dictionary<?,?> d2 = r2.getProperties();
+
+ if (d1 == null) {
+ return d2 == null ? match : false;
+ }
+ else {
+ return d1.equals(d2);
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + getResource().hashCode();
+ result = prime * result + ((getProperties() == null) ? 0 : getProperties().hashCode());
+ return result;
+ }
+
+ @Override
+ public int compareTo(Event that) {
+ if (this.equals(that)) {
+ return 0;
+ }
+
+ // Sort by resource name.
+ return getResource().toString().compareTo(((ResourceEventImpl) that).getResource().toString());
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/SerialExecutor.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/SerialExecutor.java
new file mode 100644
index 0000000..9ef6c9f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/SerialExecutor.java
@@ -0,0 +1,153 @@
+/*
+ * 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.dm.impl;
+
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.felix.dm.Logger;
+import org.osgi.service.log.LogService;
+
+/**
+ * Allows you to enqueue tasks from multiple threads and then execute
+ * them on one thread sequentially. It assumes no more than one thread will
+ * try to execute the tasks and it will make an effort to pick the first
+ * task that comes along whilst making sure subsequent tasks return
+ * without waiting. <p>
+ *
+ * This class is <b>lock free</b> by design and ensures <b>"safe object publication"</b> between scheduling threads and
+ * actual executing thread: if one thread T1 schedules a task, but another thread T2 actually
+ * executes it, then all the objects from the T1 thread will be "safely published" to the executing T2 thread.
+ * Safe publication is ensured because we are using a ConcurrentLinkedQueue.
+ * (see [1], chapter 3.5.3 (Safe publication idioms).
+ *
+ * [1] Java Concurrency In Practice, Addison Wesley
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class SerialExecutor implements Executor {
+ /**
+ * All tasks scheduled are stored there and only one thread may run them.
+ **/
+ protected final ConcurrentLinkedQueue<Runnable> m_tasks = new ConcurrentLinkedQueue<Runnable>();
+
+ /**
+ * Thread currently executing the task queue.
+ **/
+ protected final AtomicReference<Thread> m_runningThread = new AtomicReference<>();
+
+ /**
+ * Logger used when a task execution throws an exception
+ **/
+ private final Logger m_logger;
+
+ /**
+ * Makes a new SerialExecutor
+ * @param logger used when a task execution throws an exception. Can be null if no exception should be logger.
+ */
+ public SerialExecutor(Logger logger) {
+ m_logger = logger;
+ }
+
+ /**
+ * Enqueues a task for later execution. You must call {@link #execute()} in order
+ * to trigger the task execution, which may or may not be executed by
+ * your current thread.
+ */
+ public void schedule(Runnable task) {
+ m_tasks.add(task); // No need to synchronize, m_tasks is a concurrent linked queue.
+ }
+
+ /**
+ * Executes any pending tasks, enqueued using the {@link SerialExecutor#schedule(Runnable)} method.
+ * This method is thread safe, so multiple threads can try to execute the pending
+ * tasks, but only the first will be used to actually do so. Other threads will return immediately.
+ */
+ public void execute() {
+ Thread currentThread = Thread.currentThread();
+ if (m_runningThread.compareAndSet(null, currentThread)) {
+ runTasks(currentThread);
+ }
+ }
+
+ /**
+ * Schedules a task for execution, and then attempts to execute it. This method is thread safe, so
+ * multiple threads can try to execute a task but only the first will be executed, other threads will
+ * return immediately, and the first thread will execute the tasks scheduled by the other threads.<p>
+ * <p>
+ * This method is reentrant: if the current thread is currently being executed by this executor, then
+ * the task passed to this method will be executed immediately, from the current invoking thread
+ * (inline execution).
+ */
+ public void execute(Runnable task) {
+ Thread currentThread = Thread.currentThread();
+ if (m_runningThread.get() == currentThread) {
+ runTask(task);
+ } else {
+ schedule(task);
+ execute();
+ }
+ }
+
+ /**
+ * Run all pending tasks
+ * @param currentRunninghread the current executing thread
+ */
+ private void runTasks(Thread currentRunninghread) {
+ do {
+ try {
+ Runnable task;
+ ConcurrentLinkedQueue<Runnable> tasks = m_tasks;
+
+ while ((task = tasks.poll()) != null) {
+ runTask(task);
+ }
+ }
+ finally {
+ m_runningThread.set(null);
+ }
+ }
+ // We must test again if some tasks have been scheduled after our "while" loop above, but before the
+ // m_runningThread reference has been reset to null.
+ while (!m_tasks.isEmpty() && m_runningThread.compareAndSet(null, currentRunninghread));
+ }
+
+ /**
+ * Run a given task.
+ * @param task the task to execute.
+ */
+ void runTask(Runnable command) {
+ try {
+ command.run();
+ }
+ catch (Throwable t) {
+ if (m_logger != null) {
+ m_logger.log(LogService.LOG_ERROR, "Error processing tasks", t);
+ } else {
+ t.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "[Executor: queue size: " + m_tasks.size() + "]";
+ }
+}
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceDependencyImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceDependencyImpl.java
new file mode 100644
index 0000000..ab4daf4
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceDependencyImpl.java
@@ -0,0 +1,554 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Proxy;
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentDeclaration;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.context.AbstractDependency;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.context.EventType;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceDependencyImpl extends AbstractDependency<ServiceDependency> implements ServiceDependency, ServiceTrackerCustomizer {
+ protected volatile ServiceTracker m_tracker;
+ protected String m_swap;
+ protected volatile Class<?> m_trackedServiceName;
+ private volatile String m_trackedServiceFilter;
+ private volatile String m_trackedServiceFilterUnmodified;
+ private volatile ServiceReference m_trackedServiceReference;
+ private volatile Object m_defaultImplementation;
+ private volatile Object m_defaultImplementationInstance;
+ private volatile Object m_nullObject;
+ private boolean m_debug = false;
+ private String m_debugKey;
+ private long m_trackedServiceReferenceId;
+
+ public ServiceDependency setDebug(String debugKey) {
+ m_debugKey = debugKey;
+ m_debug = true;
+ return this;
+ }
+
+ /**
+ * Entry to wrap service properties behind a Map.
+ */
+ private static final class ServicePropertiesMapEntry implements Map.Entry<String, Object> {
+ private final String m_key;
+ private Object m_value;
+
+ public ServicePropertiesMapEntry(String key, Object value) {
+ m_key = key;
+ m_value = value;
+ }
+
+ public String getKey() {
+ return m_key;
+ }
+
+ public Object getValue() {
+ return m_value;
+ }
+
+ public String toString() {
+ return m_key + "=" + m_value;
+ }
+
+ public Object setValue(Object value) {
+ Object oldValue = m_value;
+ m_value = value;
+ return oldValue;
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object o) {
+ if (!(o instanceof Map.Entry)) {
+ return false;
+ }
+ Map.Entry<String, Object> e = (Map.Entry<String, Object>) o;
+ return eq(m_key, e.getKey()) && eq(m_value, e.getValue());
+ }
+
+ public int hashCode() {
+ return ((m_key == null) ? 0 : m_key.hashCode()) ^ ((m_value == null) ? 0 : m_value.hashCode());
+ }
+
+ private static final boolean eq(Object o1, Object o2) {
+ return (o1 == null ? o2 == null : o1.equals(o2));
+ }
+ }
+
+ /**
+ * Wraps service properties behind a Map.
+ */
+ private final static class ServicePropertiesMap extends AbstractMap<String, Object> {
+ private final ServiceReference m_ref;
+
+ public ServicePropertiesMap(ServiceReference ref) {
+ m_ref = ref;
+ }
+
+ public Object get(Object key) {
+ return m_ref.getProperty(key.toString());
+ }
+
+ public int size() {
+ return m_ref.getPropertyKeys().length;
+ }
+
+ public Set<Map.Entry<String, Object>> entrySet() {
+ Set<Map.Entry<String, Object>> set = new HashSet<>();
+ String[] keys = m_ref.getPropertyKeys();
+ for (int i = 0; i < keys.length; i++) {
+ set.add(new ServicePropertiesMapEntry(keys[i], m_ref.getProperty(keys[i])));
+ }
+ return set;
+ }
+ }
+
+ public ServiceDependencyImpl() {
+ }
+
+ public ServiceDependencyImpl(ServiceDependencyImpl prototype) {
+ super(prototype);
+ m_trackedServiceName = prototype.m_trackedServiceName;
+ m_nullObject = prototype.m_nullObject;
+ m_trackedServiceFilter = prototype.m_trackedServiceFilter;
+ m_trackedServiceFilterUnmodified = prototype.m_trackedServiceFilterUnmodified;
+ m_trackedServiceReference = prototype.m_trackedServiceReference;
+ m_autoConfigInstance = prototype.m_autoConfigInstance;
+ m_defaultImplementation = prototype.m_defaultImplementation;
+ m_autoConfig = prototype.m_autoConfig;
+ }
+
+ // --- CREATION
+
+ public ServiceDependency setCallbacks(Object instance, String added, String changed, String removed, String swapped) {
+ setCallbacks(instance, added, changed, removed);
+ m_swap = swapped;
+ return this;
+ }
+
+ public ServiceDependency setCallbacks(String added, String changed, String removed, String swapped) {
+ setCallbacks(added, changed, removed);
+ m_swap = swapped;
+ return this;
+ }
+
+ @Override
+ public ServiceDependency setDefaultImplementation(Object implementation) {
+ ensureNotActive();
+ m_defaultImplementation = implementation;
+ return this;
+ }
+
+ @Override
+ public ServiceDependency setService(Class<?> serviceName) {
+ setService(serviceName, null, null);
+ return this;
+ }
+
+ public ServiceDependency setService(Class<?> serviceName, String serviceFilter) {
+ setService(serviceName, null, serviceFilter);
+ return this;
+ }
+
+ public ServiceDependency setService(String serviceFilter) {
+ if (serviceFilter == null) {
+ throw new IllegalArgumentException("Service filter cannot be null.");
+ }
+ setService(null, null, serviceFilter);
+ return this;
+ }
+
+ public ServiceDependency setService(Class<?> serviceName, ServiceReference serviceReference) {
+ setService(serviceName, serviceReference, null);
+ return this;
+ }
+
+ @Override
+ public void start() {
+ if (m_trackedServiceName != null) {
+ BundleContext ctx = m_component.getBundleContext();
+ if (m_trackedServiceFilter != null) {
+ try {
+ m_tracker = new ServiceTracker(ctx, ctx.createFilter(m_trackedServiceFilter), this);
+ } catch (InvalidSyntaxException e) {
+ throw new IllegalStateException("Invalid filter definition for dependency: "
+ + m_trackedServiceFilter);
+ }
+ } else if (m_trackedServiceReference != null) {
+ m_tracker = new ServiceTracker(ctx, m_trackedServiceReference, this);
+ } else {
+ m_tracker = new ServiceTracker(ctx, m_trackedServiceName.getName(), this);
+ }
+ } else {
+ throw new IllegalStateException("Could not create tracker for dependency, no service name specified.");
+ }
+ if (m_debug) {
+ m_tracker.setDebug(m_debugKey);
+ }
+ m_tracker.open();
+ super.start();
+ }
+
+ @Override
+ public void stop() {
+ m_tracker.close();
+ m_tracker = null;
+ super.stop();
+ }
+
+ @Override
+ public Object addingService(ServiceReference reference) {
+ try {
+ return m_component.getBundleContext().getService(reference);
+ } catch (IllegalStateException e) {
+ // most likely our bundle is being stopped. Only log an exception if our component is enabled.
+ if (m_component.isActive()) {
+ m_component.getLogger().warn("could not handle service dependency for component %s", e,
+ m_component.getComponentDeclaration().getClassName());
+ }
+ return null;
+ }
+ }
+
+ @Override
+ public void addedService(ServiceReference reference, Object service) {
+ if (m_debug) {
+ System.out.println(m_debugKey + " addedService: ref=" + reference + ", service=" + service);
+ }
+ m_component.handleEvent(this, EventType.ADDED,
+ new ServiceEventImpl(m_component.getBundle(), m_component.getBundleContext(), reference, service));
+ }
+
+ @Override
+ public void modifiedService(ServiceReference reference, Object service) {
+ m_component.handleEvent(this, EventType.CHANGED,
+ new ServiceEventImpl(m_component.getBundle(), m_component.getBundleContext(), reference, service));
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service) {
+ m_component.handleEvent(this, EventType.REMOVED,
+ new ServiceEventImpl(m_component.getBundle(), m_component.getBundleContext(), reference, service));
+ }
+
+ @Override
+ public void invokeCallback(EventType type, Event ... events) {
+ switch (type) {
+ case ADDED:
+ if (m_add != null) {
+ invoke (m_add, events[0], getInstances());
+ }
+ break;
+ case CHANGED:
+ if (m_change != null) {
+ invoke (m_change, events[0], getInstances());
+ }
+ break;
+ case REMOVED:
+ if (m_remove != null) {
+ invoke (m_remove, events[0], getInstances());
+ }
+ break;
+ case SWAPPED:
+ if (m_swap != null) {
+ ServiceEventImpl oldE = (ServiceEventImpl) events[0];
+ ServiceEventImpl newE = (ServiceEventImpl) events[1];
+ invokeSwap(m_swap, oldE.getReference(), oldE.getEvent(), newE.getReference(), newE.getEvent(),
+ getInstances());
+ }
+ break;
+ }
+ }
+
+ @Override
+ public Class<?> getAutoConfigType() {
+ return m_trackedServiceName;
+ }
+
+ @Override
+ public DependencyContext createCopy() {
+ return new ServiceDependencyImpl(this);
+ }
+
+ @Override
+ public String getName() {
+ StringBuilder sb = new StringBuilder();
+ if (m_trackedServiceName != null) {
+ sb.append(m_trackedServiceName.getName());
+ if (m_trackedServiceFilterUnmodified != null) {
+ sb.append(' ');
+ sb.append(m_trackedServiceFilterUnmodified);
+ }
+ }
+ if (m_trackedServiceReference != null) {
+ sb.append("{service.id=" + m_trackedServiceReference.getProperty(Constants.SERVICE_ID) + "}");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String getSimpleName() {
+ if (m_trackedServiceName != null) {
+ return m_trackedServiceName.getName();
+ }
+ return null;
+ }
+
+ @Override
+ public String getFilter() {
+ if (m_trackedServiceFilterUnmodified != null) {
+ return m_trackedServiceFilterUnmodified;
+ } else if (m_trackedServiceReference != null) {
+ return new StringBuilder("(").append(Constants.SERVICE_ID).append("=").append(
+ String.valueOf(m_trackedServiceReferenceId)).append(")").toString();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public String getType() {
+ return "service";
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ ServiceEventImpl se = (ServiceEventImpl) m_component.getDependencyEvent(this);
+ if (se != null) {
+ if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) {
+ try {
+ return (Dictionary<String, Object>) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod,
+ new Class[][]{{ServiceReference.class, Object.class}, {ServiceReference.class}}, new Object[][]{
+ {se.getReference(), se.getEvent()}, {se.getReference()}});
+ } catch (InvocationTargetException e) {
+ m_component.getLogger().warn("Exception while invoking callback method", e.getCause());
+ } catch (Throwable e) {
+ m_component.getLogger().warn("Exception while trying to invoke callback method", e);
+ }
+ throw new IllegalStateException("Could not invoke callback");
+ } else {
+ Hashtable<String, Object> props = new Hashtable<>();
+ String[] keys = se.getReference().getPropertyKeys();
+ for (int i = 0; i < keys.length; i++) {
+ if (!(keys[i].equals(Constants.SERVICE_ID) || keys[i].equals(Constants.SERVICE_PID))) {
+ props.put(keys[i], se.getReference().getProperty(keys[i]));
+ }
+ }
+ return props;
+ }
+ } else {
+ throw new IllegalStateException("cannot find service reference");
+ }
+ }
+
+ /** Internal method to set the name, service reference and/or filter. */
+ private void setService(Class<?> serviceName, ServiceReference serviceReference, String serviceFilter) {
+ ensureNotActive();
+ if (serviceName == null) {
+ m_trackedServiceName = Object.class;
+ }
+ else {
+ m_trackedServiceName = serviceName;
+ }
+ if (serviceFilter != null) {
+ m_trackedServiceFilterUnmodified = serviceFilter;
+ if (serviceName == null) {
+ m_trackedServiceFilter = serviceFilter;
+ }
+ else {
+ m_trackedServiceFilter = "(&(" + Constants.OBJECTCLASS + "=" + serviceName.getName() + ")"
+ + serviceFilter + ")";
+ }
+ }
+ else {
+ m_trackedServiceFilterUnmodified = null;
+ m_trackedServiceFilter = null;
+ }
+ if (serviceReference != null) {
+ m_trackedServiceReference = serviceReference;
+ if (serviceFilter != null) {
+ throw new IllegalArgumentException("Cannot specify both a filter and a service reference.");
+ }
+ m_trackedServiceReferenceId = (Long) m_trackedServiceReference.getProperty(Constants.SERVICE_ID);
+ }
+ else {
+ m_trackedServiceReference = null;
+ }
+ }
+
+ @Override
+ public Object getDefaultService(boolean nullObject) {
+ Object service = null;
+ if (isAutoConfig()) {
+ service = getDefaultImplementation();
+ if (service == null && nullObject) {
+ service = getNullObject();
+ }
+ }
+ return service;
+ }
+
+ private Object getNullObject() {
+ if (m_nullObject == null) {
+ Class<?> trackedServiceName;
+ trackedServiceName = m_trackedServiceName;
+ try {
+ m_nullObject = Proxy.newProxyInstance(trackedServiceName.getClassLoader(),
+ new Class[] { trackedServiceName }, new DefaultNullObject());
+ }
+ catch (Throwable err) {
+ m_component.getLogger().err("Could not create null object for %s.", err, trackedServiceName);
+ }
+ }
+ return m_nullObject;
+ }
+
+ private Object getDefaultImplementation() {
+ if (m_defaultImplementation != null) {
+ if (m_defaultImplementation instanceof Class) {
+ try {
+ m_defaultImplementationInstance = ((Class<?>) m_defaultImplementation).newInstance();
+ }
+ catch (Throwable e) {
+ m_component.getLogger().err("Could not create default implementation instance of class %s.", e,
+ m_defaultImplementation);
+ }
+ }
+ else {
+ m_defaultImplementationInstance = m_defaultImplementation;
+ }
+ }
+ return m_defaultImplementationInstance;
+ }
+
+ public void invoke(String method, Event e, Object[] instances) {
+ ServiceEventImpl se = (ServiceEventImpl) e;
+ ServicePropertiesMap propertiesMap = new ServicePropertiesMap(se.getReference());
+ Dictionary<?,?> properties = se.getProperties();
+ m_component.invokeCallbackMethod(instances, method,
+ new Class[][]{
+ {Component.class, ServiceReference.class, m_trackedServiceName},
+ {Component.class, ServiceReference.class, Object.class},
+ {Component.class, ServiceReference.class},
+ {Component.class, m_trackedServiceName},
+ {Component.class, Object.class},
+ {Component.class},
+ {Component.class, Map.class, m_trackedServiceName},
+ {ServiceReference.class, m_trackedServiceName},
+ {ServiceReference.class, Object.class},
+ {ServiceReference.class},
+ {m_trackedServiceName},
+ {m_trackedServiceName, Map.class},
+ {Map.class, m_trackedServiceName},
+ {m_trackedServiceName, Dictionary.class},
+ {Dictionary.class, m_trackedServiceName},
+ {Object.class},
+ {}},
+
+ new Object[][]{
+ {m_component, se.getReference(), se.getEvent()},
+ {m_component, se.getReference(), se.getEvent()},
+ {m_component, se.getReference()},
+ {m_component, se.getEvent()},
+ {m_component, se.getEvent()},
+ {m_component},
+ {m_component, propertiesMap, se.getEvent()},
+ {se.getReference(), se.getEvent()},
+ {se.getReference(), se.getEvent()},
+ {se.getReference()},
+ {se.getEvent()},
+ {se.getEvent(), propertiesMap},
+ {propertiesMap, se.getEvent()},
+ {se.getEvent(), properties},
+ {properties, se.getEvent()},
+ {se.getEvent()},
+ {}}
+ );
+ }
+
+ public void invokeSwap(String swapMethod, ServiceReference previousReference, Object previous,
+ ServiceReference currentReference, Object current, Object[] instances) {
+ if (m_debug) {
+ System.out.println("invoke swap: " + swapMethod + " on component " + m_component + ", instances: " + Arrays.toString(instances) + " - " + ((ComponentDeclaration)m_component).getState());
+ }
+ try {
+ m_component.invokeCallbackMethod(instances, swapMethod,
+ new Class[][]{
+ {m_trackedServiceName, m_trackedServiceName},
+ {Object.class, Object.class},
+ {ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName},
+ {ServiceReference.class, Object.class, ServiceReference.class, Object.class},
+ {Component.class, m_trackedServiceName, m_trackedServiceName},
+ {Component.class, Object.class, Object.class},
+ {Component.class, ServiceReference.class, m_trackedServiceName, ServiceReference.class, m_trackedServiceName},
+ {Component.class, ServiceReference.class, Object.class, ServiceReference.class, Object.class}},
+
+ new Object[][]{
+ {previous, current},
+ {previous, current},
+ {previousReference, previous, currentReference, current},
+ {previousReference, previous, currentReference, current}, {m_component, previous, current},
+ {m_component, previous, current}, {m_component, previousReference, previous, currentReference, current},
+ {m_component, previousReference, previous, currentReference, current}}
+ );
+ } catch (Throwable e) {
+ m_component.getLogger().err("Could not invoke swap callback", e);
+ }
+ }
+
+ @Override
+ public void swappedService(final ServiceReference reference, final Object service,
+ final ServiceReference newReference, final Object newService) {
+ if (m_swap != null) {
+ // it will not trigger a state change, but the actual swap should be scheduled to prevent things
+ // getting out of order.
+ // We delegate the swap handling to the ComponentImpl, which is the class responsible for state management.
+ // The ComponentImpl will first check if the component is in the proper state so the swap method can be invoked.
+ m_component.handleEvent(this, EventType.SWAPPED,
+ new ServiceEventImpl(m_component.getBundle(), m_component.getBundleContext(), reference, service),
+ new ServiceEventImpl(m_component.getBundle(), m_component.getBundleContext(), newReference, newService));
+ } else {
+ addedService(newReference, newService);
+ removedService(reference, service);
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceEventImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceEventImpl.java
new file mode 100644
index 0000000..a27147d
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceEventImpl.java
@@ -0,0 +1,124 @@
+/*
+ * 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.dm.impl;
+
+import java.util.Dictionary;
+
+import org.apache.felix.dm.context.Event;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceEventImpl extends Event {
+ /**
+ * The service reference on which a service dependency depends on
+ */
+ private final ServiceReference m_reference;
+
+ /**
+ * The bundle context of the bundle which has created the service dependency. If not null,
+ * will be used in close method when ugetting the service reference of the dependency.
+ */
+ private final BundleContext m_bundleContext;
+
+ /**
+ * The bundle which has created the service dependency. If not null, will be used to ensure that the bundle is still active before
+ * ungetting the service reference of the dependency. (ungetting a service reference on a bundle which is not
+ * active triggers an exception, and this may degrade performance, especially when doing some benchmarks).
+ */
+ private final Bundle m_bundle;
+
+ public ServiceEventImpl(ServiceReference reference, Object service) {
+ this(null, null, reference, service);
+ }
+
+ public ServiceEventImpl(Bundle bundle, BundleContext bundleContext, ServiceReference reference, Object service) {
+ super(service);
+ m_bundle = bundle;
+ m_bundleContext = bundleContext;
+ m_reference = reference;
+ }
+
+ /**
+ * Returns the bundle which has declared a service dependency.
+ */
+ public Bundle getBundle() {
+ return m_bundle;
+ }
+
+ /**
+ * Returns the context of the bundle which has declared a service dependency.
+ */
+ public BundleContext getBundleContext() {
+ return m_bundleContext;
+ }
+
+ /**
+ * Returns the reference service dependency.
+ */
+ public ServiceReference getReference() {
+ return m_reference;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Dictionary<String, Object> getProperties() {
+ return ServiceUtil.propertiesToDictionary(m_reference);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ServiceEventImpl) {
+ return getReference().equals(((ServiceEventImpl) obj).getReference());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return getReference().hashCode();
+ }
+
+ @Override
+ public int compareTo(Event b) {
+ return getReference().compareTo(((ServiceEventImpl) b).getReference());
+ }
+
+ @Override
+ public String toString() {
+ return getEvent().toString();
+ }
+
+ @Override
+ public void close() {
+ if (m_bundleContext != null) {
+ try {
+ // Optimization: don't call ungetService if the bundle referring to the service is not active.
+ // This optim is important when doing benchmarks where the referring bundle is being stopped
+ // while some dependencies are lost concurrently (here we want to avoid having many exception thrown).
+ if (m_bundle == null || m_bundle.getState() == Bundle.ACTIVE) {
+ m_bundleContext.ungetService(m_reference);
+ }
+ } catch (IllegalStateException e) {}
+ }
+ }
+}
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceRegistrationImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceRegistrationImpl.java
new file mode 100644
index 0000000..4604acb
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceRegistrationImpl.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.dm.impl;
+
+import java.util.Dictionary;
+
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * A wrapper around a service registration that blocks until the
+ * service registration is available.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public final class ServiceRegistrationImpl implements ServiceRegistration {
+ public static final ServiceRegistrationImpl ILLEGAL_STATE = new ServiceRegistrationImpl();
+ private volatile ServiceRegistration m_registration;
+
+ public ServiceRegistrationImpl() {
+ m_registration = null;
+ }
+
+ public ServiceReference getReference() {
+ return ensureRegistration().getReference();
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void setProperties(Dictionary dictionary) {
+ ensureRegistration().setProperties(dictionary);
+ }
+
+ public void unregister() {
+ ensureRegistration().unregister();
+ }
+
+ public boolean equals(Object obj) {
+ return ensureRegistration().equals(obj);
+ }
+
+ public int hashCode() {
+ return ensureRegistration().hashCode();
+ }
+
+ public String toString() {
+ return ensureRegistration().toString();
+ }
+
+ private synchronized ServiceRegistration ensureRegistration() {
+ while (m_registration == null) {
+ try {
+ wait();
+ }
+ catch (InterruptedException ie) {
+ // we were interrupted so hopefully we will now have a
+ // service registration ready; if not we wait again
+ }
+ }
+ // check if we're in an illegal state and throw an exception
+ if (ILLEGAL_STATE == m_registration) {
+ throw new IllegalStateException("Service is not registered.");
+ }
+ return m_registration;
+ }
+
+ /**
+ * Sets the service registration and notifies all waiting parties.
+ */
+ void setServiceRegistration(ServiceRegistration registration) {
+ synchronized (this) {
+ m_registration = registration;
+ notifyAll();
+ }
+ }
+
+ /**
+ * Sets this wrapper to an illegal state, which will cause all threads
+ * that are waiting for this service registration to fail.
+ */
+ void setIllegalState() {
+ setServiceRegistration(ServiceRegistrationImpl.ILLEGAL_STATE);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceUtil.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceUtil.java
new file mode 100644
index 0000000..cb7bfb6
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/ServiceUtil.java
@@ -0,0 +1,246 @@
+/*
+ * 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.dm.impl;
+
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * OSGi service utilities.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceUtil {
+ /**
+ * Useful when needing to provide empty service properties.
+ */
+ public final static Dictionary<String, Object> EMPTY_PROPERTIES = new Hashtable<>();
+
+ /**
+ * Returns the service ranking of a service, based on its service reference. If
+ * the service has a property specifying its ranking, that will be returned. If
+ * not, the default ranking of zero will be returned.
+ *
+ * @param ref the service reference to determine the ranking for
+ * @return the ranking
+ */
+ public static int getRanking(ServiceReference ref) {
+ return getRankingAsInteger(ref).intValue();
+ }
+
+ /**
+ * Returns the service ranking of a service, based on its service reference. If
+ * the service has a property specifying its ranking, that will be returned. If
+ * not, the default ranking of zero will be returned.
+ *
+ * @param ref the service reference to determine the ranking for
+ * @return the ranking
+ */
+ public static Integer getRankingAsInteger(ServiceReference ref) {
+ Integer rank = (Integer) ref.getProperty(Constants.SERVICE_RANKING);
+ if (rank != null) {
+ return rank;
+ }
+ return new Integer(0);
+ }
+
+ /**
+ * Returns the service ID of a service, based on its service reference. This
+ * method is aware of service aspects as defined by the dependency manager and
+ * will return the ID of the orginal service if you give it an aspect.
+ *
+ * @param ref the service reference to determine the service ID of
+ * @return the service ID
+ */
+ public static long getServiceId(ServiceReference ref) {
+ return getServiceIdAsLong(ref).longValue();
+ }
+
+ /**
+ * Returns the service ID of a service, based on its service reference. This
+ * method is aware of service aspects as defined by the dependency manager and
+ * will return the ID of the orginal service if you give it an aspect.
+ *
+ * @param ref the service reference to determine the service ID of
+ * @return the service ID
+ */
+ public static Long getServiceIdAsLong(ServiceReference ref) {
+ return getServiceIdObject(ref);
+ }
+
+ public static Long getServiceIdObject(ServiceReference ref) {
+ Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+ if (aid != null) {
+ return aid;
+ }
+ Long sid = (Long) ref.getProperty(Constants.SERVICE_ID);
+ if (sid != null) {
+ return sid;
+ }
+ throw new IllegalArgumentException("Invalid service reference, no service ID found");
+ }
+
+ /**
+ * Determines if the service is an aspect as defined by the dependency manager.
+ * Aspects are defined by a property and this method will check for its presence.
+ *
+ * @param ref the service reference
+ * @return <code>true</code> if it's an aspect, <code>false</code> otherwise
+ */
+ public static boolean isAspect(ServiceReference ref) {
+ Long aid = (Long) ref.getProperty(DependencyManager.ASPECT);
+ return (aid != null);
+ }
+
+ /**
+ * Converts a service reference to a string, listing both the bundle it was
+ * registered from and all properties.
+ *
+ * @param ref the service reference
+ * @return a string representation of the service
+ */
+ public static String toString(ServiceReference ref) {
+ if (ref == null) {
+ return "ServiceReference[null]";
+ }
+ else {
+ StringBuffer buf = new StringBuffer();
+ Bundle bundle = ref.getBundle();
+ if (bundle != null) {
+ buf.append("ServiceReference[");
+ buf.append(bundle.getBundleId());
+ buf.append("]{");
+ }
+ else {
+ buf.append("ServiceReference[unregistered]{");
+ }
+ buf.append(propertiesToString(ref, null));
+ buf.append("}");
+ return buf.toString();
+ }
+ }
+
+ /**
+ * Converts the properties of a service reference to a string.
+ *
+ * @param ref the service reference
+ * @param exclude a list of properties to exclude, or <code>null</code> to show everything
+ * @return a string representation of the service properties
+ */
+ public static String propertiesToString(ServiceReference ref, List<String> exclude) {
+ StringBuffer buf = new StringBuffer();
+ String[] keys = ref.getPropertyKeys();
+ for (int i = 0; i < keys.length; i++) {
+ if (i > 0) {
+ buf.append(',');
+ }
+ buf.append(keys[i]);
+ buf.append('=');
+ Object val = ref.getProperty(keys[i]);
+ if (exclude == null || !exclude.contains(val)) {
+ if (val instanceof String[]) {
+ String[] valArray = (String[]) val;
+ StringBuffer valBuf = new StringBuffer();
+ valBuf.append('{');
+ for (int j = 0; j < valArray.length; j++) {
+ if (valBuf.length() > 1) {
+ valBuf.append(',');
+ }
+ valBuf.append(valArray[j].toString());
+ }
+ valBuf.append('}');
+ buf.append(valBuf);
+ }
+ else {
+ buf.append(val.toString());
+ }
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Wraps ServiceReference properties behind a Dictionary object.
+ * @param ref the ServiceReference to wrap
+ * @return a new Dictionary used to wrap the ServiceReference properties
+ */
+ public static Dictionary<String, Object> propertiesToDictionary(final ServiceReference ref) {
+ return new Dictionary<String, Object>() {
+ private Dictionary<String, Object> m_wrapper;
+
+ @Override
+ public int size() {
+ return getWrapper().size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return getWrapper().isEmpty();
+ }
+
+ @Override
+ public Enumeration<String> keys() {
+ return getWrapper().keys();
+ }
+
+ @Override
+ public Enumeration<Object> elements() {
+ return getWrapper().elements();
+ }
+
+ @Override
+ public Object get(Object key) {
+ return ref.getProperty(key.toString());
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ throw new UnsupportedOperationException("Unmodified Dictionary.");
+ }
+
+ @Override
+ public Object remove(Object key) {
+ throw new UnsupportedOperationException("Unmodified Dictionary.");
+ }
+
+ @Override
+ public String toString() {
+ return getWrapper().toString();
+ }
+
+ private synchronized Dictionary<String, Object> getWrapper() {
+ if (m_wrapper == null) {
+ m_wrapper = new Hashtable<String, Object>();
+ String[] keys = ref.getPropertyKeys();
+ for (String key : keys) {
+ m_wrapper.put(key, ref.getProperty(key));
+ }
+ }
+ return m_wrapper;
+ }
+ };
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/TemporalServiceDependencyImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/TemporalServiceDependencyImpl.java
new file mode 100644
index 0000000..c329307
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/TemporalServiceDependencyImpl.java
@@ -0,0 +1,169 @@
+/*
+ * 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.dm.impl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.ServiceDependency;
+import org.apache.felix.dm.context.EventType;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * Temporal Service dependency implementation, used to hide temporary service dependency "outage".
+ * Only works with a required dependency.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class TemporalServiceDependencyImpl extends ServiceDependencyImpl implements ServiceDependency, InvocationHandler {
+ // Max millis to wait for service availability.
+ private final long m_timeout;
+
+ // Framework bundle (we use it to detect if the framework is stopping)
+ private final Bundle m_frameworkBundle;
+
+ // The service proxy, which blocks when service is not available.
+ private volatile Object m_serviceInstance;
+
+ /**
+ * Creates a new Temporal Service Dependency.
+ *
+ * @param context The bundle context of the bundle which is instantiating this dependency object
+ * @param logger the logger our Internal logger for logging events.
+ * @see DependencyActivatorBase#createTemporalServiceDependency()
+ */
+ public TemporalServiceDependencyImpl(BundleContext context, long timeout) {
+ super.setRequired(true);
+ if (timeout < 0) {
+ throw new IllegalArgumentException("Invalid timeout value: " + timeout);
+ }
+ m_timeout = timeout;
+ m_frameworkBundle = context.getBundle(0);
+ }
+
+ /**
+ * Sets the required flag which determines if this service is required or not. This method
+ * just override the superclass method in order to check if the required flag is true
+ * (optional dependency is not supported by this class).
+ *
+ * @param required the required flag, which must be set to true
+ * @return this service dependency
+ * @throws IllegalArgumentException if the "required" parameter is not true.
+ */
+ @Override
+ public ServiceDependency setRequired(boolean required) {
+ if (! required) {
+ throw new IllegalArgumentException("A Temporal Service dependency can't be optional");
+ }
+ super.setRequired(required);
+ return this;
+ }
+
+ /**
+ * The ServiceTracker calls us here in order to inform about a service arrival.
+ */
+ @Override
+ public void addedService(ServiceReference ref, Object service) {
+ // Update our service cache, using the tracker. We do this because the
+ // just added service might not be the service with the highest rank ...
+ boolean makeAvailable = false;
+ synchronized (this) {
+ if (m_serviceInstance == null) {
+ m_serviceInstance = Proxy.newProxyInstance(m_trackedServiceName.getClassLoader(), new Class[] { m_trackedServiceName }, this);
+ makeAvailable = true;
+ }
+ }
+ if (makeAvailable) {
+ getComponentContext().handleEvent(this, EventType.ADDED,
+ new ServiceEventImpl(m_component.getBundle(), m_component.getBundleContext(), ref, m_serviceInstance));
+ } else {
+ // This added will possibly unblock our invoke() method (if it's blocked in m_tracker.waitForService method).
+ }
+ }
+
+ /**
+ * The ServiceTracker calls us here when a tracked service properties are modified.
+ */
+ @Override
+ public void modifiedService(ServiceReference ref, Object service) {
+ // We don't care.
+ }
+
+ /**
+ * The ServiceTracker calls us here when a tracked service is lost.
+ */
+ @Override
+ public void removedService(ServiceReference ref, Object service) {
+ // If we detect that the fwk is stopping, we behave as our superclass. That is:
+ // the lost dependency has to trigger our service deactivation, since the fwk is stopping
+ // and the lost dependency won't come up anymore.
+ if (m_frameworkBundle.getState() == Bundle.STOPPING) {
+ // Important: Notice that calling "super.removedService() might invoke our service "stop"
+ // callback, which in turn might invoke the just removed service dependency. In this case,
+ // our "invoke" method won't use the tracker to get the service dependency (because at this point,
+ // the tracker has withdrawn its reference to the lost service). So, you will see that the "invoke"
+ // method will use the "m_cachedService" instead ...
+ boolean makeUnavailable = false;
+ synchronized (this) {
+ if (m_tracker.getService() == null) {
+ makeUnavailable = true;
+ }
+ }
+ if (makeUnavailable) {
+ // the event.close method will unget the service.
+ m_component.handleEvent(this, EventType.REMOVED, new ServiceEventImpl(m_component.getBundle(),
+ m_component.getBundleContext(), ref, m_serviceInstance));
+ }
+ } else {
+ // Unget what we got in addingService (see ServiceTracker 701.4.1)
+ m_component.getBundleContext().ungetService(ref);
+ // if there is no available services, the next call to invoke() method will block until another service
+ // becomes available. Else the next call to invoke() will return that highest ranked available service.
+ }
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Object service = null;
+ try {
+ service = m_tracker.waitForService(m_timeout);
+ } catch (InterruptedException e) {
+ }
+
+ if (service == null) {
+ throw new IllegalStateException("Service unavailable: " + m_trackedServiceName.getName());
+ }
+
+ try {
+ try {
+ return method.invoke(service, args);
+ } catch (IllegalAccessException iae) {
+ method.setAccessible(true);
+ return method.invoke(service, args);
+ }
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AbstractFactoryFilterIndex.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AbstractFactoryFilterIndex.java
new file mode 100644
index 0000000..1e72f31
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AbstractFactoryFilterIndex.java
@@ -0,0 +1,101 @@
+/*
+ * 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.dm.impl.index;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.apache.felix.dm.impl.ServiceUtil;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class AbstractFactoryFilterIndex {
+ protected final Map<Long, SortedSet<ServiceReference>> m_sidToServiceReferencesMap = new HashMap<>();
+ protected final Map <ServiceListener, String> m_listenerToFilterMap = new HashMap<>();
+
+ public void addedService(ServiceReference reference, Object service) {
+ add(reference);
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ modify(reference);
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ remove(reference);
+ }
+
+ public void swappedService(ServiceReference reference, Object service,
+ ServiceReference newReference, Object newService) {
+ addedService(newReference, newService);
+ removedService(reference, service);
+ }
+
+ public void add(ServiceReference reference) {
+ Long sid = ServiceUtil.getServiceIdObject(reference);
+ synchronized (m_sidToServiceReferencesMap) {
+ SortedSet<ServiceReference> list = m_sidToServiceReferencesMap.get(sid);
+ if (list == null) {
+ list = new TreeSet<ServiceReference>();
+ m_sidToServiceReferencesMap.put(sid, list);
+ }
+ list.add(reference);
+ }
+ }
+
+ public void modify(ServiceReference reference) {
+ remove(reference);
+ add(reference);
+ }
+
+ public void remove(ServiceReference reference) {
+ Long sid = ServiceUtil.getServiceIdObject(reference);
+ synchronized (m_sidToServiceReferencesMap) {
+ SortedSet<ServiceReference> list = m_sidToServiceReferencesMap.get(sid);
+ if (list != null) {
+ list.remove(reference);
+ }
+ }
+ }
+
+ protected boolean referenceMatchesObjectClass(ServiceReference ref, String objectClass) {
+ boolean matches = false;
+ Object value = ref.getProperty(Constants.OBJECTCLASS);
+ matches = Arrays.asList((String[])value).contains(objectClass);
+ return matches;
+ }
+
+ /** Structure to hold internal filter data. */
+ protected static class FilterData {
+ public long m_serviceId;
+ public String m_objectClass;
+ public int m_ranking;
+
+ public String toString() {
+ return "FilterData [serviceId=" + m_serviceId + "]";
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AdapterFilterIndex.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AdapterFilterIndex.java
new file mode 100644
index 0000000..74699b9
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AdapterFilterIndex.java
@@ -0,0 +1,223 @@
+/*
+ * 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.dm.impl.index;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.impl.ServiceUtil;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AdapterFilterIndex extends AbstractFactoryFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
+ // (&(objectClass=foo.Bar)(|(service.id=18233)(org.apache.felix.dependencymanager.aspect=18233)))
+ private static final String FILTER_REGEXP = "\\(&\\(" + Constants.OBJECTCLASS + "=([a-zA-Z\\.\\$0-9]*)\\)\\(\\|\\("
+ + Constants.SERVICE_ID + "=([0-9]*)\\)\\("
+ + DependencyManager.ASPECT + "=([0-9]*)\\)\\)\\)";
+ private static final Pattern PATTERN = Pattern.compile(FILTER_REGEXP);
+ private final Object m_lock = new Object();
+ private ServiceTracker m_tracker;
+ private BundleContext m_context;
+ private final Map<Object, List<ServiceListener>> m_sidToListenersMap = new HashMap<>();
+ protected final Map<ServiceListener, String> m_listenerToObjectClassMap = new HashMap<>();
+
+ public void open(BundleContext context) {
+ synchronized (m_lock) {
+ if (m_context != null) {
+ throw new IllegalStateException("Filter already open.");
+ }
+ try {
+ m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
+ }
+ catch (InvalidSyntaxException e) {
+ throw new Error();
+ }
+ m_context = context;
+ }
+ m_tracker.open(true, true);
+ }
+
+ public void close() {
+ ServiceTracker tracker;
+ synchronized (m_lock) {
+ if (m_context == null) {
+ throw new IllegalStateException("Filter already closed.");
+ }
+ tracker = m_tracker;
+ m_tracker = null;
+ m_context = null;
+ }
+ tracker.close();
+ }
+
+ public boolean isApplicable(String clazz, String filter) {
+ return getFilterData(clazz, filter) != null;
+ }
+
+ /** Returns a value object with the relevant filter data, or <code>null</code> if this filter was not valid. */
+ private FilterData getFilterData(String clazz, String filter) {
+ // something like:
+ // (&(objectClass=foo.Bar)(|(service.id=18233)(org.apache.felix.dependencymanager.aspect=18233)))
+ FilterData resultData = null;
+ if (filter != null) {
+ Matcher matcher = PATTERN.matcher(filter);
+ if (matcher.matches()) {
+ String sid = matcher.group(2);
+ String sid2 = matcher.group(3);
+ if (sid.equals(sid2)) {
+ resultData = new FilterData();
+ resultData.m_serviceId = Long.parseLong(sid);
+ }
+ }
+ }
+ return resultData;
+ }
+
+ public List<ServiceReference> getAllServiceReferences(String clazz, String filter) {
+ List<ServiceReference> result = new ArrayList<>();
+ Matcher matcher = PATTERN.matcher(filter);
+ if (matcher.matches()) {
+ FilterData data = getFilterData(clazz, filter);
+ if (data != null) {
+ SortedSet<ServiceReference> list = null;
+ synchronized (m_sidToServiceReferencesMap) {
+ list = m_sidToServiceReferencesMap.get(Long.valueOf(data.m_serviceId));
+ if (list != null) {
+ Iterator<ServiceReference> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ ServiceReference ref = (ServiceReference) iterator.next();
+ String objectClass = matcher.group(1);
+ if (referenceMatchesObjectClass(ref, objectClass)) {
+ result.add(ref);
+ }
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ ServiceReference reference = event.getServiceReference();
+ Long sid = ServiceUtil.getServiceIdObject(reference);
+ List<ServiceListener> notificationList = new ArrayList<>();
+ synchronized (m_sidToListenersMap) {
+ List<ServiceListener> list = m_sidToListenersMap.get(sid);
+ if (list != null) {
+ Iterator<ServiceListener> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ ServiceListener listener = (ServiceListener) iterator.next();
+ String objectClass = m_listenerToObjectClassMap.get(listener);
+ if (referenceMatchesObjectClass(reference, objectClass)) {
+ notificationList.add(listener);
+ }
+ }
+ }
+ }
+ // notify
+ Iterator<ServiceListener> iterator = notificationList.iterator();
+ while (iterator.hasNext()) {
+ ServiceListener listener = (ServiceListener) iterator.next();
+ listener.serviceChanged(event);
+ }
+ }
+
+ public void addServiceListener(ServiceListener listener, String filter) {
+ FilterData data = getFilterData(null, filter);
+ if (data != null) {
+ Long sidObject = Long.valueOf(data.m_serviceId);
+ synchronized (m_sidToListenersMap) {
+ List<ServiceListener> listeners = m_sidToListenersMap.get(sidObject);
+ if (listeners == null) {
+ listeners = new ArrayList<>();
+ m_sidToListenersMap.put(sidObject, listeners);
+ }
+ listeners.add(listener);
+ m_listenerToFilterMap.put(listener, filter);
+ Matcher matcher = PATTERN.matcher(filter);
+ if (matcher.matches()) {
+ String objectClass = matcher.group(1);
+ m_listenerToObjectClassMap.put(listener, objectClass);
+ } else {
+ throw new IllegalArgumentException("Filter string does not match index pattern");
+ }
+
+ }
+ }
+ }
+
+ public void removeServiceListener(ServiceListener listener) {
+ synchronized (m_sidToListenersMap) {
+ m_listenerToObjectClassMap.remove(listener);
+ String filter = (String) m_listenerToFilterMap.remove(listener);
+ if (filter != null) {
+ // the listener does exist
+ FilterData data = getFilterData(null, filter);
+ if (data != null) {
+ Long sidObject = Long.valueOf(data.m_serviceId);
+ List<ServiceListener> listeners = m_sidToListenersMap.get(sidObject);
+ if (listeners != null) {
+ listeners.remove(listener);
+ }
+ }
+ }
+ }
+ }
+
+ public Object addingService(ServiceReference reference) {
+ BundleContext context;
+ synchronized (m_lock) {
+ context = m_context;
+ }
+ if (context != null) {
+ return context.getService(reference);
+ }
+ else {
+ throw new IllegalStateException("No valid bundle context.");
+ }
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("AdapterFilterIndex[");
+ sb.append("S2L: " + m_sidToListenersMap.size());
+ sb.append(", S2SR: " + m_sidToServiceReferencesMap.size());
+ sb.append(", L2F: " + m_listenerToFilterMap.size());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AspectFilterIndex.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AspectFilterIndex.java
new file mode 100644
index 0000000..f14e884
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/AspectFilterIndex.java
@@ -0,0 +1,265 @@
+/*
+ * 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.dm.impl.index;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.impl.ServiceUtil;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AspectFilterIndex extends AbstractFactoryFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
+ // (&(objectClass=foo.Bar)(|(!(service.ranking=*))(service.ranking<=99))(|(service.id=4451)(org.apache.felix.dependencymanager.aspect=4451)))
+ private static final String FILTER_START = "(&(" + Constants.OBJECTCLASS + "=";
+ private static final String FILTER_SUBSTRING_0 = ")(&(|(!(" + Constants.SERVICE_RANKING + "=*))(" + Constants.SERVICE_RANKING + "<=";
+ private static final String FILTER_SUBSTRING_1 = "))(|(" + Constants.SERVICE_ID + "=";
+ private static final String FILTER_SUBSTRING_2 = ")(" + DependencyManager.ASPECT + "=";
+ private static final String FILTER_END = "))))";
+ private final Object m_lock = new Object();
+ private ServiceTracker m_tracker;
+ private BundleContext m_context;
+
+ private final Map<Long, Map<String, SortedMap<Integer, List<ServiceListener>>>> m_sidToObjectClassToRankingToListenersMap = new HashMap<>();
+
+ public void open(BundleContext context) {
+ synchronized (m_lock) {
+ if (m_context != null) {
+ throw new IllegalStateException("Filter already open.");
+ }
+ try {
+ m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
+ }
+ catch (InvalidSyntaxException e) {
+ throw new Error();
+ }
+ m_context = context;
+ }
+ m_tracker.open(true, true);
+ }
+
+ public void close() {
+ ServiceTracker tracker;
+ synchronized (m_lock) {
+ if (m_context == null) {
+ throw new IllegalStateException("Filter already closed.");
+ }
+ tracker = m_tracker;
+ m_tracker = null;
+ m_context = null;
+ }
+ tracker.close();
+ }
+
+ public boolean isApplicable(String clazz, String filter) {
+ return getFilterData(clazz, filter) != null;
+ }
+
+ /** Returns a value object with the relevant filter data, or <code>null</code> if this filter was not valid. */
+ private FilterData getFilterData(String clazz, String filter) {
+ // something like:
+ // (&(objectClass=foo.Bar)(&(|(!(service.ranking=*))(service.ranking<=9))(|(service.id=37)(org.apache.felix.dependencymanager.aspect=37))))
+ if ((filter != null)
+ && (filter.startsWith(FILTER_START)) // (&(objectClass=
+ && (filter.endsWith(FILTER_END)) // ))))
+ ) {
+ int i0 = filter.indexOf(FILTER_SUBSTRING_0);
+ if (i0 == -1) {
+ return null;
+ }
+ int i1 = filter.indexOf(FILTER_SUBSTRING_1);
+ if (i1 == -1 || i1 <= i0) {
+ return null;
+ }
+ int i2 = filter.indexOf(FILTER_SUBSTRING_2);
+ if (i2 == -1 || i2 <= i1) {
+ return null;
+ }
+ long sid = Long.parseLong(filter.substring(i1 + FILTER_SUBSTRING_1.length(), i2));
+ long sid2 = Long.parseLong(filter.substring(i2 + FILTER_SUBSTRING_2.length(), filter.length() - FILTER_END.length()));
+ if (sid != sid2) {
+ return null;
+ }
+ FilterData result = new FilterData();
+ result.m_objectClass = filter.substring(FILTER_START.length(), i0);
+ result.m_serviceId = sid;
+ result.m_ranking = Integer.parseInt(filter.substring(i0 + FILTER_SUBSTRING_0.length(), i1));
+ return result;
+ }
+ return null;
+ }
+
+ public List<ServiceReference> getAllServiceReferences(String clazz, String filter) {
+ List<ServiceReference> result = new ArrayList<>();
+ FilterData data = getFilterData(clazz, filter);
+ if (data != null) {
+ SortedSet<ServiceReference> list = null;
+ synchronized (m_sidToServiceReferencesMap) {
+ list = m_sidToServiceReferencesMap.get(Long.valueOf(data.m_serviceId));
+ if (list != null) {
+ Iterator<ServiceReference> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ ServiceReference reference = (ServiceReference) iterator.next();
+ if (referenceMatchesObjectClass(reference, data.m_objectClass) && ServiceUtil.getRanking(reference) <= data.m_ranking) {
+ result.add(reference);
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ List<ServiceListener> list = new ArrayList<>();
+ ServiceReference reference = event.getServiceReference();
+ Long sidObject = ServiceUtil.getServiceIdObject(reference);
+ int ranking = ServiceUtil.getRanking(reference);
+ String[] objectClasses = (String[]) reference.getProperty(Constants.OBJECTCLASS);
+
+ synchronized (m_sidToObjectClassToRankingToListenersMap) {
+ for (int i = 0; i < objectClasses.length; i++) {
+ // handle each of the object classes separately since aspects only work on one object class at a time
+ String objectClass = objectClasses[i];
+ Map<String, SortedMap<Integer, List<ServiceListener>>> objectClassToRankingToListenersMap = m_sidToObjectClassToRankingToListenersMap.get(sidObject);
+ if (objectClassToRankingToListenersMap != null) {
+ SortedMap<Integer, List<ServiceListener>> rankingToListenersMap = objectClassToRankingToListenersMap.get(objectClass);
+ if (rankingToListenersMap != null) {
+ Iterator<Entry<Integer, List<ServiceListener>>> iterator = rankingToListenersMap.entrySet().iterator();
+ while (iterator.hasNext()) {
+ Entry<Integer, List<ServiceListener>> entry = iterator.next();
+ if (ranking <= ((Integer) entry.getKey()).intValue()) {
+ list.addAll(entry.getValue());
+ }
+ }
+ }
+ }
+ }
+ }
+ Iterator<ServiceListener> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ ServiceListener listener = iterator.next();
+ listener.serviceChanged(event);
+ }
+ }
+
+ public void addServiceListener(ServiceListener listener, String filter) {
+ FilterData data = getFilterData(null, filter);
+ if (data != null) {
+ Long sidObject = Long.valueOf(data.m_serviceId);
+ synchronized (m_sidToObjectClassToRankingToListenersMap) {
+ Map<String, SortedMap<Integer, List<ServiceListener>>> objectClassToRankingToListenersMap = m_sidToObjectClassToRankingToListenersMap.get(sidObject);
+ if (objectClassToRankingToListenersMap == null) {
+ objectClassToRankingToListenersMap = new TreeMap<>();
+ m_sidToObjectClassToRankingToListenersMap.put(sidObject, objectClassToRankingToListenersMap);
+ }
+
+ SortedMap<Integer, List<ServiceListener>> rankingToListenersMap = objectClassToRankingToListenersMap.get(data.m_objectClass);
+ if (rankingToListenersMap == null) {
+ rankingToListenersMap = new TreeMap<>();
+ objectClassToRankingToListenersMap.put(data.m_objectClass, rankingToListenersMap);
+ }
+
+ List<ServiceListener> listeners = rankingToListenersMap.get(Integer.valueOf(data.m_ranking));
+ if (listeners == null) {
+ listeners = new ArrayList<>();
+ rankingToListenersMap.put(Integer.valueOf(data.m_ranking), listeners);
+ }
+
+ listeners.add(listener);
+ m_listenerToFilterMap.put(listener, filter);
+ }
+ }
+ }
+
+ public void removeServiceListener(ServiceListener listener) {
+ synchronized (m_sidToObjectClassToRankingToListenersMap) {
+ String filter = (String) m_listenerToFilterMap.remove(listener);
+ if (filter != null) {
+ // the listener does exist
+ FilterData data = getFilterData(null, filter);
+ if (data != null) {
+ // this index is applicable
+ Long sidObject = Long.valueOf(data.m_serviceId);
+ Map<String, SortedMap<Integer, List<ServiceListener>>> objectClassToRankingToListenersMap = m_sidToObjectClassToRankingToListenersMap.get(sidObject);
+ if (objectClassToRankingToListenersMap != null) {
+ SortedMap<Integer, List<ServiceListener>> rankingToListenersMap = objectClassToRankingToListenersMap.get(data.m_objectClass);
+ if (rankingToListenersMap != null) {
+ List<ServiceListener> listeners = rankingToListenersMap.get(Integer.valueOf(data.m_ranking));
+ if (listeners != null) {
+ listeners.remove(listener);
+ }
+ // cleanup
+ if (listeners != null && listeners.isEmpty()) {
+ rankingToListenersMap.remove(Integer.valueOf(data.m_ranking));
+ }
+ if (rankingToListenersMap.isEmpty()) {
+ objectClassToRankingToListenersMap.remove(data.m_objectClass);
+ }
+ if (objectClassToRankingToListenersMap.isEmpty()) {
+ m_sidToObjectClassToRankingToListenersMap.remove(sidObject);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public Object addingService(ServiceReference reference) {
+ BundleContext context;
+ synchronized (m_lock) {
+ context = m_context;
+ }
+ if (context != null) {
+ return context.getService(reference);
+ }
+ else {
+ throw new IllegalStateException("No valid bundle context.");
+ }
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("AspectFilterIndex[");
+ sb.append("S2R2L: " + m_sidToObjectClassToRankingToListenersMap.size());
+ sb.append(", S2SR: " + m_sidToServiceReferencesMap.size());
+ sb.append(", L2F: " + m_listenerToFilterMap.size());
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/BundleContextInterceptor.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
new file mode 100644
index 0000000..11cf401
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/BundleContextInterceptor.java
@@ -0,0 +1,162 @@
+/*
+ * 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.dm.impl.index;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.Logger;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class BundleContextInterceptor extends BundleContextInterceptorBase {
+ protected static final String INDEX_LOG_TRESHOLD = "org.apache.felix.dm.index.log.treshold";
+ private final ServiceRegistryCache m_cache;
+ private final boolean m_perfmon;
+ private Logger m_logger;
+ private long m_threshold;
+
+ public BundleContextInterceptor(ServiceRegistryCache cache, BundleContext context) {
+ super(context);
+ m_cache = cache;
+ m_perfmon = context.getProperty(INDEX_LOG_TRESHOLD) != null;
+ if (m_perfmon) {
+ m_threshold = Long.parseLong(context.getProperty(INDEX_LOG_TRESHOLD));
+ m_logger = new Logger(context);
+ }
+ }
+
+ public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
+ FilterIndex filterIndex = m_cache.hasFilterIndexFor(null, filter);
+ if (filterIndex != null) {
+ filterIndex.addServiceListener(listener, filter);
+ }
+ else {
+ m_context.addServiceListener(listener, filter);
+ }
+ }
+
+ public void addServiceListener(ServiceListener listener) {
+ FilterIndex filterIndex = m_cache.hasFilterIndexFor(null, null);
+ if (filterIndex != null) {
+ filterIndex.addServiceListener(listener, null);
+ }
+ else {
+ m_context.addServiceListener(listener);
+ }
+ }
+
+ public void removeServiceListener(ServiceListener listener) {
+ // remove servicelistener. although it would be prettier to find the correct filterindex first it's
+ // probaby faster to do a brute force removal.
+ Iterator<FilterIndex> filterIndexIterator = m_cache.getFilterIndices().iterator();
+ while (filterIndexIterator.hasNext()) {
+ filterIndexIterator.next().removeServiceListener(listener);
+ }
+ m_context.removeServiceListener(listener);
+ }
+
+ public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+ long start = 0L;
+ if (m_perfmon) {
+ start = System.currentTimeMillis();
+ }
+ // first we ask the cache if there is an index for our request (class and filter combination)
+ FilterIndex filterIndex = m_cache.hasFilterIndexFor(clazz, filter);
+ if (filterIndex != null) {
+ List<ServiceReference> result = filterIndex.getAllServiceReferences(clazz, filter);
+ Iterator<ServiceReference> iterator = result.iterator();
+ while (iterator.hasNext()) {
+ ServiceReference reference = iterator.next();
+ String[] list = (String[]) reference.getProperty(Constants.OBJECTCLASS);
+ for (int i = 0; i < list.length; i++) {
+ if (!reference.isAssignableTo(m_context.getBundle(), list[i])) {
+ iterator.remove();
+ break;
+ }
+ }
+ }
+ if (m_perfmon) {
+ long duration = System.currentTimeMillis() - start;
+ if (duration > m_threshold) {
+ m_logger.log(Logger.LOG_DEBUG, "Indexed filter exceeds lookup time threshold (" + duration + " ms): " + clazz + " " + filter);
+ }
+ }
+ if (result.size() == 0) {
+ return null;
+ }
+ return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
+ }
+ else {
+ // if they don't know, we ask the real bundle context instead
+ ServiceReference[] serviceReferences = m_context.getServiceReferences(clazz, filter);
+ if (m_perfmon) {
+ long duration = System.currentTimeMillis() - start;
+ if (duration > m_threshold) {
+ m_logger.log(Logger.LOG_DEBUG, "Unindexed filter exceeds lookup time threshold (" + duration + " ms): " + clazz + " " + filter);
+ }
+ }
+ return serviceReferences;
+ }
+ }
+
+ public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+ // first we ask the cache if there is an index for our request (class and filter combination)
+ FilterIndex filterIndex = m_cache.hasFilterIndexFor(clazz, filter);
+ if (filterIndex != null) {
+ List<ServiceReference> result = filterIndex.getAllServiceReferences(clazz, filter);
+ if (result == null || result.size() == 0) {
+ return null;
+ }
+ return (ServiceReference[]) result.toArray(new ServiceReference[result.size()]);
+ }
+ else {
+ // if they don't know, we ask the real bundle context instead
+ return m_context.getAllServiceReferences(clazz, filter);
+ }
+ }
+
+ public ServiceReference getServiceReference(String clazz) {
+ ServiceReference[] references;
+ try {
+ references = getServiceReferences(clazz, null);
+ if (references == null || references.length == 0) {
+ return null;
+ }
+ Arrays.sort(references);
+ return references[references.length - 1];
+ }
+ catch (InvalidSyntaxException e) {
+ throw new Error("Invalid filter syntax thrown for null filter.", e);
+ }
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ m_cache.serviceChangedForFilterIndices(event);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/BundleContextInterceptorBase.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/BundleContextInterceptorBase.java
new file mode 100644
index 0000000..498fd16
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/BundleContextInterceptorBase.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.dm.impl.index;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkListener;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * Base class for bundle context interceptors that keep track of service listeners and delegate incoming changes to them.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class BundleContextInterceptorBase implements BundleContext, ServiceListener {
+ protected final BundleContext m_context;
+ /** Keeps track of all service listeners and their optional filters. */
+ private final Map<ServiceListener, String> m_serviceListenerFilterMap = new HashMap<>();
+ private long m_currentVersion = 0;
+ private long m_entryVersion = -1;
+ private Entry<ServiceListener, String>[] m_serviceListenerFilterMapEntries;
+
+ public BundleContextInterceptorBase(BundleContext context) {
+ m_context = context;
+ }
+
+ public String getProperty(String key) {
+ return m_context.getProperty(key);
+ }
+
+ public Bundle getBundle() {
+ return m_context.getBundle();
+ }
+
+ public Bundle installBundle(String location) throws BundleException {
+ return m_context.installBundle(location);
+ }
+
+ public Bundle installBundle(String location, InputStream input) throws BundleException {
+ return m_context.installBundle(location, input);
+ }
+
+ public Bundle getBundle(long id) {
+ return m_context.getBundle(id);
+ }
+
+ public Bundle[] getBundles() {
+ return m_context.getBundles();
+ }
+
+ public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {
+ synchronized (m_serviceListenerFilterMap) {
+ m_serviceListenerFilterMap.put(listener, filter);
+ m_currentVersion++;
+ }
+ }
+
+ public void addServiceListener(ServiceListener listener) {
+ synchronized (m_serviceListenerFilterMap) {
+ m_serviceListenerFilterMap.put(listener, null);
+ m_currentVersion++;
+ }
+ }
+
+ public void removeServiceListener(ServiceListener listener) {
+ synchronized (m_serviceListenerFilterMap) {
+ m_serviceListenerFilterMap.remove(listener);
+ m_currentVersion++;
+ }
+ }
+
+ public void addBundleListener(BundleListener listener) {
+ m_context.addBundleListener(listener);
+ }
+
+ public void removeBundleListener(BundleListener listener) {
+ m_context.removeBundleListener(listener);
+ }
+
+ public void addFrameworkListener(FrameworkListener listener) {
+ m_context.addFrameworkListener(listener);
+ }
+
+ public void removeFrameworkListener(FrameworkListener listener) {
+ m_context.removeFrameworkListener(listener);
+ }
+
+ public ServiceRegistration registerService(String[] clazzes, Object service, @SuppressWarnings("rawtypes") Dictionary properties) {
+ return m_context.registerService(clazzes, service, properties);
+ }
+
+ public ServiceRegistration registerService(String clazz, Object service, @SuppressWarnings("rawtypes") Dictionary properties) {
+ return m_context.registerService(clazz, service, properties);
+ }
+
+ public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+ return m_context.getServiceReferences(clazz, filter);
+ }
+
+ public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException {
+ return m_context.getAllServiceReferences(clazz, filter);
+ }
+
+ public ServiceReference getServiceReference(String clazz) {
+ return m_context.getServiceReference(clazz);
+ }
+
+ public Object getService(ServiceReference reference) {
+ return m_context.getService(reference);
+ }
+
+ public boolean ungetService(ServiceReference reference) {
+ return m_context.ungetService(reference);
+ }
+
+ public File getDataFile(String filename) {
+ return m_context.getDataFile(filename);
+ }
+
+ public Filter createFilter(String filter) throws InvalidSyntaxException {
+ return m_context.createFilter(filter);
+ }
+
+ @SuppressWarnings("unchecked")
+ protected Entry<ServiceListener, String>[] synchronizeCollection() {
+ // lazy copy on write: we make a new copy only if writes have changed the collection
+ synchronized (m_serviceListenerFilterMap) {
+ if (m_currentVersion != m_entryVersion) {
+ m_serviceListenerFilterMapEntries = (Entry<ServiceListener, String>[]) m_serviceListenerFilterMap.entrySet().toArray(new Entry[m_serviceListenerFilterMap.size()]);
+ m_entryVersion = m_currentVersion;
+ }
+ }
+ return m_serviceListenerFilterMapEntries;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/FilterIndexBundleContext.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/FilterIndexBundleContext.java
new file mode 100644
index 0000000..9a6a266
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/FilterIndexBundleContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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.dm.impl.index;
+
+import java.util.Map.Entry;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class FilterIndexBundleContext extends BundleContextInterceptorBase {
+ public FilterIndexBundleContext(BundleContext context) {
+ super(context);
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ Entry<ServiceListener, String>[] entries = synchronizeCollection();
+ for (int i = 0; i < entries.length; i++) {
+ Entry<ServiceListener, String> serviceListenerFilterEntry = entries[i];
+ ServiceListener serviceListener = serviceListenerFilterEntry.getKey();
+ String filter = serviceListenerFilterEntry.getValue();
+ if (filter == null) {
+ serviceListener.serviceChanged(event);
+ }
+ else {
+ // call service changed on the listener if the filter matches the event
+ // TODO review if we can be smarter here
+ try {
+ if ("(objectClass=*)".equals(filter)) {
+ serviceListener.serviceChanged(event);
+ }
+ else {
+ if (m_context.createFilter(filter).match(event.getServiceReference())) {
+ serviceListener.serviceChanged(event);
+ }
+ }
+ }
+ catch (InvalidSyntaxException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/ServiceRegistryCache.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
new file mode 100644
index 0000000..8848e11
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/ServiceRegistryCache.java
@@ -0,0 +1,125 @@
+/*
+ * 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.dm.impl.index;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.felix.dm.FilterIndex;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ServiceRegistryCache implements ServiceListener/*, CommandProvider*/ {
+ private final List<FilterIndex> m_filterIndexList = new CopyOnWriteArrayList<>();
+ private final BundleContext m_context;
+ private final FilterIndexBundleContext m_filterIndexBundleContext;
+ private final Map<BundleContext, BundleContextInterceptor> m_bundleContextInterceptorMap = new HashMap<>();
+ private long m_currentVersion = 0;
+ private long m_arrayVersion = -1;
+
+ public ServiceRegistryCache(BundleContext context) {
+ m_context = context;
+ m_filterIndexBundleContext = new FilterIndexBundleContext(m_context);
+ }
+
+ public void open() {
+ m_context.addServiceListener(this);
+ }
+
+ public void close() {
+ m_context.removeServiceListener(this);
+ }
+
+ public void addFilterIndex(FilterIndex index) {
+ m_filterIndexList.add(index);
+ index.open(m_filterIndexBundleContext);
+ }
+
+ public void removeFilterIndex(FilterIndex index) {
+ index.close();
+ m_filterIndexList.remove(index);
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ // any incoming event is first dispatched to the list of filter indices
+ m_filterIndexBundleContext.serviceChanged(event);
+ // and then all the other listeners can access it
+ synchronized (m_bundleContextInterceptorMap) {
+ if (m_currentVersion != m_arrayVersion) {
+ // if our copy is out of date, we make a new one
+ m_arrayVersion = m_currentVersion;
+ }
+ }
+
+ serviceChangedForFilterIndices(event);
+ }
+
+ /** Creates an interceptor for a bundle context that uses our cache. */
+ public BundleContext createBundleContextInterceptor(BundleContext context) {
+ synchronized (m_bundleContextInterceptorMap) {
+ BundleContextInterceptor bundleContextInterceptor = m_bundleContextInterceptorMap.get(context);
+ if (bundleContextInterceptor == null) {
+ bundleContextInterceptor = new BundleContextInterceptor(this, context);
+ m_bundleContextInterceptorMap.put(context, bundleContextInterceptor);
+ m_currentVersion++;
+ // TODO figure out a good way to clean up bundle contexts that are no longer valid so they can be garbage collected
+ }
+ return bundleContextInterceptor;
+ }
+ }
+
+ public FilterIndex hasFilterIndexFor(String clazz, String filter) {
+ Iterator<FilterIndex> iterator = m_filterIndexList.iterator();
+ while (iterator.hasNext()) {
+ FilterIndex filterIndex = iterator.next();
+ if (filterIndex.isApplicable(clazz, filter)) {
+ return filterIndex;
+ }
+ }
+ return null;
+ }
+
+ public void serviceChangedForFilterIndices(ServiceEvent event) {
+ Iterator<FilterIndex> iterator = m_filterIndexList.iterator();
+ while (iterator.hasNext()) {
+ FilterIndex filterIndex = iterator.next();
+ filterIndex.serviceChanged(event);
+ }
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("ServiceRegistryCache[");
+ sb.append("FilterIndices: " + m_filterIndexList.size());
+ sb.append(", BundleContexts intercepted: " + m_bundleContextInterceptorMap.size());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ public List<FilterIndex> getFilterIndices() {
+ return m_filterIndexList;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java
new file mode 100644
index 0000000..602b4cc
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Filter.java
@@ -0,0 +1,147 @@
+/*
+ * 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.dm.impl.index.multiproperty;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Filter {
+ private boolean m_valid = true;
+ private Map<String, Property> m_properties = new HashMap<>();
+ private Set<String> m_propertyKeys = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+
+ private Filter() {
+ }
+
+ // Sample valid filter string (&(objectClass=OBJECTCLASS)(&(model=MODEL)(concept=CONCEPT)(role=ROLE)(!(context=*))))
+ public static Filter parse(String filterString) {
+ Filter filter = new Filter();
+ StringTokenizer tokenizer = new StringTokenizer(filterString, "(&|=)", true);
+
+ String token = null;
+ String prevToken = null;
+ String key = null;
+ StringBuilder valueBuilder = new StringBuilder();
+ boolean negate = false;
+
+ while (tokenizer.hasMoreTokens()) {
+ prevToken = token;
+ token = tokenizer.nextToken();
+ if (token.equals("|")) {
+ // we're not into OR's
+ filter.m_valid = false;
+ break;
+ }
+ if (token.equals("!")) {
+ negate = true;
+ } else if (token.equals("=")) {
+ key = prevToken.toLowerCase();
+ } else if (key != null) {
+ if (!token.equals(")")) {
+ valueBuilder.append(token); // might be superseded by a &
+ }
+ if (token.equals(")")) {
+ // set complete
+ if (filter.m_properties.containsKey(key)) {
+ // set current property to multivalue
+ Property property = filter.m_properties.get(key);
+ property.addValue(valueBuilder.toString(), negate);
+ } else {
+ Property property = new Property(negate, key, valueBuilder.toString());
+ filter.m_properties.put(key, property);
+ filter.m_propertyKeys.add(key);
+ }
+ negate = false;
+ key = null;
+ valueBuilder = new StringBuilder();
+ }
+ }
+ }
+ return filter;
+ }
+
+ public boolean containsProperty(String propertyKey) {
+ return m_properties.containsKey(propertyKey);
+ }
+
+ public Set<String> getPropertyKeys() {
+ return m_properties.keySet();
+ }
+
+ public Property getProperty(String key) {
+ return m_properties.get(key);
+ }
+
+ public boolean isValid() {
+ if (!m_valid) {
+ return m_valid;
+ } else {
+ // also check the properties
+ Iterator<Property> propertiesIterator = m_properties.values().iterator();
+ while (propertiesIterator.hasNext()) {
+ Property property = propertiesIterator.next();
+ if (!property.isValid()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static void main(String args[]) {
+ Filter parser = Filter.parse("(&(objectClass=OBJECTCLASS)(&(a=x)(a=n)(a=y)(b=y)(c=z)))");
+ System.out.println("key: " + parser.createKey());
+ }
+
+ protected String createKey() {
+ StringBuilder builder = new StringBuilder();
+ Iterator<String> keys = m_propertyKeys.iterator();
+
+ while (keys.hasNext()) {
+ String key = keys.next();
+ Property prop = m_properties.get(key);
+ if (!prop.isWildcard()) {
+ Iterator<String> values = prop.getValues().iterator();
+ while (values.hasNext()) {
+ String value = values.next();
+ builder.append(key);
+ builder.append("=");
+ builder.append(value);
+ if (keys.hasNext() || values.hasNext()) {
+ builder.append(";");
+ }
+ }
+ }
+ }
+ // strip the final ';' in case the last key was a wildcard property
+ if (builder.charAt(builder.length() - 1) == ';') {
+ return builder.toString().substring(0, builder.length() - 1);
+ } else {
+ return builder.toString();
+ }
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
new file mode 100644
index 0000000..42cb5b7
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/MultiPropertyFilterIndex.java
@@ -0,0 +1,492 @@
+/*
+ * 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.dm.impl.index.multiproperty;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.felix.dm.FilterIndex;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class MultiPropertyFilterIndex implements FilterIndex, ServiceTrackerCustomizer {
+
+ private final Object m_lock = new Object();
+ private ServiceTracker m_tracker;
+ private BundleContext m_context;
+ private Map<String, Property> m_configProperties = new LinkedHashMap<>();
+ private List<String> m_negatePropertyKeys = new ArrayList<>();
+ private final Map<String, List<ServiceReference>> m_keyToServiceReferencesMap = new HashMap<>();
+ private final Map<String, List<ServiceListener>> m_keyToListenersMap = new HashMap<>();
+ private final Map<ServiceListener, String> m_listenerToFilterMap = new HashMap<>();
+
+ public MultiPropertyFilterIndex(String configString) {
+ parseConfig(configString);
+ }
+
+ public boolean isApplicable(String clazz, String filterString) {
+ Filter filter = createFilter(clazz, filterString);
+
+ if (!filter.isValid()) {
+ return false;
+ }
+ // compare property keys to the ones in the configuration
+ Set<String> filterPropertyKeys = filter.getPropertyKeys();
+ if (m_configProperties.size() != filterPropertyKeys.size()) {
+ return false;
+ }
+ Iterator<String> filterPropertyKeysIterator = filterPropertyKeys.iterator();
+ while (filterPropertyKeysIterator.hasNext()) {
+ String filterPropertyKey = filterPropertyKeysIterator.next();
+ if (!m_configProperties.containsKey(filterPropertyKey)) {
+ return false;
+ } else if ((m_configProperties.get(filterPropertyKey)).isNegate() != filter.getProperty(filterPropertyKey).isNegate()) {
+ // negation should be equal
+ return false;
+ } else if (!filter.getProperty(filterPropertyKey).isNegate() && filter.getProperty(filterPropertyKey).getValue().equals("*")) {
+ // no wildcards without negation allowed
+ return false;
+ }
+ }
+ // our properties match so we're applicable
+ return true;
+ }
+
+ public boolean isApplicable(ServiceReference ref) {
+ String[] propertyKeys = ref.getPropertyKeys();
+ TreeSet<String> referenceProperties = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ for (int i = 0; i < propertyKeys.length; i++) {
+ referenceProperties.add(propertyKeys[i]);
+ }
+ Iterator<String> iterator = m_configProperties.keySet().iterator();
+ while (iterator.hasNext()) {
+ String item = iterator.next();
+ Property configProperty = m_configProperties.get(item);
+ if (!configProperty.isNegate() && !(referenceProperties.contains(item))) {
+ return false;
+ } else if (configProperty.isNegate() && referenceProperties.contains(item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void parseConfig(String configString) {
+ String[] propertyConfigs = configString.split(",");
+ for (int i = 0; i < propertyConfigs.length; i++) {
+ String propertyConfig = propertyConfigs[i];
+ Property property = new Property();
+ String key;
+ String value = null;
+ if (propertyConfig.startsWith("!")) {
+ property.setNegate(true);
+ key = propertyConfig.substring(1);
+ } else {
+ key = propertyConfig;
+ }
+ if (key.endsWith("*")) {
+ key = key.substring(0, key.indexOf("*"));
+ value = "*";
+ }
+ property.setKey(key.toLowerCase());
+ property.addValue(value, property.isNegate());
+ m_configProperties.put(key.toLowerCase(), property);
+ if (property.isNegate()) {
+ m_negatePropertyKeys.add(key);
+ }
+ }
+ }
+
+ protected Collection<Property> getProperties() {
+ return m_configProperties.values();
+ }
+
+ protected String createKeyFromFilter(String clazz, String filterString) {
+ return createFilter(clazz, filterString).createKey();
+ }
+
+ private Filter createFilter(String clazz, String filterString) {
+ String filterStringWithObjectClass = filterString;
+ if (clazz != null) {
+ if (filterString != null) {
+ if (!filterStringWithObjectClass.startsWith("(&(objectClass=")) {
+ filterStringWithObjectClass = "(&(objectClass=" + clazz + ")" + filterString + ")";
+ }
+ } else {
+ filterStringWithObjectClass = "(objectClass=" + clazz + ")";
+ }
+ }
+ Filter filter = Filter.parse(filterStringWithObjectClass);
+ return filter;
+ }
+
+ protected List<String> createKeys(ServiceReference reference) {
+ List<String> results = new ArrayList<>();
+ List<List<String>> sets = new ArrayList<>();
+ String[] keys = reference.getPropertyKeys();
+ Arrays.sort(keys, String.CASE_INSENSITIVE_ORDER);
+ for (int i = 0; i < keys.length; i++) {
+ List<String> set = new ArrayList<>();
+ String key = keys[i].toLowerCase();
+ if (m_configProperties.containsKey(key)) {
+ Object valueObject = reference.getProperty(key);
+ if (valueObject instanceof String[]) {
+ set.addAll(getPermutations(key, (String[]) valueObject));
+ } else {
+ set.add(toKey(key, valueObject));
+ }
+ sets.add(set);
+ }
+ }
+
+ List<List<String>> reversedSets = new ArrayList<>();
+ int size = sets.size();
+ for (int i = size - 1; i > -1; i--) {
+ reversedSets.add(sets.get(i));
+ }
+ List<List<String>> products = carthesianProduct(0, reversedSets);
+ // convert sets into strings
+ for (int i = 0; i < products.size(); i++) {
+ List<String> set = products.get(i);
+ StringBuilder b = new StringBuilder();
+ for (int j = 0; j < set.size(); j++) {
+ String item = set.get(j);
+ b.append(item);
+ if (j < set.size() - 1) {
+ b.append(";");
+ }
+ }
+ results.add(b.toString());
+ }
+
+ return results;
+ }
+
+ /**
+ * Note that we calculate the carthesian product for multi value properties. Use filters on these sparingly since memory
+ * consumption can get really high when multiple properties have a lot of values.
+ *
+ * @param index
+ * @param sets
+ * @return
+ */
+ private List<List<String>> carthesianProduct(int index, List<List<String>> sets) {
+ List<List<String>> result = new ArrayList<>();
+ if (index == sets.size()) {
+ result.add(new ArrayList<String>());
+ } else {
+ List<String> set = sets.get(index);
+ for (int i = 0; i < set.size(); i++) {
+ String object = set.get(i);
+ List<List<String>> pSets = carthesianProduct(index + 1, sets);
+ for (int j = 0; j < pSets.size(); j++) {
+ List<String> pSet = pSets.get(j);
+ pSet.add(object);
+ result.add(pSet);
+ }
+ }
+ }
+ return result;
+ }
+
+ List<String> getPermutations(String key, String[] values) {
+ List<String> results = new ArrayList<>();
+ Arrays.sort(values, String.CASE_INSENSITIVE_ORDER);
+ for (int v = 0; v < values.length; v++) {
+ String processValue = values[v];
+ List<String> items = new ArrayList<>();
+ items.add(processValue);
+ // per value get combinations
+ List<String> subItems = new ArrayList<>(items);
+ for (int w = v; w < values.length; w++) {
+ // make a copy of the current list
+ subItems = new ArrayList<>(subItems);
+ if (w != v) {
+ String value = values[w];
+ subItems.add(value);
+ }
+ results.add(toKey(key, subItems));
+ }
+ }
+ return results;
+ }
+
+ protected String toKey(String key, List<String> values) {
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < values.size(); i++) {
+ builder.append(toKey(key, values.get(i)));
+ if (i < values.size() - 1) {
+ builder.append(";");
+ }
+ }
+ return builder.toString();
+ }
+
+ protected String toKey(String key, Object value) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(key);
+ builder.append("=");
+ builder.append(value.toString());
+ return builder.toString();
+ }
+
+ public Object addingService(ServiceReference reference) {
+ BundleContext context;
+ synchronized (m_lock) {
+ context = m_context;
+ }
+ if (context != null) {
+ return context.getService(reference);
+ }
+ else {
+ throw new IllegalStateException("No valid bundle context.");
+ }
+ }
+
+ public void addedService(ServiceReference reference, Object service) {
+ if (isApplicable(reference) && shouldBeIndexed(reference)) {
+ handleServiceAdd(reference);
+ }
+ }
+
+ public void modifiedService(ServiceReference reference, Object service) {
+ if (isApplicable(reference)) {
+ handleServicePropertiesChange(reference);
+ }
+ }
+
+ public void removedService(ServiceReference reference, Object service) {
+ if (isApplicable(reference) && shouldBeIndexed(reference)) {
+ handleServiceRemove(reference);
+ }
+ }
+
+ protected void handleServiceAdd(ServiceReference reference) {
+ List<String> keys = createKeys(reference);
+ synchronized (m_keyToServiceReferencesMap) {
+ for (int i = 0; i < keys.size(); i++) {
+ List<ServiceReference> references = m_keyToServiceReferencesMap.get(keys.get(i));
+ if (references == null) {
+ references = new ArrayList<>();
+ m_keyToServiceReferencesMap.put(keys.get(i), references);
+ }
+ references.add(reference);
+ }
+ }
+ }
+
+ protected void handleServicePropertiesChange(ServiceReference reference) {
+
+ synchronized (m_keyToServiceReferencesMap) {
+ // TODO this is a quite expensive linear scan over the existing collection
+ // because we first need to remove any existing references and they can be
+ // all over the place :)
+ Iterator<List<ServiceReference>> iterator = m_keyToServiceReferencesMap.values().iterator();
+ while (iterator.hasNext()) {
+ List<ServiceReference> list = iterator.next();
+ if (list != null) {
+ Iterator<ServiceReference> i2 = list.iterator();
+ while (i2.hasNext()) {
+ ServiceReference ref = i2.next();
+ if (ref.equals(reference)) {
+ i2.remove();
+ }
+ }
+ }
+ }
+ // only re-add the reference when it is still applicable for this filter index
+ if (shouldBeIndexed(reference)) {
+ List<String> keys = createKeys(reference);
+ for (int i = 0; i < keys.size(); i++) {
+ List<ServiceReference> references = m_keyToServiceReferencesMap.get(keys.get(i));
+ if (references == null) {
+ references = new ArrayList<>();
+ m_keyToServiceReferencesMap.put(keys.get(i), references);
+ }
+ references.add(reference);
+ }
+ }
+ }
+ }
+
+ protected void handleServiceRemove(ServiceReference reference) {
+ List<String> keys = createKeys(reference);
+ synchronized (m_keyToServiceReferencesMap) {
+ for (int i = 0; i < keys.size(); i++) {
+ List<ServiceReference> references = m_keyToServiceReferencesMap.get(keys.get(i));
+ if (references != null) {
+ references.remove(reference);
+ if (references.isEmpty()) {
+ m_keyToServiceReferencesMap.remove(keys.get(i));
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean shouldBeIndexed(ServiceReference reference) {
+ // is already applicable, so we should only check whether there's a negate field in the filter which has a value in the reference
+ Iterator<String> negatePropertyKeyIterator = m_negatePropertyKeys.iterator();
+ while (negatePropertyKeyIterator.hasNext()) {
+ String negatePropertyKey = negatePropertyKeyIterator.next();
+ if (reference.getProperty(negatePropertyKey) != null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void open(BundleContext context) {
+ synchronized (m_lock) {
+ if (m_context != null) {
+ throw new IllegalStateException("Filter already open.");
+ }
+ try {
+ m_tracker = new ServiceTracker(context, context.createFilter("(" + Constants.OBJECTCLASS + "=*)"), this);
+ }
+ catch (InvalidSyntaxException e) {
+ throw new Error();
+ }
+ m_context = context;
+ }
+ m_tracker.open(true, true);
+ }
+
+ public void close() {
+ ServiceTracker tracker;
+ synchronized (m_lock) {
+ if (m_context == null) {
+ throw new IllegalStateException("Filter already closed.");
+ }
+ tracker = m_tracker;
+ m_tracker = null;
+ m_context = null;
+ }
+ tracker.close();
+ }
+
+ public List<ServiceReference> getAllServiceReferences(String clazz, String filter) {
+ List<ServiceReference> result = new ArrayList<>();
+ String key = createKeyFromFilter(clazz, filter);
+ synchronized (m_keyToServiceReferencesMap) {
+ List<ServiceReference> references = m_keyToServiceReferencesMap.get(key);
+ if (references != null) {
+ result.addAll(references);
+ }
+ }
+ return result;
+ }
+
+ public void serviceChanged(ServiceEvent event) {
+ if (isApplicable(event.getServiceReference())) {
+ List<String> keys = createKeys(event.getServiceReference());
+ List<ServiceListener> list = new ArrayList<ServiceListener>();
+ synchronized (m_keyToListenersMap) {
+ for (int i = 0; i < keys.size(); i++) {
+ String key = keys.get(i);
+ List<ServiceListener> listeners = m_keyToListenersMap.get(key);
+ if (listeners != null) {
+ list.addAll(listeners);
+ }
+ }
+ }
+ if (list != null) {
+ Iterator<ServiceListener> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ ServiceListener listener = iterator.next();
+ listener.serviceChanged(event);
+ }
+ }
+ }
+ }
+
+ public void addServiceListener(ServiceListener listener, String filter) {
+ String key = createKeyFromFilter(null, filter);
+ synchronized (m_keyToListenersMap) {
+ List<ServiceListener> listeners = m_keyToListenersMap.get(key);
+ if (listeners == null) {
+ listeners = new CopyOnWriteArrayList<ServiceListener>();
+ m_keyToListenersMap.put(key, listeners);
+ }
+ listeners.add(listener);
+ m_listenerToFilterMap.put(listener, filter);
+ }
+ }
+
+ public void removeServiceListener(ServiceListener listener) {
+ synchronized (m_keyToListenersMap) {
+ String filter = m_listenerToFilterMap.remove(listener);
+ if (filter != null) {
+ // the listener does exist
+ String key = createKeyFromFilter(null, filter);
+
+ boolean result = filter != null;
+ if (result) {
+ List<ServiceListener> listeners = m_keyToListenersMap.get(key);
+ if (listeners != null) {
+ listeners.remove(listener);
+ if (listeners.isEmpty()) {
+ m_keyToListenersMap.remove(key);
+ }
+ }
+ // TODO actually, if listeners == null that would be strange....
+ }
+ }
+ }
+ }
+
+ protected Collection<ServiceListener> getServiceListeners() {
+ return m_listenerToFilterMap.keySet();
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(" dMultiPropertyExactFilter[");
+ sb.append("K2L: " + m_keyToListenersMap.size());
+ sb.append(", K2SR: " + m_keyToServiceReferencesMap.size());
+ sb.append(", L2F: " + m_listenerToFilterMap.size());
+ sb.append("]");
+ return sb.toString();
+ }
+
+ @Override
+ public void swappedService(ServiceReference reference, Object service,
+ ServiceReference newReference, Object newService) {
+ addedService(newReference, newService);
+ removedService(reference, service);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Property.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Property.java
new file mode 100644
index 0000000..deb31c4
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/index/multiproperty/Property.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.dm.impl.index.multiproperty;
+
+import java.util.Set;
+import java.util.TreeSet;
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+
+public class Property {
+ boolean m_negate;
+ boolean m_valid = true;
+ String m_key;
+ String m_value;
+ Set<String> m_values = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+
+ public Property() {
+ }
+
+ public Property(boolean negate, String key, String value) {
+ super();
+ m_negate = negate;
+ m_key = key.toLowerCase();
+ m_values.add(value);
+ m_value = value;
+ }
+
+ public void setNegate(boolean negate) {
+ this.m_negate = negate;
+ }
+
+ public void setKey(String key) {
+ this.m_key = key.toLowerCase();
+ }
+
+ public void addValue(String value, boolean negate) {
+ if (this.m_negate != negate) {
+ // multiproperty with different negations, causes invalid configuration.
+ m_valid = false;
+ }
+ if (this.m_value == null) {
+ // value has not bee set yet
+ this.m_value = value;
+ }
+ if (value != null) {
+ m_values.add(value);
+ }
+ }
+
+ public boolean isNegate() {
+ return m_negate;
+ }
+
+ public String getKey() {
+ return m_key;
+ }
+
+ public String getValue() {
+ return m_value;
+ }
+
+ public Set<String> getValues() {
+ return m_values;
+ }
+
+ public boolean isWildcard() {
+ return "*".equals(m_value);
+ }
+
+ public boolean isMultiValue() {
+ return m_values.size() > 1;
+ }
+
+ public String toString() {
+ return "Property [negate=" + m_negate + ", key=" + m_key + ", values="
+ + m_values + "]";
+ }
+
+ public boolean isValid() {
+ return m_valid;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/AttributeDefinitionImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/AttributeDefinitionImpl.java
new file mode 100644
index 0000000..3a72c95
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/AttributeDefinitionImpl.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.dm.impl.metatype;
+
+import org.osgi.service.metatype.AttributeDefinition;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class AttributeDefinitionImpl implements AttributeDefinition {
+ private PropertyMetaDataImpl m_propertyMetaData;
+ private Resource m_resource;
+
+ public AttributeDefinitionImpl(PropertyMetaDataImpl propertyMetaData, Resource resource) {
+ m_propertyMetaData = propertyMetaData;
+ m_resource = resource;
+ }
+
+ public int getCardinality() {
+ return m_propertyMetaData.getCardinality();
+ }
+
+ public String[] getDefaultValue() {
+ return m_propertyMetaData.getDefaults();
+ }
+
+ public String getDescription() {
+ return m_resource.localize(m_propertyMetaData.getDescription());
+ }
+
+ public String getID() {
+ return m_propertyMetaData.getId();
+ }
+
+ public String getName() {
+ return m_resource.localize(m_propertyMetaData.getHeading());
+ }
+
+ public String[] getOptionLabels() {
+ String[] labels = m_propertyMetaData.getOptionLabels();
+ if (labels != null) {
+ for (int i = 0; i < labels.length; i++) {
+ labels[i] = m_resource.localize(labels[i]);
+ }
+ }
+ return labels;
+ }
+
+ public String[] getOptionValues() {
+ return m_propertyMetaData.getOptionValues();
+ }
+
+ public int getType() {
+ return m_propertyMetaData.getType();
+ }
+
+ public String validate(String value) {
+ return null;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/MetaTypeProviderImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/MetaTypeProviderImpl.java
new file mode 100644
index 0000000..d3bf2e2
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/MetaTypeProviderImpl.java
@@ -0,0 +1,275 @@
+/*
+ * 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.dm.impl.metatype;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.PropertyMetaData;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+import org.osgi.service.metatype.MetaTypeProvider;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * When a ConfigurationDepdendency is configured with properties metadata, we provide
+ * a specific ManagedService which also implements the MetaTypeProvider interface. This interface
+ * allows the MetaTypeService to retrieve our properties metadata, which will then be handled by webconsole.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class MetaTypeProviderImpl implements MetaTypeProvider, ManagedService, ManagedServiceFactory {
+ private ManagedService m_managedServiceDelegate;
+ private ManagedServiceFactory m_managedServiceFactoryDelegate;
+ private ArrayList<PropertyMetaData> m_propertiesMetaData = new ArrayList<>();
+ private String m_description;
+ private String m_heading;
+ private String m_localization;
+ private HashMap<String, Properties> m_localesProperties = new HashMap<>();
+ private Logger m_logger;
+ private BundleContext m_bctx;
+ private String m_pid;
+
+ public MetaTypeProviderImpl(String pid, BundleContext ctx, Logger logger, ManagedService msDelegate, ManagedServiceFactory msfDelegate) {
+ m_pid = pid;
+ m_bctx = ctx;
+ m_logger = logger;
+ m_managedServiceDelegate = msDelegate;
+ m_managedServiceFactoryDelegate = msfDelegate;
+ // Set the default localization file base name (see core specification, in section Localization on page 68).
+ // By default, this file can be stored in OSGI-INF/l10n/bundle.properties (and corresponding localized version
+ // in OSGI-INF/l10n/bundle_en_GB_welsh.properties, OSGI-INF/l10n/bundle_en_GB.properties, etc ...
+ // This default localization property file name can be overriden using the PropertyMetaData.setLocalization method.
+ m_localization = (String) m_bctx.getBundle().getHeaders().get(Constants.BUNDLE_LOCALIZATION);
+ if (m_localization == null) {
+ m_localization = Constants.BUNDLE_LOCALIZATION_DEFAULT_BASENAME;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public MetaTypeProviderImpl(MetaTypeProviderImpl prototype, ManagedService msDelegate, ManagedServiceFactory msfDelegate) {
+ m_pid = prototype.m_pid;
+ m_bctx = prototype.m_bctx;
+ m_logger = prototype.m_logger;
+ m_localization = prototype.m_localization;
+ m_propertiesMetaData = prototype.m_propertiesMetaData != null ? (ArrayList<PropertyMetaData>) prototype.m_propertiesMetaData.clone() : null;
+ m_description = prototype.m_description;
+ m_heading = prototype.m_heading;
+ m_localization = prototype.m_localization;
+ m_localesProperties = prototype.m_localesProperties != null ? (HashMap<String, Properties>) prototype.m_localesProperties.clone() : null;
+ m_managedServiceDelegate = msDelegate;
+ m_managedServiceFactoryDelegate = msfDelegate;
+ }
+
+
+ /**
+ * Registers the metatype information of a given configuration property
+ * @param property
+ */
+ public void add(PropertyMetaData property) {
+ m_propertiesMetaData.add(property);
+ }
+
+ /**
+ * A human readable description of the PID this annotation is associated with. Example: "Configuration for the PrinterService bundle".
+ * @return A human readable description of the PID this annotation is associated with (may be localized)
+ */
+ public void setDescription(String description) {
+ m_description = description;
+ }
+
+ /**
+ * The label used to display the tab name (or section) where the properties are displayed. Example: "Printer Service".
+ * @return The label used to display the tab name where the properties are displayed (may be localized)
+ */
+ public void setName(String heading) {
+ m_heading = heading;
+ }
+
+ /**
+ * Points to the basename of the Properties file that can localize the Meta Type informations.
+ * By default, (e.g. <code>setLocalization("person")</code> would match person_du_NL.properties in the root bundle directory.
+ * The default localization base name for the properties is OSGI-INF/l10n/bundle, but can
+ * be overridden by the manifest Bundle-Localization header (see core specification, in section Localization on page 68).
+ */
+ public void setLocalization(String path) {
+ if (path.endsWith(".properties")) {
+ throw new IllegalArgumentException(
+ "path must point to the base name of the propertie file, "
+ + "excluding local suffixes. For example: "
+ + "foo/bar/person is valid and matches the property file \"foo/bar/person_bundle_en_GB_welsh.properties\"");
+ }
+ m_localization = path.startsWith("/") ? path.substring(1) : path;
+ }
+
+ // --------------- MetaTypeProvider interface -------------------------------------------------
+
+ /**
+ * Returns all the Locales our bundle is containing. For instance, if our bundle contains the following localization files:
+ * OSGI-INF/l10n/bundle_en_GB_welsh.properties and OSGI-INF/l10n/bundle_en_GB.properties, then this method will return
+ * "en_GB", "en_GB_welsh" ...
+ * @return the list of Locale supported by our bundle.
+ */
+ @SuppressWarnings("rawtypes")
+ public String[] getLocales() {
+ int lastSlash = m_localization.lastIndexOf("/");
+ String path = (lastSlash == -1) ? "/" : ("/" + m_localization.substring(0, lastSlash - 1));
+ String base = (lastSlash == -1) ? m_localization : m_localization.substring(lastSlash + 1);
+ Enumeration e = m_bctx.getBundle().findEntries(path,
+ base + "*.properties", false);
+ if (e == null) {
+ return null;
+ }
+
+ TreeSet<String> set = new TreeSet<>();
+ while (e.hasMoreElements()) {
+ // We have found a locale property file in the form of "path/file[_language[_ country[_variation]].properties"
+ // And now, we have to get the "language[_country[_variation]]" part ...
+ URL url = (URL) e.nextElement();
+ String name = url.getPath();
+ name = name.substring(name.lastIndexOf("/") + 1);
+ int underscore = name.indexOf("_");
+ if (underscore != -1) {
+ name = name.substring(underscore + 1, name.length() - ".properties".length());
+ }
+ if (name.length() > 0) {
+ set.add(name);
+ }
+ }
+
+ String[] locales = (String[]) set.toArray(new String[set.size()]);
+ return locales.length == 0 ? null : locales;
+ }
+
+ /**
+ * Returns the ObjectClassDefinition for a given Pid/Locale.
+ */
+ public ObjectClassDefinition getObjectClassDefinition(String id, String locale) {
+ try {
+ // Check if the id matches our PID
+ if (!id.equals(m_pid)) {
+ m_logger.log(LogService.LOG_ERROR, "id " + id + " does not match pid " + m_pid);
+ return null;
+ }
+
+ Properties localeProperties = getLocaleProperties(locale);
+ return new ObjectClassDefinitionImpl(m_pid, m_heading,
+ m_description, m_propertiesMetaData, new Resource(localeProperties));
+ }
+
+ catch (Throwable t) {
+ m_logger.log(
+ Logger.LOG_ERROR,
+ "Unexpected exception while geting ObjectClassDefinition for " + id + " (locale="
+ + locale + ")", t);
+ return null;
+ }
+ }
+
+ /**
+ * We also implements the ManagedService and we just delegates the configuration handling to
+ * our associated ConfigurationDependency.
+ */
+ @SuppressWarnings("rawtypes")
+ public void updated(Dictionary properties) throws ConfigurationException {
+ m_managedServiceDelegate.updated(properties);
+ }
+
+ /**
+ * Gets the properties for a given Locale.
+ * @param locale
+ * @return
+ * @throws IOException
+ */
+ private synchronized Properties getLocaleProperties(String locale) throws IOException {
+ locale = locale == null ? Locale.getDefault().toString() : locale;
+ Properties properties = (Properties) m_localesProperties.get(locale);
+ if (properties == null) {
+ properties = new Properties();
+ URL url = m_bctx.getBundle().getEntry(m_localization + ".properties");
+ if (url != null) {
+ loadLocale(properties, url);
+ }
+
+ String path = m_localization;
+ StringTokenizer tok = new StringTokenizer(locale, "_");
+ while (tok.hasMoreTokens()) {
+ path += "_" + tok.nextToken();
+ url = m_bctx.getBundle().getEntry(path + ".properties");
+ if (url != null) {
+ properties = new Properties(properties);
+ loadLocale(properties, url);
+ }
+ }
+ m_localesProperties.put(locale, properties);
+ }
+ return properties;
+ }
+
+ /**
+ * Loads a Locale Properties file.
+ * @param properties
+ * @param url
+ * @throws IOException
+ */
+ private void loadLocale(Properties properties, URL url) throws IOException {
+ InputStream in = null;
+ try {
+ in = url.openStream();
+ properties.load(in);
+ }
+ finally {
+ if (in != null) {
+ try {
+ in.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+ }
+
+ // ManagedServiceFactory implementation
+ public void deleted(String pid) {
+ m_managedServiceFactoryDelegate.deleted(pid);
+ }
+
+ public String getName() {
+ return m_pid;
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void updated(String pid, Dictionary properties) throws ConfigurationException {
+ m_managedServiceFactoryDelegate.updated(pid, properties);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/ObjectClassDefinitionImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/ObjectClassDefinitionImpl.java
new file mode 100644
index 0000000..bd0a980
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/ObjectClassDefinitionImpl.java
@@ -0,0 +1,103 @@
+/*
+ * 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.dm.impl.metatype;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.felix.dm.PropertyMetaData;
+import org.osgi.service.metatype.AttributeDefinition;
+import org.osgi.service.metatype.ObjectClassDefinition;
+
+/**
+ * ObjectClassDefinition implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ObjectClassDefinitionImpl implements ObjectClassDefinition {
+ // Our OCD name (may be localized)
+ private String m_name;
+
+ // Our OCD description (may be localized)
+ private String m_description;
+
+ // Our OCD id
+ private String m_id;
+
+ // The list of Properties MetaData objects (from DependencyManager API)
+ private final List<PropertyMetaData> m_propertiesMetaData;
+
+ // The localized resource that can be used when localizing some parameters
+ private Resource m_resource;
+
+ public ObjectClassDefinitionImpl(String id, String name, String description, List<PropertyMetaData> propertiesMetaData, Resource resource) {
+ m_id = id;
+ m_name = name;
+ m_description = description;
+ m_propertiesMetaData = propertiesMetaData;
+ m_resource = resource;
+ }
+
+ // --------------------- ObjectClassDefinition ----------------------------------------
+
+ public AttributeDefinition[] getAttributeDefinitions(int filter) {
+ List<AttributeDefinition> attrs = new ArrayList<>();
+ for (int i = 0; i < m_propertiesMetaData.size(); i++) {
+ PropertyMetaDataImpl metaData = (PropertyMetaDataImpl) m_propertiesMetaData.get(i);
+ switch (filter) {
+ case ObjectClassDefinition.ALL:
+ attrs.add(new AttributeDefinitionImpl(metaData, m_resource));
+ break;
+ case ObjectClassDefinition.OPTIONAL:
+ if (!metaData.isRequired()) {
+ attrs.add(new AttributeDefinitionImpl(metaData, m_resource));
+ }
+ break;
+ case ObjectClassDefinition.REQUIRED:
+ if (metaData.isRequired()) {
+ attrs.add(new AttributeDefinitionImpl(metaData, m_resource));
+ }
+ break;
+ default:
+ }
+ }
+
+ AttributeDefinition[] array = new AttributeDefinitionImpl[attrs.size()];
+ return attrs.toArray(array);
+ }
+
+ public String getDescription() {
+ return m_resource.localize(m_description);
+ }
+
+ public String getID() {
+ return m_id;
+ }
+
+ public InputStream getIcon(int size) throws IOException {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public String getName() {
+ return m_resource.localize(m_name);
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/PropertyMetaDataImpl.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/PropertyMetaDataImpl.java
new file mode 100644
index 0000000..583b014
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/PropertyMetaDataImpl.java
@@ -0,0 +1,205 @@
+/*
+ * 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.dm.impl.metatype;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.dm.PropertyMetaData;
+import org.osgi.service.metatype.AttributeDefinition;
+
+/**
+ * DependencyManager PropertyMetaData Implementation. This class describes meta informations regarding
+ * one given configuration property.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class PropertyMetaDataImpl implements PropertyMetaData {
+ /**
+ * List of option labels (may be localized)
+ */
+ List<String> m_optionsLabels = new ArrayList<>();
+
+ /**
+ * List of option values
+ */
+ List<String> m_optionsValues = new ArrayList<>();
+
+ /**
+ * Property cardinality.
+ * @see {@link AttributeDefinition#getCardinality()}
+ */
+ private int m_cardinality;
+
+ /**
+ * Valid default property values
+ */
+ private String[] m_defaults;
+
+ /**
+ * Property description.
+ */
+ private String m_description;
+
+ /**
+ * Property title.
+ */
+ private String m_heading;
+
+ /**
+ * Property unique Id
+ */
+ private String m_id;
+
+ /**
+ * Required flag.
+ */
+ private boolean m_required;
+
+ /**
+ * Property Type.
+ * @see {@link AttributeDefinition#getType()}
+ */
+ private int m_type = AttributeDefinition.STRING;
+
+ /**
+ * Mapping between java types and valid MetaType types.
+ * @see {@link AttributeDefinition#getType()}
+ */
+ @SuppressWarnings({ "unchecked", "serial", "rawtypes" })
+ private final static Map<Class<?>, Integer> m_typeMapping = new HashMap() {{
+ put(Boolean.class, new Integer(AttributeDefinition.BOOLEAN));
+ put(Byte.class, new Integer(AttributeDefinition.BYTE));
+ put(Character.class, new Integer(AttributeDefinition.CHARACTER));
+ put(Double.class, new Integer(AttributeDefinition.FLOAT));
+ put(Integer.class, new Integer(AttributeDefinition.INTEGER));
+ put(Long.class, new Integer(AttributeDefinition.LONG));
+ put(Short.class, new Integer(AttributeDefinition.SHORT));
+ put(String.class, new Integer(AttributeDefinition.STRING));
+ }};
+
+ public PropertyMetaData addOption(String optionLabel, String optionValue) {
+ m_optionsLabels.add(optionLabel);
+ m_optionsValues.add(optionValue);
+ return this;
+ }
+
+ public PropertyMetaData setCardinality(int cardinality) {
+ m_cardinality = cardinality;
+ return this;
+ }
+
+ public PropertyMetaData setDefaults(String[] defaults) {
+ m_defaults = defaults;
+ return this;
+ }
+
+ public PropertyMetaData setDescription(String description) {
+ m_description = description;
+ return this;
+ }
+
+ public PropertyMetaData setHeading(String heading) {
+ m_heading = heading;
+ return this;
+ }
+
+ public PropertyMetaData setId(String id) {
+ m_id = id;
+ return this;
+ }
+
+ public PropertyMetaData setRequired(boolean required) {
+ m_required = required;
+ return this;
+ }
+
+ public PropertyMetaData setType(Class<?> classType) {
+ Integer type = (Integer) m_typeMapping.get(classType);
+ if (type == null) {
+ throw new IllegalArgumentException("Invalid type: " + classType + ". Valid types are "
+ + m_typeMapping.keySet());
+ }
+ m_type = type.intValue();
+ return this;
+ }
+
+ public String[] getOptionLabels() {
+ String[] optionLabels = new String[m_optionsLabels.size()];
+ return (String[]) m_optionsLabels.toArray(optionLabels);
+ }
+
+ public String[] getOptionValues() {
+ String[] optionValues = new String[m_optionsValues.size()];
+ return (String[]) m_optionsValues.toArray(optionValues);
+ }
+
+ public int getCardinality() {
+ return m_cardinality;
+ }
+
+ public String[] getDefaults() {
+ return m_defaults;
+ }
+
+ public String getDescription() {
+ return m_description;
+ }
+
+ public String getHeading() {
+ return m_heading;
+ }
+
+ public String getId() {
+ return m_id;
+ }
+
+ public boolean isRequired() {
+ return m_required;
+ }
+
+ public int getType() {
+ return m_type;
+ }
+
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("cardinality=").append(m_cardinality);
+ sb.append("; defaults=");
+ for (int i = 0; i < m_defaults.length; i ++) {
+ sb.append(m_defaults[i]).append(" ");
+ }
+ sb.append("; description=").append(m_description);
+ sb.append("; heading=").append(m_heading);
+ sb.append("; id=").append(m_id);
+ sb.append("; required=").append(m_required);
+ sb.append("; type=").append(getType());
+ sb.append("; optionLabels=");
+ for (int i = 0; i < m_optionsLabels.size(); i ++) {
+ sb.append(m_optionsLabels.get(i)).append(" ");
+ }
+ sb.append("; optionValues=");
+ for (int i = 0; i < m_optionsValues.size(); i ++) {
+ sb.append(m_optionsValues.get(i)).append(" ");
+ }
+ return sb.toString();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/Resource.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/Resource.java
new file mode 100644
index 0000000..c588a0c
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/impl/metatype/Resource.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.dm.impl.metatype;
+
+import java.util.Properties;
+
+/**
+ * Helper class used to localize a given Property Meta Data.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Resource {
+ private Properties m_properties;
+
+ public Resource(Properties properties) {
+ m_properties = properties;
+ }
+
+ public String localize(String param) {
+ if (m_properties != null && param != null && param.startsWith("%")) {
+ param = param.substring(1);
+ return m_properties.getProperty(param);
+ }
+ return param;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/packageinfo b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/packageinfo
new file mode 100644
index 0000000..bd33369
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/packageinfo
@@ -0,0 +1 @@
+version 4.0.0
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/AbstractCustomizerActionSet.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/AbstractCustomizerActionSet.java
new file mode 100644
index 0000000..4add2c2
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/AbstractCustomizerActionSet.java
@@ -0,0 +1,100 @@
+/*
+ * 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.dm.tracker;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Actions which can be performed on a given customizer interface.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class AbstractCustomizerActionSet {
+
+ enum Type { ADDED, MODIFIED, REMOVED }
+
+ final List<CustomizerAction> m_actions = new ArrayList<>();
+
+ public void addCustomizerAdded(Object item, Object related, Object object) {
+ m_actions.add(new CustomizerAction(Type.ADDED, item, related, object));
+ }
+
+ public void addCustomizerModified(Object item, Object related, Object object) {
+ m_actions.add(new CustomizerAction(Type.MODIFIED, item, related, object));
+ }
+
+ public void addCustomizerRemoved(Object item, Object related, Object object) {
+ m_actions.add(new CustomizerAction(Type.REMOVED, item, related, object));
+ }
+
+ public void appendActionSet(AbstractCustomizerActionSet actionSet) {
+ for (CustomizerAction action : actionSet.getActions()) {
+ m_actions.add(action);
+ }
+ }
+
+ abstract void execute();
+
+ public List<CustomizerAction> getActions() {
+ return m_actions;
+ }
+
+ @Override
+ public String toString() {
+ return "AbstractCustomizerActionSet [m_actions=" + m_actions + "]";
+ }
+
+ static class CustomizerAction {
+ private final Type m_type;
+ private final Object m_item;
+ private final Object m_related;
+ private final Object m_object;
+
+ public CustomizerAction(Type type, Object item, Object related, Object object) {
+ m_type = type;
+ m_item = item;
+ m_related = related;
+ m_object = object;
+ }
+
+ public Type getType() {
+ return m_type;
+ }
+
+ public Object getItem() {
+ return m_item;
+ }
+
+ public Object getRelated() {
+ return m_related;
+ }
+
+ public Object getObject() {
+ return m_object;
+ }
+
+ @Override
+ public String toString() {
+ return "CustomizerAction [m_type=" + m_type + ", m_item=" + m_item
+ + ", m_related=" + m_related + ", m_object=" + m_object
+ + "]";
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/AbstractTracked.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/AbstractTracked.java
new file mode 100644
index 0000000..5e254ad
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/AbstractTracked.java
@@ -0,0 +1,486 @@
+package org.apache.felix.dm.tracker;
+/*
+ * Copyright (c) OSGi Alliance (2007, 2008). 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.
+ */
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.dm.impl.SerialExecutor;
+
+/**
+ * Abstract class to track items. If a Tracker is reused (closed then reopened),
+ * then a new AbstractTracked object is used. This class acts a map of tracked
+ * item -> customized object. Subclasses of this class will act as the listener
+ * object for the tracker. This class is used to synchronize access to the
+ * tracked items. This is not a public class. It is only for use by the
+ * implementation of the Tracker class.
+ *
+ * @ThreadSafe
+ * @version $Revision: 5871 $
+ * @since 1.4
+ */
+@SuppressWarnings({"rawtypes", "unchecked"})
+abstract class AbstractTracked {
+ /* set this to true to compile in debug messages */
+ private static final boolean DEBUG = false;
+
+ /**
+ * Map of tracked items to customized objects.
+ *
+ * @GuardedBy this
+ */
+ private Map tracked;
+
+ /**
+ * Modification count. This field is initialized to zero and incremented by
+ * modified.
+ *
+ * @GuardedBy this
+ */
+ private int trackingCount;
+
+ /**
+ * List of items in the process of being added. This is used to deal with
+ * nesting of events. Since events may be synchronously delivered, events
+ * 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.
+ *
+ * @GuardedBy this
+ */
+ private final List adding;
+
+ /**
+ * true if the tracked object is closed.
+ *
+ * This field is volatile because it is set by one thread and read by
+ * another.
+ */
+ volatile boolean closed;
+
+ /**
+ * Initial list of items for the tracker. This is used to correctly process
+ * the initial items which could be modified before they are tracked. This
+ * is necessary since the initial set of tracked items are not "announced"
+ * by events and therefore the event which makes the item untracked could be
+ * delivered before we track the item.
+ *
+ * An item must not be in both the initial and adding lists at the same
+ * time. An item 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.
+ *
+ * @GuardedBy this
+ */
+ private final LinkedList initial;
+
+ private final SerialExecutor m_executor = new SerialExecutor(null);
+
+ /**
+ * AbstractTracked constructor.
+ */
+ AbstractTracked() {
+ this.tracked = new HashMap();
+ trackingCount = 0;
+ adding = new ArrayList(6);
+ initial = new LinkedList();
+ closed = false;
+ }
+
+ void setTracked(HashMap map) {
+ this.tracked = map;
+ }
+
+ /**
+ * Set initial list of items into tracker before events begin to be
+ * received.
+ *
+ * This method must be called from Tracker's open method while synchronized
+ * on this object in the same synchronized block as the add listener call.
+ *
+ * @param list The initial list of items to be tracked. <code>null</code>
+ * entries in the list are ignored.
+ * @GuardedBy this
+ */
+ void setInitial(Object[] list) {
+ if (list == null) {
+ return;
+ }
+ int size = list.length;
+ for (int i = 0; i < size; i++) {
+ Object item = list[i];
+ if (item == null) {
+ continue;
+ }
+ if (DEBUG) {
+ System.out.println("AbstractTracked.setInitial: " + item); //$NON-NLS-1$
+ }
+ initial.add(item);
+ }
+ }
+
+ /**
+ * Track the initial list of items. This is called after events can begin to
+ * be received.
+ *
+ * This method must be called from Tracker's open method while not
+ * synchronized on this object after the add listener call.
+ *
+ */
+ void trackInitial() {
+ while (true) {
+ Object item;
+ synchronized (this) {
+ if (closed || (initial.size() == 0)) {
+ /*
+ * if there are no more initial items
+ */
+ return; /* we are done */
+ }
+ /*
+ * move the first item from the initial list to the adding list
+ * within this synchronized block.
+ */
+ item = initial.removeFirst();
+ if (tracked.get(item) != null) {
+ /* if we are already tracking this item */
+ if (DEBUG) {
+ System.out
+ .println("AbstractTracked.trackInitial[already tracked]: " + item); //$NON-NLS-1$
+ }
+ continue; /* skip this item */
+ }
+ if (adding.contains(item)) {
+ /*
+ * if this item is already in the process of being added.
+ */
+ if (DEBUG) {
+ System.out
+ .println("AbstractTracked.trackInitial[already adding]: " + item); //$NON-NLS-1$
+ }
+ continue; /* skip this item */
+ }
+ adding.add(item);
+ final AbstractCustomizerActionSet actionSet = trackAdding(item, null);
+ m_executor.schedule(new Runnable() {
+
+ @Override
+ public void run() {
+ actionSet.execute();
+
+ }
+
+ });
+ }
+ if (DEBUG) {
+ System.out.println("AbstractTracked.trackInitial: " + item); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * Called by the owning Tracker object when it is closed.
+ */
+ void close() {
+ closed = true;
+ }
+
+ abstract AbstractCustomizerActionSet createCustomizerActionSet();
+
+ /**
+ * Begin to track an item.
+ *
+ * @param item Item to be tracked.
+ * @param related Action related object.
+ */
+ AbstractCustomizerActionSet track(final Object item, final Object related) {
+ final Object object;
+ final AbstractCustomizerActionSet actionSet = createCustomizerActionSet();
+ synchronized (this) {
+ if (closed) {
+ return actionSet;
+ }
+ object = tracked.get(item);
+ if (object == null) { /* we are not tracking the item */
+ if (adding.contains(item)) {
+ /* if this item is already in the process of being added. */
+ if (DEBUG) {
+ System.out
+ .println("AbstractTracked.track[already adding]: " + item); //$NON-NLS-1$
+ }
+ return actionSet;
+ }
+ adding.add(item); /* mark this item is being added */
+ }
+ else { /* we are currently tracking this item */
+ if (DEBUG) {
+ System.out
+ .println("AbstractTracked.track[modified]: " + item); //$NON-NLS-1$
+ }
+ modified(); /* increment modification count */
+ }
+ }
+
+ if (object == null) { /* we are not tracking the item */
+ actionSet.appendActionSet(trackAdding(item, related));
+ }
+ else {
+ /* Call customizer outside of synchronized region */
+ actionSet.addCustomizerModified(item, related, object);
+ /*
+ * If the customizer throws an unchecked exception, it is safe to
+ * let it propagate
+ */
+ }
+ return actionSet;
+ }
+
+ /**
+ * Common logic to add an item to the tracker used by track and
+ * trackInitial. The specified item must have been placed in the adding list
+ * before calling this method.
+ *
+ * @param item Item to be tracked.
+ * @param related Action related object.
+ */
+ private AbstractCustomizerActionSet trackAdding(final Object item, final Object related) {
+ final AbstractCustomizerActionSet actionSet = createCustomizerActionSet();
+ if (DEBUG) {
+ System.out.println("AbstractTracked.trackAdding: " + item); //$NON-NLS-1$
+ }
+ Object object = null;
+ boolean becameUntracked = false;
+ /* Call customizer outside of synchronized region */
+ try {
+ object = customizerAdding(item, related);
+ /*
+ * If the customizer throws an unchecked exception, it will
+ * propagate after the finally
+ */
+ }
+ finally {
+ boolean needToCallback = false;
+ synchronized (this) {
+ if (adding.remove(item) && !closed) {
+ /*
+ * if the item was not untracked during the customizer
+ * callback
+ */
+ if (object != null) {
+ tracked.put(item, object);
+ modified(); /* increment modification count */
+ notifyAll(); /* notify any waiters */
+ needToCallback = true; /* marrs: invoke added callback */
+ }
+ }
+ else {
+ becameUntracked = true;
+ }
+ }
+ if (needToCallback) {
+ actionSet.addCustomizerAdded(item, related, object);
+ }
+ }
+ /*
+ * The item became untracked during the customizer callback.
+ */
+ if (becameUntracked && (object != null)) {
+ if (DEBUG) {
+ System.out
+ .println("AbstractTracked.trackAdding[removed]: " + item); //$NON-NLS-1$
+ }
+ /* Call customizer outside of synchronized region */
+ actionSet.addCustomizerRemoved(item, related, object);
+ /*
+ * If the customizer throws an unchecked exception, it is safe to
+ * let it propagate
+ */
+ }
+ return actionSet;
+ }
+
+ /**
+ * Discontinue tracking the item.
+ *
+ * @param item Item to be untracked.
+ * @param related Action related object.
+ */
+ AbstractCustomizerActionSet untrack(final Object item, final Object related) {
+ AbstractCustomizerActionSet actionSet = createCustomizerActionSet();
+ final Object object;
+ synchronized (this) {
+ if (initial.remove(item)) { /*
+ * if this item is already in the list
+ * of initial references to process
+ */
+ if (DEBUG) {
+ System.out
+ .println("AbstractTracked.untrack[removed from initial]: " + item); //$NON-NLS-1$
+ }
+ return actionSet; /*
+ * we have removed it from the list and it will not be
+ * processed
+ */
+ }
+
+ if (adding.remove(item)) { /*
+ * if the item is in the process of
+ * being added
+ */
+ if (DEBUG) {
+ System.out
+ .println("AbstractTracked.untrack[being added]: " + item); //$NON-NLS-1$
+ }
+ return actionSet; /*
+ * in case the item is untracked while in the process of
+ * adding
+ */
+ }
+ object = tracked.remove(item); /*
+ * must remove from tracker before
+ * calling customizer callback
+ */
+ if (object == null) { /* are we actually tracking the item */
+ return actionSet;
+ }
+ modified(); /* increment modification count */
+ }
+ if (DEBUG) {
+ System.out.println("AbstractTracked.untrack[removed]: " + item); //$NON-NLS-1$
+ }
+ /* Call customizer outside of synchronized region */
+ actionSet.addCustomizerRemoved(item, related, object);
+ /*
+ * If the customizer throws an unchecked exception, it is safe to let it
+ * propagate
+ */
+ return actionSet;
+ }
+
+ /**
+ * Returns the number of tracked items.
+ *
+ * @return The number of tracked items.
+ *
+ * @GuardedBy this
+ */
+ int size() {
+ return tracked.size();
+ }
+
+ /**
+ * Return the customized object for the specified item
+ *
+ * @param item The item to lookup in the map
+ * @return The customized object for the specified item.
+ *
+ * @GuardedBy this
+ */
+ Object getCustomizedObject(final Object item) {
+ return tracked.get(item);
+ }
+
+ /**
+ * Return the list of tracked items.
+ *
+ * @param list An array to contain the tracked items.
+ * @return The specified list if it is large enough to hold the tracked
+ * items or a new array large enough to hold the tracked items.
+ * @GuardedBy this
+ */
+ Object[] getTracked(final Object[] list) {
+ return tracked.keySet().toArray(list);
+ }
+
+ /**
+ * Increment the modification count. If this method is overridden, the
+ * overriding method MUST call this method to increment the tracking count.
+ *
+ * @GuardedBy this
+ */
+ void modified() {
+ trackingCount++;
+ }
+
+ /**
+ * Returns the tracking count for this <code>ServiceTracker</code> object.
+ *
+ * The tracking count is initialized to 0 when this object is opened. Every
+ * time an item is added, modified or removed from this object the tracking
+ * count is incremented.
+ *
+ * @GuardedBy this
+ * @return The tracking count for this object.
+ */
+ int getTrackingCount() {
+ return trackingCount;
+ }
+
+ /**
+ * Returns the serial executor used by this tracked.
+ * @return
+ */
+ SerialExecutor getExecutor() {
+ return m_executor;
+ }
+
+ /**
+ * Call the specific customizer adding method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Item to be tracked.
+ * @param related Action related object.
+ * @return Customized object for the tracked item or <code>null</code> if
+ * the item is not to be tracked.
+ */
+ abstract Object customizerAdding(final Object item, final Object related);
+
+ /** marrs: Call the specific customizer added method. */
+ abstract void customizerAdded(final Object item, final Object related, final Object object);
+
+ /**
+ * Call the specific customizer modified method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Tracked item.
+ * @param related Action related object.
+ * @param object Customized object for the tracked item.
+ */
+ abstract void customizerModified(final Object item, final Object related,
+ final Object object);
+
+ /**
+ * Call the specific customizer removed method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Tracked item.
+ * @param related Action related object.
+ * @param object Customized object for the tracked item.
+ */
+ abstract void customizerRemoved(final Object item, final Object related,
+ final Object object);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/BundleTracker.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/BundleTracker.java
new file mode 100644
index 0000000..e3b1677
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/BundleTracker.java
@@ -0,0 +1,505 @@
+package org.apache.felix.dm.tracker;
+/*
+ * Copyright (c) OSGi Alliance (2007, 2008). 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.
+ */
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.SynchronousBundleListener;
+
+/**
+ * The <code>BundleTracker</code> class simplifies tracking bundles much like
+ * the <code>ServiceTracker</code> simplifies tracking services.
+ * <p>
+ * A <code>BundleTracker</code> is constructed with state criteria and a
+ * <code>BundleTrackerCustomizer</code> object. A <code>BundleTracker</code> can
+ * use the <code>BundleTrackerCustomizer</code> to select which bundles are
+ * tracked and to create a customized object to be tracked with the bundle. The
+ * <code>BundleTracker</code> can then be opened to begin tracking all bundles
+ * whose state matches the specified state criteria.
+ * <p>
+ * The <code>getBundles</code> method can be called to get the
+ * <code>Bundle</code> objects of the bundles being tracked. The
+ * <code>getObject</code> method can be called to get the customized object for
+ * a tracked bundle.
+ * <p>
+ * The <code>BundleTracker</code> class is thread-safe. It does not call a
+ * <code>BundleTrackerCustomizer</code> while holding any locks.
+ * <code>BundleTrackerCustomizer</code> implementations must also be
+ * thread-safe.
+ *
+ * @ThreadSafe
+ * @version $Revision: 5894 $
+ * @since 1.4
+ */
+public class BundleTracker implements BundleTrackerCustomizer {
+ /* set this to true to compile in debug messages */
+ static final boolean DEBUG = false;
+
+ /**
+ * The Bundle Context used by this <code>BundleTracker</code>.
+ */
+ protected final BundleContext context;
+
+ /**
+ * The <code>BundleTrackerCustomizer</code> object for this tracker.
+ */
+ final BundleTrackerCustomizer customizer;
+
+ /**
+ * Tracked bundles: <code>Bundle</code> object -> customized Object and
+ * <code>BundleListener</code> object
+ */
+ private volatile Tracked tracked;
+
+ /**
+ * Accessor method for the current Tracked object. This method is only
+ * intended to be used by the unsynchronized methods which do not modify the
+ * tracked field.
+ *
+ * @return The current Tracked object.
+ */
+ private Tracked tracked() {
+ return tracked;
+ }
+
+ /**
+ * State mask for bundles being tracked. This field contains the ORed values
+ * of the bundle states being tracked.
+ */
+ final int mask;
+
+ /**
+ * Create a <code>BundleTracker</code> for bundles whose state is present in
+ * the specified state mask.
+ *
+ * <p>
+ * Bundles whose state is present on the specified state mask will be
+ * tracked by this <code>BundleTracker</code>.
+ *
+ * @param context The <code>BundleContext</code> against which the tracking
+ * is done.
+ * @param stateMask The bit mask of the <code>OR</code>ing of the bundle
+ * states to be tracked.
+ * @param customizer The customizer object to call when bundles are added,
+ * modified, or removed in this <code>BundleTracker</code>. If
+ * customizer is <code>null</code>, then this
+ * <code>BundleTracker</code> will be used as the
+ * <code>BundleTrackerCustomizer</code> and this
+ * <code>BundleTracker</code> will call the
+ * <code>BundleTrackerCustomizer</code> methods on itself.
+ * @see Bundle#getState()
+ */
+ public BundleTracker(BundleContext context, int stateMask,
+ BundleTrackerCustomizer customizer) {
+ this.context = context;
+ this.mask = stateMask;
+ this.customizer = (customizer == null) ? this : customizer;
+ }
+
+ /**
+ * Open this <code>BundleTracker</code> and begin tracking bundles.
+ *
+ * <p>
+ * Bundle which match the state criteria specified when this
+ * <code>BundleTracker</code> was created are now tracked by this
+ * <code>BundleTracker</code>.
+ *
+ * @throws java.lang.IllegalStateException If the <code>BundleContext</code>
+ * with which this <code>BundleTracker</code> was created is no
+ * longer valid.
+ * @throws java.lang.SecurityException If the caller and this class do not
+ * have the appropriate
+ * <code>AdminPermission[context bundle,LISTENER]</code>, and the
+ * Java Runtime Environment supports permissions.
+ */
+ public void open() {
+ final Tracked t;
+ synchronized (this) {
+ if (tracked != null) {
+ return;
+ }
+ if (DEBUG) {
+ System.out.println("BundleTracker.open"); //$NON-NLS-1$
+ }
+ t = new Tracked();
+ synchronized (t) {
+ context.addBundleListener(t);
+ Bundle[] bundles = context.getBundles();
+ if (bundles != null) {
+ int length = bundles.length;
+ for (int i = 0; i < length; i++) {
+ int state = bundles[i].getState();
+ if ((state & mask) == 0) {
+ /* null out bundles whose states are not interesting */
+ bundles[i] = null;
+ }
+ }
+ /* set tracked with the initial bundles */
+ t.setInitial(bundles);
+ }
+ }
+ tracked = t;
+ }
+ /* Call tracked outside of synchronized region */
+ t.trackInitial(); /* process the initial references */
+ }
+
+ /**
+ * Close this <code>BundleTracker</code>.
+ *
+ * <p>
+ * This method should be called when this <code>BundleTracker</code> should
+ * end the tracking of bundles.
+ *
+ * <p>
+ * This implementation calls {@link #getBundles()} to get the list of
+ * tracked bundles to remove.
+ */
+ public void close() {
+ final Bundle[] bundles;
+ final Tracked outgoing;
+ synchronized (this) {
+ outgoing = tracked;
+ if (outgoing == null) {
+ return;
+ }
+ if (DEBUG) {
+ System.out.println("BundleTracker.close"); //$NON-NLS-1$
+ }
+ outgoing.close();
+ bundles = getBundles();
+ tracked = null;
+ try {
+ context.removeBundleListener(outgoing);
+ }
+ catch (IllegalStateException e) {
+ /* In case the context was stopped. */
+ }
+ }
+ if (bundles != null) {
+ for (int i = 0; i < bundles.length; i++) {
+ outgoing.untrack(bundles[i], null);
+ }
+ }
+ }
+
+ /**
+ * Default implementation of the
+ * <code>BundleTrackerCustomizer.addingBundle</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>BundleTracker</code> has been
+ * constructed with a <code>null BundleTrackerCustomizer</code> argument.
+ *
+ * <p>
+ * This implementation simply returns the specified <code>Bundle</code>.
+ *
+ * <p>
+ * This method can be overridden in a subclass to customize the object to be
+ * tracked for the bundle being added.
+ *
+ * @param bundle The <code>Bundle</code> being added to this
+ * <code>BundleTracker</code> object.
+ * @param event The bundle event which caused this customizer method to be
+ * called or <code>null</code> if there is no bundle event associated
+ * with the call to this method.
+ * @return The specified bundle.
+ * @see BundleTrackerCustomizer#addingBundle(Bundle, BundleEvent)
+ */
+ public Object addingBundle(Bundle bundle, BundleEvent event) {
+ return bundle;
+ }
+
+ public void addedBundle(Bundle bundle, BundleEvent event, Object object) {
+ /* do nothing */
+ }
+
+ /**
+ * Default implementation of the
+ * <code>BundleTrackerCustomizer.modifiedBundle</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>BundleTracker</code> has been
+ * constructed with a <code>null BundleTrackerCustomizer</code> argument.
+ *
+ * <p>
+ * This implementation does nothing.
+ *
+ * @param bundle The <code>Bundle</code> whose state has been modified.
+ * @param event The bundle event which caused this customizer method to be
+ * called or <code>null</code> if there is no bundle event associated
+ * with the call to this method.
+ * @param object The customized object for the specified Bundle.
+ * @see BundleTrackerCustomizer#modifiedBundle(Bundle, BundleEvent, Object)
+ */
+ public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
+ /* do nothing */
+ }
+
+ /**
+ * Default implementation of the
+ * <code>BundleTrackerCustomizer.removedBundle</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>BundleTracker</code> has been
+ * constructed with a <code>null BundleTrackerCustomizer</code> argument.
+ *
+ * <p>
+ * This implementation does nothing.
+ *
+ * @param bundle The <code>Bundle</code> being removed.
+ * @param event The bundle event which caused this customizer method to be
+ * called or <code>null</code> if there is no bundle event associated
+ * with the call to this method.
+ * @param object The customized object for the specified bundle.
+ * @see BundleTrackerCustomizer#removedBundle(Bundle, BundleEvent, Object)
+ */
+ public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
+ /* do nothing */
+ }
+
+ /**
+ * Return an array of <code>Bundle</code>s for all bundles being tracked by
+ * this <code>BundleTracker</code>.
+ *
+ * @return An array of <code>Bundle</code>s or <code>null</code> if no
+ * bundles are being tracked.
+ */
+ public Bundle[] getBundles() {
+ final Tracked t = tracked();
+ if (t == null) { /* if BundleTracker is not open */
+ return null;
+ }
+ synchronized (t) {
+ int length = t.size();
+ if (length == 0) {
+ return null;
+ }
+ return (Bundle[]) t.getTracked(new Bundle[length]);
+ }
+ }
+
+ /**
+ * Returns the customized object for the specified <code>Bundle</code> if
+ * the specified bundle is being tracked by this <code>BundleTracker</code>.
+ *
+ * @param bundle The <code>Bundle</code> being tracked.
+ * @return The customized object for the specified <code>Bundle</code> or
+ * <code>null</code> if the specified <code>Bundle</code> is not
+ * being tracked.
+ */
+ public Object getObject(Bundle bundle) {
+ final Tracked t = tracked();
+ if (t == null) { /* if BundleTracker is not open */
+ return null;
+ }
+ synchronized (t) {
+ return t.getCustomizedObject(bundle);
+ }
+ }
+
+ /**
+ * Remove a bundle from this <code>BundleTracker</code>.
+ *
+ * The specified bundle will be removed from this <code>BundleTracker</code>
+ * . If the specified bundle was being tracked then the
+ * <code>BundleTrackerCustomizer.removedBundle</code> method will be called
+ * for that bundle.
+ *
+ * @param bundle The <code>Bundle</code> to be removed.
+ */
+ public void remove(Bundle bundle) {
+ final Tracked t = tracked();
+ if (t == null) { /* if BundleTracker is not open */
+ return;
+ }
+ t.untrack(bundle, null);
+ }
+
+ /**
+ * Return the number of bundles being tracked by this
+ * <code>BundleTracker</code>.
+ *
+ * @return The number of bundles being tracked.
+ */
+ public int size() {
+ final Tracked t = tracked();
+ if (t == null) { /* if BundleTracker is not open */
+ return 0;
+ }
+ synchronized (t) {
+ return t.size();
+ }
+ }
+
+ /**
+ * Returns the tracking count for this <code>BundleTracker</code>.
+ *
+ * The tracking count is initialized to 0 when this
+ * <code>BundleTracker</code> is opened. Every time a bundle is added,
+ * modified or removed from this <code>BundleTracker</code> the tracking
+ * count is incremented.
+ *
+ * <p>
+ * The tracking count can be used to determine if this
+ * <code>BundleTracker</code> has added, modified or removed a bundle by
+ * comparing a tracking count value previously collected with the current
+ * tracking count value. If the value has not changed, then no bundle has
+ * been added, modified or removed from this <code>BundleTracker</code>
+ * since the previous tracking count was collected.
+ *
+ * @return The tracking count for this <code>BundleTracker</code> or -1 if
+ * this <code>BundleTracker</code> is not open.
+ */
+ public int getTrackingCount() {
+ final Tracked t = tracked();
+ if (t == null) { /* if BundleTracker is not open */
+ return -1;
+ }
+ synchronized (t) {
+ return t.getTrackingCount();
+ }
+ }
+
+ /**
+ * Inner class which subclasses AbstractTracked. This class is the
+ * <code>SynchronousBundleListener</code> object for the tracker.
+ *
+ * @ThreadSafe
+ * @since 1.4
+ */
+ class Tracked extends AbstractTracked implements SynchronousBundleListener {
+ /**
+ * Tracked constructor.
+ */
+ Tracked() {
+ super();
+ }
+
+ /**
+ * <code>BundleListener</code> method for the <code>BundleTracker</code>
+ * class. This method must NOT be synchronized to avoid deadlock
+ * potential.
+ *
+ * @param event <code>BundleEvent</code> object from the framework.
+ */
+ public void bundleChanged(final BundleEvent event) {
+ /*
+ * Check if we had a delayed call (which could happen when we
+ * close).
+ */
+ if (closed) {
+ return;
+ }
+ final Bundle bundle = event.getBundle();
+ final int state = bundle.getState();
+ if (DEBUG) {
+ System.out
+ .println("BundleTracker.Tracked.bundleChanged[" + state + "]: " + bundle); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ if ((state & mask) != 0) {
+ track(bundle, event);
+ /*
+ * If the customizer throws an unchecked exception, it is safe
+ * to let it propagate
+ */
+ }
+ else {
+ untrack(bundle, event);
+ /*
+ * If the customizer throws an unchecked exception, it is safe
+ * to let it propagate
+ */
+ }
+ }
+
+ /**
+ * Call the specific customizer adding method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Item to be tracked.
+ * @param related Action related object.
+ * @return Customized object for the tracked item or <code>null</code>
+ * if the item is not to be tracked.
+ */
+ Object customizerAdding(final Object item,
+ final Object related) {
+ return customizer
+ .addingBundle((Bundle) item, (BundleEvent) related);
+ }
+
+ void customizerAdded(final Object item, final Object related, final Object object) {
+ customizer.addedBundle((Bundle) item, (BundleEvent) related, object);
+ }
+
+ /**
+ * Call the specific customizer modified method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Tracked item.
+ * @param related Action related object.
+ * @param object Customized object for the tracked item.
+ */
+ void customizerModified(final Object item,
+ final Object related, final Object object) {
+ customizer.modifiedBundle((Bundle) item, (BundleEvent) related,
+ object);
+ }
+
+ /**
+ * Call the specific customizer removed method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Tracked item.
+ * @param related Action related object.
+ * @param object Customized object for the tracked item.
+ */
+ void customizerRemoved(final Object item,
+ final Object related, final Object object) {
+ customizer.removedBundle((Bundle) item, (BundleEvent) related,
+ object);
+ }
+
+ @Override
+ AbstractCustomizerActionSet createCustomizerActionSet() {
+ return new AbstractCustomizerActionSet() {
+
+ @Override
+ public void addCustomizerAdded(Object item, Object related, Object object) {
+ customizerAdded(item, related, object);
+ }
+
+ @Override
+ public void addCustomizerModified(Object item, Object related,
+ Object object) {
+ customizerModified(item, related, object);
+ }
+ @Override
+ public void addCustomizerRemoved(Object item, Object related,
+ Object object) {
+ customizerRemoved(item, related, object);
+ }
+
+ @Override
+ void execute() {
+ // nothing to be done here, since this actionSet executes the actions immediately.
+ }
+ };
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/BundleTrackerCustomizer.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/BundleTrackerCustomizer.java
new file mode 100644
index 0000000..0fd340e
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/BundleTrackerCustomizer.java
@@ -0,0 +1,106 @@
+package org.apache.felix.dm.tracker;
+/*
+ * Copyright (c) OSGi Alliance (2007, 2008). 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.
+ */
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+
+/**
+ * The <code>BundleTrackerCustomizer</code> interface allows a
+ * <code>BundleTracker</code> to customize the <code>Bundle</code>s that are
+ * tracked. A <code>BundleTrackerCustomizer</code> is called when a bundle is
+ * being added to a <code>BundleTracker</code>. The
+ * <code>BundleTrackerCustomizer</code> can then return an object for the
+ * tracked bundle. A <code>BundleTrackerCustomizer</code> is also called when a
+ * tracked bundle is modified or has been removed from a
+ * <code>BundleTracker</code>.
+ *
+ * <p>
+ * The methods in this interface may be called as the result of a
+ * <code>BundleEvent</code> being received by a <code>BundleTracker</code>.
+ * Since <code>BundleEvent</code>s are received synchronously by the
+ * <code>BundleTracker</code>, it is highly recommended that implementations of
+ * these methods do not alter bundle states while being synchronized on any
+ * object.
+ *
+ * <p>
+ * The <code>BundleTracker</code> class is thread-safe. It does not call a
+ * <code>BundleTrackerCustomizer</code> while holding any locks.
+ * <code>BundleTrackerCustomizer</code> implementations must also be
+ * thread-safe.
+ *
+ * @ThreadSafe
+ * @version $Revision: 5874 $
+ * @since 1.4
+ */
+public interface BundleTrackerCustomizer {
+ /**
+ * A bundle is being added to the <code>BundleTracker</code>.
+ *
+ * <p>
+ * This method is called before a bundle which matched the search parameters
+ * of the <code>BundleTracker</code> is added to the
+ * <code>BundleTracker</code>. This method should return the object to be
+ * tracked for the specified <code>Bundle</code>. The returned object is
+ * stored in the <code>BundleTracker</code> and is available from the
+ * {@link BundleTracker#getObject(Bundle) getObject} method.
+ *
+ * @param bundle The <code>Bundle</code> being added to the
+ * <code>BundleTracker</code>.
+ * @param event The bundle event which caused this customizer method to be
+ * called or <code>null</code> if there is no bundle event associated
+ * with the call to this method.
+ * @return The object to be tracked for the specified <code>Bundle</code>
+ * object or <code>null</code> if the specified <code>Bundle</code>
+ * object should not be tracked.
+ */
+ public Object addingBundle(Bundle bundle, BundleEvent event);
+
+ /** marrs: A bundle has been added to the BundleTracker. */
+ public void addedBundle(Bundle bundle, BundleEvent event, Object object);
+
+ /**
+ * A bundle tracked by the <code>BundleTracker</code> has been modified.
+ *
+ * <p>
+ * This method is called when a bundle being tracked by the
+ * <code>BundleTracker</code> has had its state modified.
+ *
+ * @param bundle The <code>Bundle</code> whose state has been modified.
+ * @param event The bundle event which caused this customizer method to be
+ * called or <code>null</code> if there is no bundle event associated
+ * with the call to this method.
+ * @param object The tracked object for the specified bundle.
+ */
+ public void modifiedBundle(Bundle bundle, BundleEvent event,
+ Object object);
+
+ /**
+ * A bundle tracked by the <code>BundleTracker</code> has been removed.
+ *
+ * <p>
+ * This method is called after a bundle is no longer being tracked by the
+ * <code>BundleTracker</code>.
+ *
+ * @param bundle The <code>Bundle</code> that has been removed.
+ * @param event The bundle event which caused this customizer method to be
+ * called or <code>null</code> if there is no bundle event associated
+ * with the call to this method.
+ * @param object The tracked object for the specified bundle.
+ */
+ public void removedBundle(Bundle bundle, BundleEvent event,
+ Object object);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/ServiceTracker.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/ServiceTracker.java
new file mode 100644
index 0000000..f682bbd
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/ServiceTracker.java
@@ -0,0 +1,1477 @@
+package org.apache.felix.dm.tracker;
+/*
+ * Copyright (c) OSGi Alliance (2000, 2009). 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.
+ */
+
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.felix.dm.impl.ServiceUtil;
+import org.osgi.framework.AllServiceListener;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.Filter;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.Version;
+
+/**
+ * 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>
+ * can use a <code>ServiceTrackerCustomizer</code> to customize the service
+ * objects to be tracked. The <code>ServiceTracker</code> 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> correctly
+ * handles all of the details of listening to <code>ServiceEvent</code>s 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.
+ * <p>
+ * The <code>ServiceTracker</code> class is thread-safe. It does not call a
+ * <code>ServiceTrackerCustomizer</code> while holding any locks.
+ * <code>ServiceTrackerCustomizer</code> implementations must also be
+ * thread-safe.
+ *
+ * @ThreadSafe
+ * @version $Revision: 6386 $
+ */
+public class ServiceTracker implements ServiceTrackerCustomizer {
+ /* set this to true to compile in debug messages */
+ static final boolean DEBUG = false;
+ /**
+ * The Bundle Context used by this <code>ServiceTracker</code>.
+ */
+ protected final BundleContext context;
+ /**
+ * The Filter used by this <code>ServiceTracker</code> which specifies the
+ * search criteria for the services to track.
+ *
+ * @since 1.1
+ */
+ protected final Filter filter;
+ /**
+ * The <code>ServiceTrackerCustomizer</code> 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> -> customized Object and
+ * <code>ServiceListener</code> object
+ */
+ private volatile Tracked tracked;
+
+ /**
+ * Accessor method for the current Tracked object. This method is only
+ * intended to be used by the unsynchronized methods which do not modify the
+ * tracked field.
+ *
+ * @return The current Tracked object.
+ */
+ private Tracked tracked() {
+ return tracked;
+ }
+
+ /**
+ * 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;
+
+ /**
+ * org.osgi.framework package version which introduced
+ * {@link ServiceEvent#MODIFIED_ENDMATCH}
+ */
+ private static final Version endMatchVersion = new Version(1, 5, 0);
+
+ /**
+ * Flag that gets set when opening the tracker, determines if the tracker should
+ * track all aspects or just the highest ranked ones.
+ */
+ public boolean m_trackAllAspects;
+
+ private boolean debug = false;
+ private String debugKey;
+
+ public void setDebug(String debugKey) {
+ this.debug = true;
+ this.debugKey = debugKey;
+ }
+
+ /**
+ * Create a <code>ServiceTracker</code> on the specified
+ * <code>ServiceReference</code>.
+ *
+ * <p>
+ * The service referenced by the specified <code>ServiceReference</code>
+ * will be tracked by this <code>ServiceTracker</code>.
+ *
+ * @param context The <code>BundleContext</code> against which the tracking
+ * is done.
+ * @param reference The <code>ServiceReference</code> 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>. If
+ * customizer is <code>null</code>, then this
+ * <code>ServiceTracker</code> will be used as the
+ * <code>ServiceTrackerCustomizer</code> and this
+ * <code>ServiceTracker</code> will call the
+ * <code>ServiceTrackerCustomizer</code> methods on itself.
+ */
+ public ServiceTracker(final BundleContext context,
+ final ServiceReference reference,
+ final 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() + ")";
+ try {
+ this.filter = context.createFilter(listenerFilter);
+ }
+ catch (InvalidSyntaxException e) {
+ /*
+ * we could only get this exception if the ServiceReference was
+ * invalid
+ */
+ IllegalArgumentException iae = new IllegalArgumentException(
+ "unexpected InvalidSyntaxException: " + e.getMessage());
+ iae.initCause(e);
+ throw iae;
+ }
+ }
+
+ /**
+ * Create a <code>ServiceTracker</code> on the specified class name.
+ *
+ * <p>
+ * Services registered under the specified class name will be tracked by
+ * this <code>ServiceTracker</code>.
+ *
+ * @param context The <code>BundleContext</code> against which the tracking
+ * is done.
+ * @param clazz The 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>. If
+ * customizer is <code>null</code>, then this
+ * <code>ServiceTracker</code> will be used as the
+ * <code>ServiceTrackerCustomizer</code> and this
+ * <code>ServiceTracker</code> will call the
+ * <code>ServiceTrackerCustomizer</code> methods on itself.
+ */
+ public ServiceTracker(final BundleContext context, final String clazz,
+ final ServiceTrackerCustomizer customizer) {
+ this.context = context;
+ this.trackReference = null;
+ this.trackClass = clazz;
+ this.customizer = (customizer == null) ? this : customizer;
+ // we call clazz.toString to verify clazz is non-null!
+ this.listenerFilter = "(" + Constants.OBJECTCLASS + "="
+ + clazz.toString() + ")";
+ try {
+ this.filter = context.createFilter(listenerFilter);
+ }
+ catch (InvalidSyntaxException e) {
+ /*
+ * we could only get this exception if the clazz argument was
+ * malformed
+ */
+ IllegalArgumentException iae = new IllegalArgumentException(
+ "unexpected InvalidSyntaxException: " + e.getMessage());
+ iae.initCause(e);
+ throw iae;
+ }
+ }
+
+ /**
+ * Create a <code>ServiceTracker</code> 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>.
+ *
+ * @param context The <code>BundleContext</code> against which the tracking
+ * is done.
+ * @param filter The <code>Filter</code> 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>. If
+ * customizer is null, then this <code>ServiceTracker</code> will be
+ * used as the <code>ServiceTrackerCustomizer</code> and this
+ * <code>ServiceTracker</code> will call the
+ * <code>ServiceTrackerCustomizer</code> methods on itself.
+ * @since 1.1
+ */
+ public ServiceTracker(final BundleContext context, final Filter filter,
+ final ServiceTrackerCustomizer customizer) {
+ this.context = context;
+ this.trackReference = null;
+ this.trackClass = null;
+ final Version frameworkVersion = (Version) AccessController
+ .doPrivileged(new PrivilegedAction<Version>() {
+ public Version run() {
+ String version = context
+ .getProperty(Constants.FRAMEWORK_VERSION);
+ return (version == null) ? Version.emptyVersion
+ : new Version(version);
+ }
+ });
+ final boolean endMatchSupported = (frameworkVersion
+ .compareTo(endMatchVersion) >= 0);
+ this.listenerFilter = endMatchSupported ? filter.toString() : 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> and begin tracking services.
+ *
+ * <p>
+ * This implementation calls <code>open(false)</code>.
+ *
+ * @throws java.lang.IllegalStateException If the <code>BundleContext</code>
+ * with which this <code>ServiceTracker</code> was created is no
+ * longer valid.
+ * @see #open(boolean)
+ */
+ public void open() {
+ open(false);
+ }
+
+ /**
+ * Open this <code>ServiceTracker</code> and begin tracking services.
+ *
+ * <p>
+ * Services which match the search criteria specified when this
+ * <code>ServiceTracker</code> was created are now tracked by this
+ * <code>ServiceTracker</code>.
+ *
+ * @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 accessible to the bundle whose
+ * <code>BundleContext</code> is used by this
+ * <code>ServiceTracker</code>.
+ * @throws java.lang.IllegalStateException If the <code>BundleContext</code>
+ * with which this <code>ServiceTracker</code> was created is no
+ * longer valid.
+ * @since 1.3
+ */
+ public void open(boolean trackAllServices) {
+ open(trackAllServices, false);
+ }
+
+ /**
+ * Open this <code>ServiceTracker</code> and begin tracking services.
+ *
+ * <p>
+ * Services which match the search criteria specified when this
+ * <code>ServiceTracker</code> was created are now tracked by this
+ * <code>ServiceTracker</code>.
+ *
+ * @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 accessible to the bundle whose
+ * <code>BundleContext</code> is used by this
+ * <code>ServiceTracker</code>.
+ * @param trackAllAspects If <code>true</code> then this
+ * <code>ServiceTracker</code> will track all aspects regardless
+ * of their rank. If <code>false</code> only the highest ranked
+ * aspects (or the original service if there are no aspects) will
+ * be tracked. The latter is the default.
+ * @throws java.lang.IllegalStateException If the <code>BundleContext</code>
+ * with which this <code>ServiceTracker</code> was created is no
+ * longer valid.
+ */
+ public void open(boolean trackAllServices, boolean trackAllAspects) {
+ if (debug) {
+ System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " open");
+ }
+ final Tracked t;
+ synchronized (this) {
+ if (tracked != null) {
+ return;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.open: " + filter);
+ }
+ m_trackAllAspects = trackAllAspects;
+ t = trackAllServices ? new AllTracked() : new Tracked();
+ synchronized (t) {
+ try {
+ context.addServiceListener(t, listenerFilter);
+ ServiceReference[] references = null;
+ if (trackClass != null) {
+ references = getInitialReferences(trackAllServices,
+ trackClass, null);
+ }
+ else {
+ if (trackReference != null) {
+ if (trackReference.getBundle() != null) {
+ references = new ServiceReference[] {trackReference};
+ }
+ }
+ else { /* user supplied filter */
+ references = getInitialReferences(trackAllServices,
+ null,
+ (listenerFilter != null) ? listenerFilter
+ : filter.toString());
+ }
+ }
+ /* set tracked with the initial references */
+ t.setInitial(references);
+
+ // only actually schedules the actions for execution within this synchronized block,
+ // but do the actual execution afterwards.
+ t.trackInitial();
+
+ }
+ catch (InvalidSyntaxException e) {
+ throw new RuntimeException(
+ "unexpected InvalidSyntaxException: "
+ + e.getMessage(), e);
+ }
+ }
+ tracked = t;
+ }
+ /* Call tracked outside of synchronized region */
+ // just trigger the executor
+ t.getExecutor().execute();
+ }
+
+ /**
+ * Returns the list of initial <code>ServiceReference</code>s that will be
+ * tracked by this <code>ServiceTracker</code>.
+ *
+ * @param trackAllServices If <code>true</code>, use
+ * <code>getAllServiceReferences</code>.
+ * @param className The class name with which the service was registered, or
+ * <code>null</code> for all services.
+ * @param filterString The filter criteria or <code>null</code> for all
+ * services.
+ * @return The list of initial <code>ServiceReference</code>s.
+ * @throws InvalidSyntaxException If the specified filterString has an
+ * invalid syntax.
+ */
+ private ServiceReference[] getInitialReferences(boolean trackAllServices,
+ String className, String filterString)
+ throws InvalidSyntaxException {
+ if (trackAllServices) {
+ return context.getAllServiceReferences(className, filterString);
+ }
+ return context.getServiceReferences(className, filterString);
+ }
+
+ /**
+ * Close this <code>ServiceTracker</code>.
+ *
+ * <p>
+ * This method should be called when this <code>ServiceTracker</code> should
+ * end the tracking of services.
+ *
+ * <p>
+ * This implementation calls {@link #getServiceReferences()} to get the list
+ * of tracked services to remove.
+ */
+ public void close() {
+ final Tracked outgoing;
+ final ServiceReference[] references;
+ synchronized (this) {
+ outgoing = tracked;
+ if (outgoing == null) {
+ return;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.close: " + filter);
+ }
+ outgoing.close();
+ references = getServiceReferences();
+ tracked = null;
+ try {
+ context.removeServiceListener(outgoing);
+ }
+ catch (IllegalStateException e) {
+ /* In case the context was stopped. */
+ }
+ }
+ modified(); /* clear the cache */
+ synchronized (outgoing) {
+ outgoing.notifyAll(); /* wake up any waiters */
+ }
+ if (references != null) {
+ for (int i = 0; i < references.length; i++) {
+ outgoing.untrack(references[i], null).execute();
+ }
+ }
+ if (DEBUG) {
+ if ((cachedReference == null) && (cachedService == null)) {
+ System.out
+ .println("ServiceTracker.close[cached cleared]: "
+ + filter);
+ }
+ }
+ }
+
+ /**
+ * Default implementation of the
+ * <code>ServiceTrackerCustomizer.addingService</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>ServiceTracker</code> has been
+ * constructed with a <code>null ServiceTrackerCustomizer</code> argument.
+ *
+ * <p>
+ * This implementation returns the result of calling <code>getService</code>
+ * on the <code>BundleContext</code> with which this
+ * <code>ServiceTracker</code> was created passing the specified
+ * <code>ServiceReference</code>.
+ * <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
+ * {@link #removedService(ServiceReference, Object) removedService} to unget
+ * the service.
+ *
+ * @param reference The reference to the service being added to this
+ * <code>ServiceTracker</code>.
+ * @return The service object to be tracked for the service added to this
+ * <code>ServiceTracker</code>.
+ * @see ServiceTrackerCustomizer#addingService(ServiceReference)
+ */
+ public Object addingService(ServiceReference reference) {
+ return context.getService(reference);
+ }
+
+ public void addedService(ServiceReference reference, Object service) {
+ /* do nothing */
+ }
+
+ /**
+ * Default implementation of the
+ * <code>ServiceTrackerCustomizer.modifiedService</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>ServiceTracker</code> has been
+ * constructed with a <code>null ServiceTrackerCustomizer</code> argument.
+ *
+ * <p>
+ * This implementation does nothing.
+ *
+ * @param reference The reference to modified service.
+ * @param service The service object for the modified service.
+ * @see ServiceTrackerCustomizer#modifiedService(ServiceReference, Object)
+ */
+ public void modifiedService(ServiceReference reference, Object service) {
+ /* do nothing */
+ }
+
+ /**
+ * Default implementation of the
+ * <code>ServiceTrackerCustomizer.removedService</code> method.
+ *
+ * <p>
+ * This method is only called when this <code>ServiceTracker</code> has been
+ * constructed with a <code>null ServiceTrackerCustomizer</code> argument.
+ *
+ * <p>
+ * This implementation calls <code>ungetService</code>, on the
+ * <code>BundleContext</code> with which this <code>ServiceTracker</code>
+ * was created, passing the specified <code>ServiceReference</code>.
+ * <p>
+ * This method can be overridden in a subclass. If the default
+ * implementation of {@link #addingService(ServiceReference) addingService}
+ * method was used, this method must unget the service.
+ *
+ * @param reference The reference to removed service.
+ * @param service The service object for the removed service.
+ * @see ServiceTrackerCustomizer#removedService(ServiceReference, Object)
+ */
+ public void removedService(ServiceReference reference, Object service) {
+ context.ungetService(reference);
+ }
+
+ /**
+ * Wait for at least one service to be tracked by this
+ * <code>ServiceTracker</code>. This method will also return when this
+ * <code>ServiceTracker</code> is closed.
+ *
+ * <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.
+ *
+ * <p>
+ * This implementation calls {@link #getService()} to determine if a service
+ * is being tracked.
+ *
+ * @param timeout The time interval in milliseconds to wait. If zero, the
+ * method will wait indefinitely.
+ * @return Returns the result of {@link #getService()}.
+ * @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");
+ }
+ Object object = getService();
+ while (object == null) {
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (t) {
+ if (t.size() == 0) {
+ t.wait(timeout);
+ }
+ }
+ object = getService();
+ if (timeout > 0) {
+ return object;
+ }
+ }
+ return object;
+ }
+
+ /**
+ * Return an array of <code>ServiceReference</code>s for all services being
+ * tracked by this <code>ServiceTracker</code>.
+ *
+ * @return Array of <code>ServiceReference</code>s or <code>null</code> if
+ * no services are being tracked.
+ */
+ public ServiceReference[] getServiceReferences() {
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (t) {
+ int length = t.size();
+ if (length == 0) {
+ return null;
+ }
+ return (ServiceReference[]) t
+ .getTracked(new ServiceReference[length]);
+ }
+ }
+
+ /**
+ * Returns a boolean indicating whether this <code>ServiceTracker</code> is tracking any services.
+ *
+ * @return true if services are being tracked, false if no services are being tracked.
+ */
+ public boolean hasReference() {
+ if (cachedReference != null) {
+ return true;
+ }
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return false;
+ }
+ synchronized (t) {
+ int length = t.size();
+ return length > 0;
+ }
+ }
+
+ /**
+ * Returns a <code>ServiceReference</code> for one of the services being
+ * tracked by this <code>ServiceTracker</code>.
+ *
+ * <p>
+ * If multiple services are being tracked, the service with the highest
+ * ranking (as specified in its <code>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>service.id</code> property); that
+ * is, the service that was registered first is returned. This is the same
+ * algorithm used by <code>BundleContext.getServiceReference</code>.
+ *
+ * <p>
+ * This implementation calls {@link #getServiceReferences()} to get the list
+ * of references for the tracked services.
+ *
+ * @return A <code>ServiceReference</code> or <code>null</code> if no
+ * services are being tracked.
+ * @since 1.1
+ */
+ public ServiceReference getServiceReference() {
+ ServiceReference reference = cachedReference;
+ if (reference != null) {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.getServiceReference[cached]: "
+ + filter);
+ }
+ return reference;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.getServiceReference: " + filter);
+ }
+ 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> if the specified referenced service is
+ * being tracked by this <code>ServiceTracker</code>.
+ *
+ * @param reference The reference to the desired service.
+ * @return A service object or <code>null</code> if the service referenced
+ * by the specified <code>ServiceReference</code> is not being
+ * tracked.
+ */
+ public Object getService(ServiceReference reference) {
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (t) {
+ return t.getCustomizedObject(reference);
+ }
+ }
+
+ /**
+ * Return an array of service objects for all services being tracked by this
+ * <code>ServiceTracker</code>.
+ *
+ * <p>
+ * This implementation calls {@link #getServiceReferences()} to get the list
+ * of references for the tracked services and then calls
+ * {@link #getService(ServiceReference)} for each reference to get the
+ * tracked service object.
+ *
+ * @return An array of service objects or <code>null</code> if no services
+ * are being tracked.
+ */
+ public Object[] getServices() {
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return null;
+ }
+ synchronized (t) {
+ 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>.
+ *
+ * <p>
+ * If any services are being tracked, this implementation returns the result
+ * of calling <code>getService(getServiceReference())</code>.
+ *
+ * @return A service object or <code>null</code> if no services are being
+ * tracked.
+ */
+ public Object getService() {
+ Object service = cachedService;
+ if (service != null) {
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.getService[cached]: "
+ + filter);
+ }
+ return service;
+ }
+ if (DEBUG) {
+ System.out.println("ServiceTracker.getService: " + filter);
+ }
+ ServiceReference reference = getServiceReference();
+ if (reference == null) {
+ return null;
+ }
+ return cachedService = getService(reference);
+ }
+
+ /**
+ * Remove a service from this <code>ServiceTracker</code>.
+ *
+ * The specified service will be removed from this
+ * <code>ServiceTracker</code>. If the specified service was being tracked
+ * then the <code>ServiceTrackerCustomizer.removedService</code> method will
+ * be called for that service.
+ *
+ * @param reference The reference to the service to be removed.
+ */
+ public void remove(ServiceReference reference) {
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return;
+ }
+ t.untrack(reference, null).execute();
+ }
+
+ /**
+ * Return the number of services being tracked by this
+ * <code>ServiceTracker</code>.
+ *
+ * @return The number of services being tracked.
+ */
+ public int size() {
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return 0;
+ }
+ synchronized (t) {
+ return t.size();
+ }
+ }
+
+ /**
+ * Returns the tracking count for this <code>ServiceTracker</code>.
+ *
+ * The tracking count is initialized to 0 when this
+ * <code>ServiceTracker</code> is opened. Every time a service is added,
+ * modified or removed from this <code>ServiceTracker</code>, the tracking
+ * count is incremented.
+ *
+ * <p>
+ * The tracking count can be used to determine if this
+ * <code>ServiceTracker</code> has added, modified 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, modified or removed from this <code>ServiceTracker</code>
+ * since the previous tracking count was collected.
+ *
+ * @since 1.2
+ * @return The tracking count for this <code>ServiceTracker</code> or -1 if
+ * this <code>ServiceTracker</code> is not open.
+ */
+ public int getTrackingCount() {
+ final Tracked t = tracked();
+ if (t == null) { /* if ServiceTracker is not open */
+ return -1;
+ }
+ synchronized (t) {
+ return t.getTrackingCount();
+ }
+ }
+
+ /**
+ * Called by the Tracked object whenever the set of tracked services is
+ * modified. 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 listener thread and the user thread.
+ */
+ void modified() {
+ cachedReference = null; /* clear cached value */
+ cachedService = null; /* clear cached value */
+ if (DEBUG) {
+ System.out.println("ServiceTracker.modified: " + filter);
+ }
+ }
+
+ /**
+ * Inner class which subclasses AbstractTracked. This class is the
+ * <code>ServiceListener</code> object for the tracker.
+ *
+ * @ThreadSafe
+ */
+ class Tracked extends AbstractTracked implements ServiceListener {
+ /**
+ * A list of services that are currently hidden because there is an aspect available with a higher ranking.
+ * @GuardedBy this
+ */
+ private final Map<Long, TreeSet<ServiceReference>> m_highestTrackedCache = new HashMap<>();
+ private final Map<Long, TreeSet<ServiceReference>> m_highestHiddenCache = new HashMap<>();
+
+ private ServiceReference highestTrackedCache(long serviceId) {
+ Long sid = Long.valueOf(serviceId);
+ synchronized (this) {
+ TreeSet<ServiceReference> services = m_highestTrackedCache.get(sid);
+ if (services != null && services.size() > 0) {
+ ServiceReference result = (ServiceReference) services.last();
+ return result;
+ }
+ }
+ return null;
+ }
+
+ private void addHighestTrackedCache(ServiceReference reference) {
+ Long serviceId = ServiceUtil.getServiceIdObject(reference);
+ synchronized (this) {
+ TreeSet<ServiceReference> services = m_highestTrackedCache.get(serviceId);
+ if (services == null) {
+ services = new TreeSet<ServiceReference>();
+ m_highestTrackedCache.put(serviceId, services);
+ }
+ services.add(reference);
+ }
+ }
+
+ private void removeHighestTrackedCache(ServiceReference reference) {
+ Long serviceId = ServiceUtil.getServiceIdObject(reference);
+ synchronized (this) {
+ TreeSet<ServiceReference> services = m_highestTrackedCache.get(serviceId);
+ if (services != null) {
+ services.remove(reference);
+ }
+ }
+ }
+
+ private void clearHighestTrackedCache() {
+ synchronized (this) {
+ m_highestTrackedCache.clear();
+ }
+ }
+
+ private ServiceReference highestHiddenCache(long serviceId) {
+ Long sid = Long.valueOf(serviceId);
+ synchronized (this) {
+ TreeSet<ServiceReference> services = m_highestHiddenCache.get(sid);
+ if (services != null && services.size() > 0) {
+ ServiceReference result = (ServiceReference) services.last();
+ return result;
+ }
+ }
+ return null;
+ }
+
+ private void addHighestHiddenCache(ServiceReference reference) {
+ Long serviceId = ServiceUtil.getServiceIdObject(reference);
+ synchronized (this) {
+ TreeSet<ServiceReference> services = m_highestHiddenCache.get(serviceId);
+ if (services == null) {
+ services = new TreeSet<ServiceReference>();
+ m_highestHiddenCache.put(serviceId, services);
+ }
+ services.add(reference);
+ }
+ }
+
+ private void removeHighestHiddenCache(ServiceReference reference) {
+ Long serviceId = ServiceUtil.getServiceIdObject(reference);
+ synchronized (this) {
+ TreeSet<ServiceReference> services = m_highestHiddenCache.get(serviceId);
+ if (services != null) {
+ services.remove(reference);
+ }
+ }
+ }
+
+ /**
+ * Hide a service reference, placing it in the list of hidden services.
+ *
+ * @param ref the service reference to add to the hidden list
+ */
+ private void hide(ServiceReference ref) {
+ addHighestHiddenCache(ref);
+ }
+
+ /**
+ * Unhide a service reference, removing it from the list of hidden services.
+ *
+ * @param ref the service reference to remove from the hidden list
+ */
+ private void unhide(ServiceReference ref) {
+ removeHighestHiddenCache(ref);
+ }
+
+ /**
+ * Tracked constructor.
+ */
+ Tracked() {
+ super();
+ setTracked(new HashMapCache<Object, Object>());
+ }
+
+ void setInitial(Object[] list) {
+ if (list == null) {
+ return;
+ }
+ if (m_trackAllAspects) {
+ // not hiding aspects
+ super.setInitial(list);
+ } else {
+ Map<Long, RankedService> highestRankedServiceMap = new HashMap<>();
+ for (int i = 0; i < list.length; i++) {
+ ServiceReference sr = (ServiceReference) list[i];
+ if (sr != null) {
+ Long serviceId = ServiceUtil.getServiceIdAsLong(sr);
+ int ranking = ServiceUtil.getRanking(sr);
+
+ RankedService rs = (RankedService) highestRankedServiceMap.get(serviceId);
+ if (rs == null) {
+ // the service did not exist yet in our map
+ highestRankedServiceMap.put(serviceId, new RankedService(ranking, sr));
+ }
+ else if (ranking > rs.getRanking()) {
+ // the service replaces a lower ranked one
+ hide(rs.getServiceReference());
+ rs.update(ranking, sr);
+ }
+ else {
+ // the service does NOT replace a lower ranked one
+ hide(sr);
+ }
+ }
+ }
+ if (highestRankedServiceMap.size() > 0) {
+ Object[] result = new Object[highestRankedServiceMap.size()];
+ int index = 0;
+ for(Iterator<Entry<Long, RankedService>> it = highestRankedServiceMap.entrySet().iterator(); it.hasNext(); ) {
+ Entry<Long, RankedService> entry = it.next();
+ result[index] = ((RankedService)entry.getValue()).getServiceReference();
+ index++;
+ }
+ super.setInitial(result);
+ }
+ }
+ }
+
+ /**
+ * <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(final ServiceEvent event) {
+ if (m_trackAllAspects) {
+ serviceChangedIncludeAspects(event);
+ }
+ else {
+ serviceChangedHideAspects(event);
+ }
+ }
+
+ public void serviceChangedIncludeAspects(final ServiceEvent event) {
+ /*
+ * Check if we had a delayed call (which could happen when we
+ * close).
+ */
+ if (closed) {
+ return;
+ }
+ final ServiceReference reference = event.getServiceReference();
+ if (debug) {
+ System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " [serviceChangedIncludeAspects] " + reference.getProperty("service.ranking"));
+ }
+ if (DEBUG) {
+ System.out
+ .println("ServiceTracker.Tracked.serviceChanged["
+ + event.getType() + "]: " + reference);
+ }
+
+ switch (event.getType()) {
+ case ServiceEvent.REGISTERED :
+ case ServiceEvent.MODIFIED :
+ if (listenerFilter != null) { // service listener added with
+ // filter
+ track(reference, event).execute();
+ /*
+ * If the customizer throws an unchecked exception, it
+ * is safe to let it propagate
+ */
+ }
+ else { // service listener added without filter
+ if (filter.match(reference)) {
+ track(reference, event).execute();
+ /*
+ * If the customizer throws an unchecked exception,
+ * it is safe to let it propagate
+ */
+ }
+ else {
+ untrack(reference, event).execute();
+ /*
+ * If the customizer throws an unchecked exception,
+ * it is safe to let it propagate
+ */
+ }
+ }
+ break;
+ case 8 /* ServiceEvent.MODIFIED_ENDMATCH */ :
+ case ServiceEvent.UNREGISTERING :
+ untrack(reference, event).execute();
+ /*
+ * If the customizer throws an unchecked exception, it is
+ * safe to let it propagate
+ */
+ break;
+ }
+ }
+
+ private boolean isModifiedEndmatchSupported() {
+ return listenerFilter != null;
+ }
+
+ private AtomicInteger step = new AtomicInteger();
+
+ public void serviceChangedHideAspects(final ServiceEvent event) {
+ int n = step.getAndIncrement();
+ /*
+ * Check if we had a delayed call (which could happen when we
+ * close).
+ */
+ if (closed) {
+ return;
+ }
+ final ServiceReference reference = event.getServiceReference();
+ if (DEBUG) {
+ System.out
+ .println(n + " ServiceTracker.Tracked.serviceChanged["
+ + event.getType() + "]: " + reference);
+ }
+
+ long sid = ServiceUtil.getServiceId(reference);
+ AbstractCustomizerActionSet actionSet = null;
+ synchronized(this) {
+ switch (event.getType()) {
+ case ServiceEvent.REGISTERED :
+ case ServiceEvent.MODIFIED :
+ ServiceReference higherRankedReference = null;
+ ServiceReference lowerRankedReference = null;
+ ServiceReference highestTrackedReference = highestTrackedCache(sid);
+ if (highestTrackedReference != null) {
+ int ranking = ServiceUtil.getRanking(reference);
+ int highestTrackedRanking = ServiceUtil.getRanking(highestTrackedReference);
+ if (ranking > highestTrackedRanking) {
+ // found a higher ranked one!
+ if (DEBUG) {
+ System.out.println("ServiceTracker.Tracked.serviceChanged[" + event.getType() + "]: Found a higher ranked aspect: " + ServiceUtil.toString(reference) + " vs " + ServiceUtil.toString(highestTrackedReference));
+ }
+ higherRankedReference = highestTrackedReference;
+ }
+ else if (ranking < highestTrackedRanking) {
+ // found lower ranked one!
+ if (DEBUG) {
+ System.out.println("ServiceTracker.Tracked.serviceChanged[" + event.getType() + "]: Found a lower ranked aspect: " + ServiceUtil.toString(reference) + " vs " + ServiceUtil.toString(highestTrackedReference));
+ }
+ lowerRankedReference = highestTrackedReference;
+ }
+ }
+ if (isModifiedEndmatchSupported()) { // either registered or modified
+ actionSet = registerOrUpdate(event, reference, higherRankedReference, lowerRankedReference);
+ }
+ else { // service listener added without filter
+ if (filter.match(reference)) {
+ actionSet = registerOrUpdate(event, reference, higherRankedReference, lowerRankedReference);
+ }
+ else {
+ actionSet = unregister(event, reference, sid);
+ }
+ }
+ break;
+ case 8 /* ServiceEvent.MODIFIED_ENDMATCH */ : // handle as unregister
+ case ServiceEvent.UNREGISTERING :
+ actionSet = unregister(event, reference, sid);
+ /*
+ * If the customizer throws an unchecked exception, it is
+ * safe to let it propagate
+ */
+ break;
+ }
+ // schedule the actionset for execution. We'll use a serial executor to prevent the actions to
+ // be performed out of order.
+ final AbstractCustomizerActionSet commandActionSet = actionSet;
+ if (commandActionSet != null) {
+ getExecutor().schedule(new Runnable() {
+
+ @Override
+ public void run() {
+ commandActionSet.execute();
+ }
+
+ });
+ }
+ }
+ getExecutor().execute();
+ }
+
+ private AbstractCustomizerActionSet registerOrUpdate(final ServiceEvent event,
+ final ServiceReference reference, ServiceReference higher,
+ ServiceReference lower) {
+ if (debug) {
+// System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " [registerOrUpdate] lower: " + lower + ", higher: " + higher);
+ }
+ AbstractCustomizerActionSet actionSet = null;
+ if (lower != null) {
+ hide(reference);
+ }
+ else {
+ actionSet = track(reference, event);
+ if (higher != null) {
+ actionSet.appendActionSet(untrack(higher, null));
+ hide(higher);
+ }
+ }
+ /*
+ * If the customizer throws an unchecked exception, it
+ * is safe to let it propagate
+ */
+ return actionSet;
+ }
+
+ private AbstractCustomizerActionSet unregister(final ServiceEvent event,
+ final ServiceReference reference, long sid) {
+ if (debug) {
+ System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " [unregister] " + reference.getProperty("service.ranking"));
+ }
+ AbstractCustomizerActionSet actionSet = null;
+ ServiceReference ht = highestTrackedCache(sid);
+ if (reference.equals(ht)) {
+ ServiceReference hh = highestHiddenCache(sid);
+
+ if (hh != null) {
+ unhide(hh);
+ actionSet = track(hh, null);
+ }
+ if (actionSet == null) {
+ actionSet = untrack(reference, event);
+ } else {
+ actionSet.appendActionSet(untrack(reference, event));
+ }
+ }
+ else {
+ unhide(reference);
+ }
+ return actionSet;
+ }
+
+
+
+ /**
+ * Increment the tracking count and tell the tracker there was a
+ * modification.
+ *
+ * @GuardedBy this
+ */
+ void modified() {
+ super.modified(); /* increment the modification count */
+ ServiceTracker.this.modified();
+ }
+
+ /**
+ * Call the specific customizer adding method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Item to be tracked.
+ * @param related Action related object.
+ * @return Customized object for the tracked item or <code>null</code>
+ * if the item is not to be tracked.
+ */
+ Object customizerAdding(final Object item,
+ final Object related) {
+ return customizer.addingService((ServiceReference) item);
+ }
+
+ void customizerAdded(final Object item, final Object related, final Object object) {
+ customizer.addedService((ServiceReference) item, object);
+ }
+
+ /**
+ * Call the specific customizer modified method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Tracked item.
+ * @param related Action related object.
+ * @param object Customized object for the tracked item.
+ */
+ void customizerModified(final Object item,
+ final Object related, final Object object) {
+ customizer.modifiedService((ServiceReference) item, object);
+ }
+
+ /**
+ * Call the specific customizer removed method. This method must not be
+ * called while synchronized on this object.
+ *
+ * @param item Tracked item.
+ * @param related Action related object.
+ * @param object Customized object for the tracked item.
+ */
+ void customizerRemoved(final Object item,
+ final Object related, final Object object) {
+ customizer.removedService((ServiceReference) item, object);
+ }
+
+ class HashMapCache<K, V> extends LinkedHashMap<K, V> {
+
+ private static final long serialVersionUID = 1627005136730183946L;
+
+ public V put(K key, V value) {
+ addHighestTrackedCache((ServiceReference) key);
+ return super.put(key, value);
+ }
+
+ public void putAll(Map<? extends K, ? extends V> m) {
+ Iterator<? extends K> i = m.keySet().iterator();
+ while (i.hasNext()) {
+ addHighestTrackedCache((ServiceReference) i.next());
+ }
+ super.putAll(m);
+ }
+
+ public V remove(Object key) {
+ removeHighestTrackedCache((ServiceReference) key);
+ return super.remove(key);
+ }
+
+ public void clear() {
+ clearHighestTrackedCache();
+ super.clear();
+ }
+ }
+
+ @Override
+ AbstractCustomizerActionSet createCustomizerActionSet() {
+ // This actions set deliberately postpones invocation of the customizer methods to be able to combine added and removed
+ // into a single swap call.
+ return new AbstractCustomizerActionSet() {
+
+ @Override
+ public void addCustomizerAdded(Object item, Object related,
+ Object object) {
+ if (debug) {
+// System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " addCustomizerAdded " + object);
+ }
+ super.addCustomizerAdded(item, related, object);
+ }
+
+ @Override
+ public void addCustomizerModified(Object item, Object related,
+ Object object) {
+ if (debug) {
+// System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " addCustomizerModified " + object);
+ }
+ super.addCustomizerModified(item, related, object);
+ }
+
+ @Override
+ public void addCustomizerRemoved(Object item, Object related,
+ Object object) {
+ if (debug) {
+// System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " addCustomizerRemoved " + object);
+ }
+ super.addCustomizerRemoved(item, related, object);
+ }
+
+ @Override
+ void execute() {
+ // inspect the actions and check whether we should perform a swap
+ List<CustomizerAction> actions = getActions();
+ if (actions.size() > 2) {
+ throw new IllegalStateException("Unexpected action count: " + actions.size());
+ }
+ if (actions.size() == 2 && actions.get(0).getType() == Type.ADDED && actions.get(1).getType() == Type.REMOVED) {
+ // ignore related
+ // item = ServiceReference
+ // object = service
+ debug("swapped");
+ if (debug) {
+ System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " swapping " + actions.get(1).getObject() + " with " + actions.get(0).getObject());
+ }
+ customizer.swappedService((ServiceReference)actions.get(1).getItem(), actions.get(1).getObject(), (ServiceReference)actions.get(0).getItem(), actions.get(0).getObject());
+ } else {
+ // just sequentially call the customizer methods
+ for (CustomizerAction action : getActions()) {
+ try {
+ switch (action.getType()) {
+ case ADDED:
+ debug(Thread.currentThread().getId() + " added");
+ if (debug) {
+ System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " adding " + action.getObject());
+ }
+ customizerAdded(action.getItem(), action.getRelated(), action.getObject());
+ break;
+ case MODIFIED:
+ debug("modified");
+ customizerModified(action.getItem(), action.getRelated(), action.getObject());
+ break;
+ case REMOVED:
+ debug("removed");
+ if (debug) {
+ System.out.println("[ServiceTracker] " + debugKey + " T" + Thread.currentThread().getId() + " removing " + action.getObject());
+ }
+ customizerRemoved(action.getItem(), action.getRelated(), action.getObject());
+ }
+ } catch (Exception e) {
+ // just continue. log messages will be printed elsewhere.
+ }
+ }
+ }
+ }
+ };
+ }
+
+ }
+
+ private void debug(String message) {
+ if (customizer.toString().equals("ServiceDependency[interface dm.it.AspectRaceTest$S (&(!(org.apache.felix.dependencymanager.aspect=*))(id=1))]")) {
+// System.out.println(message);
+ }
+ }
+
+ /**
+ * Subclass of Tracked which implements the AllServiceListener interface.
+ * This class is used by the ServiceTracker if open is called with true.
+ *
+ * @since 1.3
+ * @ThreadSafe
+ */
+ class AllTracked extends Tracked implements AllServiceListener {
+ /**
+ * AllTracked constructor.
+ */
+ AllTracked() {
+ super();
+ setTracked(new HashMapCache<Object, Object>());
+ }
+ }
+
+ /**
+ * Holds a ranking and a service reference that can be updated if necessary.
+ */
+ private static final class RankedService {
+ private int m_ranking;
+ private ServiceReference m_serviceReference;
+
+ public RankedService(int ranking, ServiceReference serviceReference) {
+ m_ranking = ranking;
+ m_serviceReference = serviceReference;
+ }
+
+ public void update(int ranking, ServiceReference serviceReference) {
+ m_ranking = ranking;
+ m_serviceReference = serviceReference;
+ }
+
+ public int getRanking() {
+ return m_ranking;
+ }
+
+ public ServiceReference getServiceReference() {
+ return m_serviceReference;
+ }
+ }
+
+ @Override
+ public void swappedService(ServiceReference reference, Object service,
+ ServiceReference newReference, Object newService) {
+
+ }
+
+ // Package private, used for unit testing Tracked
+ Tracked getTracked() {
+ return tracked;
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/ServiceTrackerCustomizer.java b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/ServiceTrackerCustomizer.java
new file mode 100644
index 0000000..ab3abd2
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/ServiceTrackerCustomizer.java
@@ -0,0 +1,115 @@
+package org.apache.felix.dm.tracker;
+/*
+ * Copyright (c) OSGi Alliance (2000, 2008). 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.
+ */
+
+import org.osgi.framework.ServiceReference;
+
+/**
+ * The <code>ServiceTrackerCustomizer</code> interface allows a
+ * <code>ServiceTracker</code> to customize the service objects that are
+ * tracked. A <code>ServiceTrackerCustomizer</code> is called when a service is
+ * being added to a <code>ServiceTracker</code>. The
+ * <code>ServiceTrackerCustomizer</code> can then return an object for the
+ * tracked service. A <code>ServiceTrackerCustomizer</code> is also called when
+ * a tracked service is modified or has been removed from a
+ * <code>ServiceTracker</code>.
+ *
+ * <p>
+ * The methods in this interface may be called as the result of a
+ * <code>ServiceEvent</code> being received by a <code>ServiceTracker</code>.
+ * 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.
+ *
+ * <p>
+ * The <code>ServiceTracker</code> class is thread-safe. It does not call a
+ * <code>ServiceTrackerCustomizer</code> while holding any locks.
+ * <code>ServiceTrackerCustomizer</code> implementations must also be
+ * thread-safe.
+ *
+ * @ThreadSafe
+ * @version $Revision: 5874 $
+ */
+public interface ServiceTrackerCustomizer {
+ /**
+ * A service is being added to the <code>ServiceTracker</code>.
+ *
+ * <p>
+ * This method is called before a service which matched the search
+ * parameters of the <code>ServiceTracker</code> is added to the
+ * <code>ServiceTracker</code>. This method should return the service object
+ * to be tracked for the specified <code>ServiceReference</code>. The
+ * returned service object is stored in the <code>ServiceTracker</code> and
+ * is available from the <code>getService</code> and
+ * <code>getServices</code> methods.
+ *
+ * @param reference The reference to the service being added to the
+ * <code>ServiceTracker</code>.
+ * @return The service object to be tracked for the specified referenced
+ * service or <code>null</code> if the specified referenced service
+ * should not be tracked.
+ */
+ public Object addingService(ServiceReference reference);
+
+ /** marrs: A service has been added to the ServiceTracker. */
+ public void addedService(ServiceReference reference, Object service);
+
+ /**
+ * A service tracked by the <code>ServiceTracker</code> has been modified.
+ *
+ * <p>
+ * This method is called when a service being tracked by the
+ * <code>ServiceTracker</code> has had it properties modified.
+ *
+ * @param reference The reference to the service that has been modified.
+ * @param service The service object for the specified referenced service.
+ */
+ public void modifiedService(ServiceReference reference, Object service);
+
+ /**
+ * A service tracked by the <code>ServiceTracker</code> has an aspect service
+ * added or removed for a tracked service.
+ *
+ * <p>
+ * This method is called when an aspect service has been either added or removed
+ * for a tracked service. This method will only be called when there's a new
+ * highest ranked service as result of adding or removal of the aspect service.
+ * In this case the previously highest ranked service is 'swapped' for the new
+ * highest ranked service ensuring the client always gets the highest ranked
+ * aspect.
+ *
+ * @param reference The reference for the old highest ranked service.
+ * @param service The service object for the old highest ranked service.
+ * @param newReference The reference to the new highest ranked service.
+ * @param newService The service object for the new highest ranked service.
+ */
+ public void swappedService(ServiceReference reference, Object service, ServiceReference newReference, Object newService);
+
+ /**
+ * A service tracked by the <code>ServiceTracker</code> has been removed.
+ *
+ * <p>
+ * This method is called after a service is no longer being tracked by the
+ * <code>ServiceTracker</code>.
+ *
+ * @param reference The reference to the service that has been removed.
+ * @param service The service object for the specified referenced service.
+ */
+ public void removedService(ServiceReference reference, Object service);
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/packageinfo b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/packageinfo
new file mode 100644
index 0000000..bd33369
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/src/org/apache/felix/dm/tracker/packageinfo
@@ -0,0 +1 @@
+version 4.0.0
\ No newline at end of file
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/.gitignore b/dependencymanager/org.apache.felix.dependencymanager/test/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/.gitignore
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/tracker/TrackedTest.java b/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/tracker/TrackedTest.java
new file mode 100644
index 0000000..9b71939
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/org/apache/felix/dm/tracker/TrackedTest.java
@@ -0,0 +1,316 @@
+/*
+ * 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.dm.tracker;
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import org.apache.felix.dm.DependencyManager;
+import org.apache.felix.dm.tracker.ServiceTracker;
+import org.apache.felix.dm.tracker.ServiceTrackerCustomizer;
+import org.apache.felix.dm.tracker.ServiceTracker.Tracked;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class TrackedTest {
+
+ @Test
+ public void testSetInitialHideAspects() {
+ System.out.println("testSetInitialHideAspects");
+ TestCustomizer customizer = new TestCustomizer();
+
+ ServiceTracker tracker = new TestTracker(customizer);
+ tracker.open();
+ Tracked tracked = tracker.getTracked();
+
+ Object[] initialReferences = new Object[] {
+ createServiceReference(1L),
+ createServiceReference(2L, 1L, 10),
+ createServiceReference(3L),
+ createServiceReference(4L, 1L, 5),
+ createServiceReference(5L, 3L, 5),
+ };
+ tracked.setInitial(initialReferences);
+ tracked.trackInitial();
+ tracked.getExecutor().execute();
+ assertArrayEquals(new Long[] { 2L, 5L }, customizer.getServiceReferenceIds());
+ }
+
+ @Test
+ public void testUnHideAspect() {
+ System.out.println("testUnhideAspect");
+ TestCustomizer customizer = new TestCustomizer();
+
+ ServiceTracker tracker = new TestTracker(customizer);
+ tracker.open();
+ Tracked tracked = tracker.getTracked();
+
+ ServiceReference[] initialReferences = new ServiceReference[] {
+ createServiceReference(1L),
+ createServiceReference(2L, 1L, 10),
+ createServiceReference(3L),
+ createServiceReference(4L, 1L, 5),
+ createServiceReference(5L, 3L, 5),
+ };
+ tracked.setInitial(initialReferences);
+ tracked.trackInitial();
+ tracked.getExecutor().execute();
+ assertArrayEquals(new Long[] { 2L, 5L }, customizer.getServiceReferenceIds());
+
+ // create a service event that unregisters service with id 2, we would expect it to be swapped with 4.
+ ServiceEvent event = new ServiceEvent(ServiceEvent.UNREGISTERING, initialReferences[1]);
+ tracked.serviceChanged(event);
+ assertArrayEquals(new Long[] { 5L, 4L }, customizer.getServiceReferenceIds());
+ // create a service event that unregisters service with id 4, we would expect it to be swapped with 1.
+ event = new ServiceEvent(ServiceEvent.UNREGISTERING, initialReferences[3]);
+ tracked.serviceChanged(event);
+ assertArrayEquals(new Long[] { 5L, 1L }, customizer.getServiceReferenceIds());
+ }
+
+ @Test
+ public void testHideAspect() {
+ System.out.println("testHideAspect");
+ TestCustomizer customizer = new TestCustomizer();
+
+ ServiceTracker tracker = new TestTracker(customizer);
+ tracker.open();
+ Tracked tracked = tracker.getTracked();
+
+ ServiceReference[] initialReferences = new ServiceReference[] {
+ createServiceReference(1L),
+ createServiceReference(2L, 1L, 10),
+ createServiceReference(3L),
+ createServiceReference(4L, 1L, 5),
+ createServiceReference(5L, 3L, 5),
+ };
+ tracked.setInitial(initialReferences);
+ tracked.trackInitial();
+ tracked.getExecutor().execute();
+ assertArrayEquals(new Long[] { 2L, 5L }, customizer.getServiceReferenceIds());
+
+ // create a service event that registers another but lower ranked aspect for service with id 1.
+ ServiceReference newReference = createServiceReference(6L, 1L, 8);
+ ServiceEvent event = new ServiceEvent(ServiceEvent.REGISTERED, newReference);
+ tracked.serviceChanged(event);
+ assertArrayEquals(new Long[] { 2L, 5L }, customizer.getServiceReferenceIds());
+
+ // create a service event that unregisters service with id 2, we would expect it to be swapped with 6.
+ event = new ServiceEvent(ServiceEvent.UNREGISTERING, initialReferences[1]);
+ tracked.serviceChanged(event);
+ assertArrayEquals(new Long[] { 5L, 6L }, customizer.getServiceReferenceIds());
+
+ // create a service event that unregisters service with id 6, we would expect it to be swapped with 4.
+ event = new ServiceEvent(ServiceEvent.UNREGISTERING, newReference);
+ tracked.serviceChanged(event);
+ assertArrayEquals(new Long[] { 5L, 4L }, customizer.getServiceReferenceIds());
+
+ // create a service event that registers a higher ranked aspect for service with id 1.
+ ServiceReference higherRankedReference = createServiceReference(7L, 1L, 15);
+ ServiceEvent addHigherRankedEvent = new ServiceEvent(ServiceEvent.REGISTERED, higherRankedReference);
+ tracked.serviceChanged(addHigherRankedEvent);
+ assertArrayEquals(new Long[] { 5L, 7L }, customizer.getServiceReferenceIds());
+ }
+
+ @Test
+ public void testSetInitialTrackAspects() {
+ System.out.println("testSetInitialTrackAspects");
+ TestCustomizer customizer = new TestCustomizer();
+
+ ServiceTracker tracker = new TestTracker(customizer);
+ tracker.open(false, true);
+ Tracked tracked = tracker.getTracked();
+
+ Object[] initialReferences = new Object[] {
+ createServiceReference(1L),
+ createServiceReference(2L, 1L, 10),
+ createServiceReference(3L, 1L, 5)
+ };
+ tracked.setInitial(initialReferences);
+ tracked.trackInitial();
+ tracked.getExecutor().execute();
+ assertArrayEquals(new Long[] { 1L, 2L, 3L }, customizer.getServiceReferenceIds());
+ }
+
+ private static BundleContext createBundleContext() {
+ BundleContext context = mock(BundleContext.class);
+ when(context.getProperty(Constants.FRAMEWORK_VERSION)).thenReturn(null);
+ return context;
+ }
+
+ private ServiceReference createServiceReference(Long serviceId) {
+ return createServiceReference(serviceId, null, null);
+ }
+
+ private ServiceReference createServiceReference(Long serviceId, Long aspectId, Integer ranking) {
+ return new TestServiceReference(serviceId, aspectId, ranking);
+ }
+
+ class TestTracker extends ServiceTracker {
+
+ public TestTracker(ServiceTrackerCustomizer customizer) {
+ super(createBundleContext(), "(objectClass=*)", customizer);
+ }
+
+ }
+
+ class TestCustomizer implements ServiceTrackerCustomizer {
+
+ List<ServiceReference> serviceReferences = new ArrayList<>();
+
+ @Override
+ public Object addingService(ServiceReference reference) {
+ System.out.println("adding service: " + reference);
+ return new Object();
+ }
+
+ @Override
+ public void addedService(ServiceReference reference, Object service) {
+ System.out.println("added service: " + reference);
+ serviceReferences.add(reference);
+ }
+
+ @Override
+ public void modifiedService(ServiceReference reference, Object service) {
+ System.out.println("modified service: " + reference);
+ }
+
+ @Override
+ public void swappedService(ServiceReference reference, Object service,
+ ServiceReference newReference, Object newService) {
+ System.out.println("swapped service: " + reference);
+ serviceReferences.remove(reference);
+ serviceReferences.add(newReference);
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service) {
+ System.out.println("removed service: " + reference);
+ serviceReferences.remove(reference);
+ }
+
+ public Long[] getServiceReferenceIds() {
+ Long[] ids = new Long[serviceReferences.size()];
+ for (int i = 0; i < serviceReferences.size(); i++) {
+ ids[i] = (Long) serviceReferences.get(i).getProperty(Constants.SERVICE_ID);
+ }
+ return ids;
+ }
+
+ }
+
+ class TestServiceReference implements ServiceReference {
+
+ Properties props = new Properties();
+
+ public TestServiceReference(Long serviceId, Long aspectId,
+ Integer ranking) {
+ props.put(Constants.SERVICE_ID, serviceId);
+ if (aspectId != null) {
+ props.put(DependencyManager.ASPECT, aspectId);
+ }
+ if (ranking != null) {
+ props.put(Constants.SERVICE_RANKING, ranking);
+ }
+ }
+
+ @Override
+ public Object getProperty(String key) {
+ return props.get(key);
+ }
+
+ @Override
+ public String[] getPropertyKeys() {
+ return props.keySet().toArray(new String[]{});
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return null;
+ }
+
+ @Override
+ public Bundle[] getUsingBundles() {
+ return null;
+ }
+
+ @Override
+ public boolean isAssignableTo(Bundle bundle, String className) {
+ return false;
+ }
+
+ @Override
+ public int compareTo(Object reference) // Kindly borrowed from the Apache Felix ServiceRegistrationImpl.ServiceReferenceImpl
+ {
+ ServiceReference other = (ServiceReference) reference;
+
+ Long id = (Long) getProperty(Constants.SERVICE_ID);
+ Long otherId = (Long) other.getProperty(Constants.SERVICE_ID);
+
+ if (id.equals(otherId))
+ {
+ return 0; // same service
+ }
+
+ Object rankObj = getProperty(Constants.SERVICE_RANKING);
+ Object otherRankObj = other.getProperty(Constants.SERVICE_RANKING);
+
+ // If no rank, then spec says it defaults to zero.
+ rankObj = (rankObj == null) ? new Integer(0) : rankObj;
+ otherRankObj = (otherRankObj == null) ? new Integer(0) : otherRankObj;
+
+ // If rank is not Integer, then spec says it defaults to zero.
+ Integer rank = (rankObj instanceof Integer)
+ ? (Integer) rankObj : new Integer(0);
+ Integer otherRank = (otherRankObj instanceof Integer)
+ ? (Integer) otherRankObj : new Integer(0);
+
+ // Sort by rank in ascending order.
+ if (rank.compareTo(otherRank) < 0)
+ {
+ return -1; // lower rank
+ }
+ else if (rank.compareTo(otherRank) > 0)
+ {
+ return 1; // higher rank
+ }
+
+ // If ranks are equal, then sort by service id in descending order.
+ return (id.compareTo(otherId) < 0) ? 1 : -1;
+ }
+
+ @Override
+ public String toString() {
+ return "TestServiceReference [props=" + props + "]";
+ }
+
+ }
+
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/ComponentTest.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/ComponentTest.java
new file mode 100644
index 0000000..9a79f70
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/ComponentTest.java
@@ -0,0 +1,699 @@
+/*
+ * 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 test;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.context.AbstractDependency;
+import org.apache.felix.dm.impl.ComponentImpl;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings("unused")
+public class ComponentTest {
+ static class MyComponent {
+ public MyComponent() {
+ }
+ }
+
+ @Test
+ public void createStartAndStopComponent() {
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(MyComponent.class);
+ Assert.assertEquals("should not be available until started", false, c.isAvailable());
+ c.start();
+ Assert.assertEquals("should be available", true, c.isAvailable());
+ c.stop();
+ Assert.assertEquals("should no longer be available when stopped", false, c.isAvailable());
+ }
+
+ @Test
+ public void testInitCallbackOfComponent() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ void init() {
+ e.step(2);
+ }
+ void start() {
+ e.step(3);
+ }
+ void stop() {
+ e.step(5);
+ }
+ void destroy() {
+ e.step(6);
+ }
+ });
+ e.step(1);
+ c.start();
+ e.step(4);
+ c.stop();
+ e.step(7);
+ }
+
+ @Test
+ public void testAddDependencyFromInitCallback() {
+ final Ensure e = new Ensure();
+ final SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ void init(Component c) {
+ e.step(2);
+ c.add(d);
+ }
+ void start() {
+ e.step(4);
+ }
+ void stop() {
+ e.step(6);
+ }
+ void destroy() {
+ e.step(7);
+ }
+ });
+ e.step(1);
+ c.start();
+ e.step(3);
+ d.add(new EventImpl()); // NPE?!
+ e.step(5);
+ d.remove(new EventImpl());
+ c.stop();
+ e.step(8);
+ }
+
+ @Test
+ public void testAddAvailableDependencyFromInitCallback() {
+ final Ensure e = new Ensure();
+ final SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ final SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setRequired(true);
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ void init(Component c) {
+ System.out.println("init");
+ e.step(2);
+ c.add(d);
+ d.add(new EventImpl());
+ c.add(d2);
+ }
+ void start() {
+ System.out.println("start");
+ e.step();
+ }
+ void stop() {
+ System.out.println("stop");
+ e.step();
+ }
+ void destroy() {
+ System.out.println("destroy");
+ e.step(9);
+ }
+ });
+ e.step(1);
+ c.start();
+ e.step(5);
+ d2.add(new EventImpl());
+ e.step(7);
+ d.remove(new EventImpl());
+ c.stop();
+ e.step(10);
+ }
+
+ @Test
+ public void testAtomicallyAddMultipleDependenciesFromInitCallback() {
+ final Ensure e = new Ensure();
+ final SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+
+ final SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setRequired(true);
+
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ void init(Component c) {
+ System.out.println("init");
+ e.step(2);
+ c.add(d, d2);
+ d.add(new EventImpl()); // won't trigger start because d2 is not yet available
+ }
+ void start() {
+ System.out.println("start");
+ e.step(4);
+ }
+ void stop() {
+ System.out.println("stop");
+ e.step(6);
+ }
+ void destroy() {
+ System.out.println("destroy");
+ e.step(7);
+ }
+ });
+ e.step(1);
+ c.start();
+ e.step(3);
+ d2.add(new EventImpl());
+ e.step(5);
+ d.remove(new EventImpl());
+ c.stop();
+ e.step(8);
+ }
+
+ @Test
+ public void createComponentAddDependencyAndStartComponent() {
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(MyComponent.class);
+ SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ c.add(d);
+ c.start();
+ Assert.assertEquals("should not be available when started because of missing dependency", false, c.isAvailable());
+ c.stop();
+ c.remove(d);
+ }
+
+ @Test
+ public void createComponentStartItAndAddDependency() {
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(MyComponent.class);
+ SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ c.start();
+ Assert.assertEquals("should be available when started", true, c.isAvailable());
+ c.add(d);
+ Assert.assertEquals("dependency should not be available", false, d.isAvailable());
+ Assert.assertEquals("Component should not be available", false, c.isAvailable());
+ c.remove(d);
+ c.stop();
+ }
+
+ @Test
+ public void createComponentStartItAddDependencyAndMakeDependencyAvailable() {
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(MyComponent.class);
+ SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ c.start();
+ c.add(d);
+ Assert.assertEquals("Component should not be available: it is started but the dependency is not available", false, c.isAvailable());
+ d.add(new EventImpl());
+ Assert.assertEquals("dependency is available, component should be too", true, c.isAvailable());
+ d.remove(new EventImpl());
+ Assert.assertEquals("dependency is no longer available, component should not be either", false, c.isAvailable());
+ c.remove(d);
+ Assert.assertEquals("dependency is removed, component should be available again", true, c.isAvailable());
+ c.stop();
+ Assert.assertEquals("Component is stopped, should be unavailable now", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentStartItAddDependencyAndListenerMakeDependencyAvailableAndUnavailableImmediately() {
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(MyComponent.class);
+ final SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ ComponentStateListener l = new ComponentStateListener() {
+ @Override
+ public void changed(Component c, ComponentState state) {
+ // make the dependency unavailable
+ d.remove(new EventImpl());
+ }
+ };
+ c.start();
+ c.add(d);
+ // we add a listener here which immediately triggers an 'external event' that
+ // makes the dependency unavailable again as soon as it's invoked
+ c.add(l);
+ Assert.assertEquals("Component unavailable, dependency unavailable", false, c.isAvailable());
+ // so even though we make the dependency available here, before our call returns it
+ // is made unavailable again
+ d.add(new EventImpl());
+ Assert.assertEquals("Component *still* unavailable, because the listener immediately makes the dependency unavailable", false, c.isAvailable());
+ c.remove(l);
+ Assert.assertEquals("listener removed, component still unavailable", false, c.isAvailable());
+ c.remove(d);
+ Assert.assertEquals("dependency removed, component available", true, c.isAvailable());
+ c.stop();
+ Assert.assertEquals("Component stopped, should be unavailable", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentAddTwoDependenciesMakeBothAvailableAndUnavailable() {
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(MyComponent.class);
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setRequired(true);
+ SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setRequired(true);
+ c.start();
+ c.add(d1);
+ c.add(d2);
+ Assert.assertEquals("Component should be unavailable, both dependencies are too", false, c.isAvailable());
+ d1.add(new EventImpl());
+ Assert.assertEquals("one dependency available, component should still be unavailable", false, c.isAvailable());
+ d2.add(new EventImpl());
+ Assert.assertEquals("both dependencies available, component should be available", true, c.isAvailable());
+ d1.remove(new EventImpl());
+ Assert.assertEquals("one dependency unavailable again, component should be unavailable too", false, c.isAvailable());
+ d2.remove(new EventImpl());
+ Assert.assertEquals("both dependencies unavailable, component should be too", false, c.isAvailable());
+ c.remove(d2);
+ Assert.assertEquals("removed one dependency, still unavailable", false, c.isAvailable());
+ c.remove(d1);
+ Assert.assertEquals("removed the other dependency, component should be available now", true, c.isAvailable());
+ c.stop();
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentAddDependencyMakeAvailableAndUnavailableWithCallbacks() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step(1);
+ }
+ public void remove() {
+ e.step(3);
+ }
+ });
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "remove");
+ d1.setRequired(true);
+ // add the dependency to the component
+ c.add(d1);
+ // start the component
+ c.start();
+ // make the dependency available, we expect the add callback
+ // to be invoked here
+ d1.add(new EventImpl());
+ e.step(2);
+ // remove the dependency, should trigger the remove callback
+ d1.remove(new EventImpl());
+ e.step(4);
+ c.stop();
+ c.remove(d1);
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createAndStartComponentAddDependencyMakeAvailableAndUnavailableWithCallbacks() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step(1);
+ }
+ public void remove() {
+ e.step(3);
+ }
+ });
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "remove");
+ d1.setRequired(true);
+ // start the ComponentImpl (it should become available)
+ c.start();
+ // add the dependency (it should become unavailable)
+ c.add(d1);
+ // make the dependency available, which should invoke the
+ // add callback
+ d1.add(new EventImpl());
+ e.step(2);
+ // make the dependency unavailable, should trigger the
+ // remove callback
+ d1.remove(new EventImpl());
+ e.step(4);
+ c.remove(d1);
+ c.stop();
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentAddTwoDependenciesMakeBothAvailableAndUnavailableWithCallbacks() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step();
+ }
+ public void remove() {
+ e.step();
+ }
+ });
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "remove");
+ d1.setRequired(true);
+ SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setCallbacks("add", "remove");
+ d2.setRequired(true);
+ // start the component, which should become active because there are no
+ // dependencies yet
+ c.start();
+ // now add the dependencies, making the ComponentImpl unavailable
+ c.add(d1);
+ c.add(d2);
+ // make the first dependency available, should have no effect on the
+ // component
+ d1.add(new EventImpl());
+ e.step(1);
+ // second dependency available, now all the add callbacks should be
+ // invoked
+ d2.add(new EventImpl());
+ e.step(4);
+ // remove the first dependency, triggering the remove callbacks
+ d1.remove(new EventImpl());
+ e.step(7);
+ // remove the second dependency, should not trigger more callbacks
+ d2.remove(new EventImpl());
+ e.step(8);
+ c.remove(d2);
+ c.remove(d1);
+ c.stop();
+ // still, no more callbacks should have been invoked
+ e.step(9);
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createAndStartComponentAddTwoDependenciesMakeBothAvailableAndUnavailableWithCallbacks() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step();
+ }
+ public void remove() {
+ e.step();
+ }
+ });
+ // start the component, it should become available
+ c.start();
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "remove");
+ d1.setRequired(true);
+ SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setCallbacks("add", "remove");
+ d2.setRequired(true);
+ // add the first dependency, ComponentImpl should be unavailable
+ c.add(d1);
+ c.add(d2);
+ // make first dependency available, ComponentImpl should still be unavailable
+ d1.add(new EventImpl());
+ e.step(1);
+ // make second dependency available, ComponentImpl available, callbacks should
+ // be invoked
+ d2.add(new EventImpl());
+ e.step(4);
+ // remove the first dependency, callbacks should be invoked
+ d1.remove(new EventImpl());
+ e.step(7);
+ // remove second dependency, no callbacks should be invoked
+ d2.remove(new EventImpl());
+ e.step(8);
+ c.remove(d2);
+ c.remove(d1);
+ c.stop();
+ e.step(9);
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createAndStartComponentAddTwoDependenciesWithMultipleServicesWithCallbacks() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step();
+ }
+ public void remove() {
+ e.step();
+ }
+ });
+ // start component
+ c.start();
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "remove");
+ d1.setRequired(true);
+ SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setCallbacks("add", "remove");
+ d2.setRequired(true);
+ c.add(d1);
+ c.add(d2);
+ // add three instances to first dependency, no callbacks should
+ // be triggered
+ d1.add(new EventImpl(1));
+ d1.add(new EventImpl(2));
+ d1.add(new EventImpl(3));
+ e.step(1);
+ // add two instances to the second dependency, callbacks should
+ // be invoked (4x)
+ d2.add(new EventImpl(1));
+ e.step(6);
+ // add another dependency, triggering another callback
+ d2.add(new EventImpl(2));
+ e.step(8);
+ // remove first dependency (all three of them) which makes the
+ // ComponentImpl unavailable so it should trigger calling remove for
+ // all of them (so 5x)
+ d1.remove(new EventImpl(1));
+ d1.remove(new EventImpl(2));
+ d1.remove(new EventImpl(3));
+ e.step(14);
+ // remove second dependency, should not trigger further callbacks
+ d2.remove(new EventImpl(1));
+ d2.remove(new EventImpl(2));
+ e.step(15);
+ c.remove(d2);
+ c.remove(d1);
+ c.stop();
+ e.step(16);
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentAddDependencyMakeAvailableChangeAndUnavailableWithCallbacks() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step(1);
+ }
+ public void change() {
+ e.step(3);
+ }
+ public void remove() {
+ e.step(5);
+ }
+ });
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "change", "remove");
+ d1.setRequired(true);
+ // add the dependency to the component
+ c.add(d1);
+ // start the component
+ c.start();
+ // make the dependency available, we expect the add callback
+ // to be invoked here
+ d1.add(new EventImpl());
+ e.step(2);
+ // change the dependency
+ d1.change(new EventImpl());
+ e.step(4);
+ // remove the dependency, should trigger the remove callback
+ d1.remove(new EventImpl());
+ e.step(6);
+ c.stop();
+ c.remove(d1);
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentWithOptionalDependency() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step(1);
+ }
+ public void change() {
+ e.step(3);
+ }
+ public void remove() {
+ e.step(5);
+ }
+ });
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "change", "remove");
+ d1.setRequired(false);
+ // add the dependency to the component
+ c.add(d1);
+ // start the component
+ c.start();
+ Assert.assertEquals("Component started with an optional dependency, should be available", true, c.isAvailable());
+ // make the dependency available, we expect the add callback
+ // to be invoked here
+ d1.add(new EventImpl());
+ e.step(2);
+ Assert.assertEquals("Component started with an optional dependency, should be available", true, c.isAvailable());
+ // change the dependency
+ d1.change(new EventImpl());
+ e.step(4);
+ Assert.assertEquals("Component started with an optional dependency, should be available", true, c.isAvailable());
+ // remove the dependency, should trigger the remove callback
+ d1.remove(new EventImpl());
+ Assert.assertEquals("Component started with an optional dependency, should be available", true, c.isAvailable());
+ e.step(6);
+ c.stop();
+ c.remove(d1);
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentWithOptionalAndRequiredDependency() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ public void add() {
+ e.step();
+ }
+ public void remove() {
+ e.step();
+ }
+ });
+ SimpleServiceDependency d1 = new SimpleServiceDependency();
+ d1.setCallbacks("add", "remove");
+ d1.setRequired(false);
+ SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setCallbacks("add", "remove");
+ d2.setRequired(true);
+ // add the dependencies to the component
+ c.add(d1);
+ c.add(d2);
+ // start the component
+ c.start();
+ Assert.assertEquals("Component started with a required and optional dependency, should not be available", false, c.isAvailable());
+ // make the optional dependency available
+ d1.add(new EventImpl());
+ e.step(1);
+ Assert.assertEquals("Component should not be available", false, c.isAvailable());
+ // make the required dependency available
+ d2.add(new EventImpl());
+ e.step(4);
+ Assert.assertEquals("Component should be available", true, c.isAvailable());
+ // remove the optional dependency
+ d1.remove(new EventImpl());
+ e.step(6);
+ Assert.assertEquals("Component should be available", true, c.isAvailable());
+ // remove the required dependency
+ d1.remove(new EventImpl());
+ e.step(8);
+ Assert.assertEquals("Component should be available", true, c.isAvailable());
+ c.stop();
+ c.remove(d1);
+ Assert.assertEquals("Component stopped, should be unavailable again", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentAddAvailableDependencyRemoveDependencyCheckStopCalledBeforeUnbind() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ void add() {
+ e.step(1);
+ }
+ void start() {
+ e.step(2);
+ }
+ void stop() {
+ e.step(4);
+ }
+ void remove() {
+ e.step(5);
+ }
+ });
+ SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setCallbacks("add", "remove");
+ d.setRequired(true);
+ // add the dependency to the component
+ c.add(d);
+ // start the component
+ c.start();
+ // make the dependency available, we expect the add callback
+ // to be invoked here, then start is called.
+ d.add(new EventImpl());
+ e.step(3);
+ // remove the dependency, should trigger the stop, then remove callback
+ d.remove(new EventImpl());
+ e.step(6);
+ c.stop();
+ c.remove(d);
+ Assert.assertEquals("Component stopped, should be unavailable", false, c.isAvailable());
+ }
+
+ @Test
+ public void createDependenciesWithCallbackInstance() {
+ final Ensure e = new Ensure();
+ ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object() {
+ void start() {
+ e.step(2);
+ }
+
+ void stop() {
+ e.step(4);
+ }
+ });
+
+ Object callbackInstance = new Object() {
+ void add() {
+ e.step(1);
+ }
+
+ void remove() {
+ e.step(5);
+ }
+ };
+
+ SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setCallbacks(callbackInstance, "add", "remove");
+ d.setRequired(true);
+ // add the dependency to the component
+ c.add(d);
+ // start the component
+ c.start();
+ // make the dependency available, we expect the add callback
+ // to be invoked here, then start is called.
+ d.add(new EventImpl());
+ e.step(3);
+ // remove the dependency, should trigger the stop, then remove callback
+ d.remove(new EventImpl());
+ e.step(6);
+ c.stop();
+ c.remove(d);
+ Assert.assertEquals("Component stopped, should be unavailable", false, c.isAvailable());
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/ConcurrencyTest.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/ConcurrencyTest.java
new file mode 100644
index 0000000..7f999bc
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/ConcurrencyTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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 test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.impl.ComponentImpl;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class ConcurrencyTest {
+
+ /**
+ * Ensure actions from another thread than the current thread executing in the SerialExecutor are being
+ * scheduled (added to the queue) rather than being executed immediately.
+ */
+ @Test
+ public void createComponentAddDependencyAndListenerAndAddAnotherDependencyInAParallelThread() {
+ final Semaphore s = new Semaphore(0);
+ final ComponentImpl c = new ComponentImpl();
+ final SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ final SimpleServiceDependency d2 = new SimpleServiceDependency();
+ d2.setRequired(true);
+ final Thread t = new Thread() {
+ public void run() {
+ c.add(d2);
+ s.release();
+ }
+ };
+ ComponentStateListener l = new ComponentStateListener() {
+ @Override
+ public void changed(Component component, ComponentState state) {
+ try {
+ c.remove(this);
+ // launch a second thread interacting with our ComponentImpl and block this thread until the
+ // second thread finished its interaction with our component. We want to ensure the work of
+ // the second thread is scheduled after our current job in the serial executor and does not
+ // get executed immediately.
+ t.start();
+ s.acquire();
+ Assert.assertEquals("dependency count should be 1", 1, c.getDependencies().size());
+ }
+ catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ c.setImplementation(new Object()); // If not, we may see NullPointers when invoking lifecycle callbacks
+ c.start();
+ c.add(d);
+ c.add(l);
+ Assert.assertEquals("component should not be available", false, c.isAvailable());
+ d.add(new EventImpl()); // sets dependency d to available and triggers our ComponentStateListener
+
+ // due to the dependency added by the second thread in the serial executor we still expect our component
+ // to be unavailable. This work was scheduled in the serial executor and will be executed by the current
+ // thread after it finished handling the job for handling the changed() method.
+ Assert.assertEquals("component should not be available", false, c.isAvailable());
+ c.remove(l);
+ Assert.assertEquals("component should not be available", false, c.isAvailable());
+ c.remove(d);
+ Assert.assertEquals("component should not be available", false, c.isAvailable());
+ c.remove(d2);
+ Assert.assertEquals("component should be available", true, c.isAvailable());
+ c.stop();
+ Assert.assertEquals("component should not be available", false, c.isAvailable());
+ }
+
+ @Test
+ public void createComponentAddAndRemoveDependenciesInParallelThreads() throws Exception {
+ final ComponentImpl c = new ComponentImpl();
+ c.setImplementation(new Object()); // If not, we may see NullPointers when invoking lifecycle callbacks
+ ExecutorService e = Executors.newFixedThreadPool(16);
+ c.start();
+ for (int i = 0; i < 1000; i++) {
+ e.execute(new Runnable() {
+ @Override
+ public void run() {
+ SimpleServiceDependency d = new SimpleServiceDependency();
+ d.setRequired(true);
+ c.add(d);
+// d.changed(new EventImpl(true));
+// d.changed(new EventImpl(false));
+ c.remove(d);
+ }});
+ }
+ e.shutdown();
+ e.awaitTermination(10, TimeUnit.SECONDS);
+// Assert.assertEquals("component should not be available", false, c.isAvailable());
+ c.stop();
+ Assert.assertEquals("component should not be available", false, c.isAvailable());
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/ConfigurationTest.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/ConfigurationTest.java
new file mode 100644
index 0000000..bf58bcf
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/ConfigurationTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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 test;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.impl.ComponentImpl;
+import org.apache.felix.dm.impl.ConfigurationDependencyImpl;
+import org.junit.Test;
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes", "unused"})
+public class ConfigurationTest extends TestBase {
+ @Test
+ public void testConfigurationFailure() throws Throwable {
+ final Ensure e = new Ensure();
+
+ // Create our configuration dependency
+ final ConfigurationDependencyImpl conf = new ConfigurationDependencyImpl();
+
+ // Create another required dependency
+ final SimpleServiceDependency requiredDependency = new SimpleServiceDependency();
+ requiredDependency.setRequired(true);
+ requiredDependency.setCallbacks("addDep", null);
+
+ // Create our component, which will fail when handling configuration update
+ ComponentImpl c = new ComponentImpl();
+
+ c.setImplementation(new Object() {
+ volatile Dictionary m_conf;
+
+ public void updated(Dictionary conf) {
+ debug("updated: conf=%s", conf);
+ m_conf = conf;
+ if ("invalid".equals(conf.get("conf"))) {
+ // We refuse the first configuration.
+ debug("refusing configuration");
+ e.step(1);
+ // Set our acceptUpdate flag to true, so next update will be successful
+ throw new RuntimeException("update failed (expected)");
+ }
+ else {
+ debug("accepting configuration");
+ e.step(2);
+ }
+ }
+
+ public void addDep() {
+ if ("invalid".equals(m_conf.get("conf"))) {
+ e.throwable(new Exception("addDep should not be called"));
+ }
+ e.step(3);
+ debug("addDep");
+ }
+
+ void init(Component c) {
+ if ("invalid".equals(m_conf.get("conf"))) {
+ e.throwable(new Exception("init should not be called"));
+ }
+ e.step(4);
+ debug("init");
+ }
+
+ void start() {
+ if ("invalid".equals(m_conf.get("conf"))) {
+ e.throwable(new Exception("start should not be called"));
+ }
+ e.step(5);
+ debug("start");
+ }
+ });
+
+ // Add the dependencies
+ c.add(conf);
+ c.add(requiredDependency);
+
+ // Start our component ("requiredDependency" is not yet available, so we'll stay in WAITING_FOR_REQUIRED state).
+ c.start();
+
+ // Enabled "requiredDependency"
+ requiredDependency.add(new EventImpl());
+
+ // Now, act as the configuration admin service and inject a wrong dependency
+ try {
+ Hashtable props = new Hashtable();
+ props.put("conf", "invalid");
+ conf.updated(props);
+ }
+ catch (ConfigurationException err) {
+ warn("got expected configuration error");
+ }
+ e.waitForStep(1, 5000);
+ e.ensure();
+
+ // Now, inject another valid configuration
+ try {
+ Hashtable props = new Hashtable();
+ props.put("conf", "valid");
+ conf.updated(props);
+ }
+ catch (ConfigurationException err) {
+ warn("got expected configuration error");
+ }
+
+ // This time, our component should be started properly.
+ e.waitForStep(5, 5000);
+ e.ensure();
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/Ensure.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/Ensure.java
new file mode 100644
index 0000000..4d2378f
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/Ensure.java
@@ -0,0 +1,186 @@
+/*
+ * 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 test;
+
+import java.io.PrintStream;
+
+import org.junit.Assert;
+
+/**
+ * Helper class to make sure that steps in a test happen in the correct order. Instantiate
+ * this class and subsequently invoke <code>step(nr)</code> with steps starting at 1. You
+ * can also have threads wait until you arrive at a certain step.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class Ensure {
+ private final boolean DEBUG;
+ private static long INSTANCE = 0;
+ private static final int RESOLUTION = 100;
+ private static PrintStream STREAM = System.out;
+ int step = 0;
+ private Throwable m_throwable;
+ private boolean previousStepFailed;
+
+ public Ensure() {
+ this(true);
+ }
+
+ public Ensure(boolean debug) {
+ DEBUG = debug;
+ if (DEBUG) {
+ INSTANCE++;
+ }
+ }
+
+ public void setStream(PrintStream output) {
+ STREAM = output;
+ }
+
+ /**
+ * Mark this point as step <code>nr</code>.
+ *
+ * @param nr the step we are in
+ */
+ public synchronized void step(int nr) {
+ if (previousStepFailed) {
+ throw new RuntimeException("can not enter into step " + nr + " (some previous steps failed)");
+ }
+ step++;
+ try {
+ Assert.assertEquals(nr, step);
+ } catch (Throwable e) {
+ previousStepFailed = true;
+ throw e;
+ }
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] step " + step + " [" + currentThread() + "] " + info);
+ }
+ notifyAll();
+ }
+
+ private String getLineInfo(int depth) {
+ StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+ String info = trace[depth].getClassName() + "." + trace[depth].getMethodName() + ":" + trace[depth].getLineNumber();
+ return info;
+ }
+
+ /**
+ * Mark this point as the next step.
+ */
+ public synchronized void step() {
+ if (previousStepFailed) {
+ throw new RuntimeException("can not enter into step " + (step+1) + " (some previous steps failed)");
+ }
+ step++;
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] next step " + step + " [" + currentThread() + "] " + info);
+ }
+ notifyAll();
+ }
+
+ /**
+ * Wait until we arrive at least at step <code>nr</code> in the process, or fail if that
+ * takes more than <code>timeout</code> milliseconds. If you invoke wait on a thread,
+ * you are effectively assuming some other thread will invoke the <code>step(nr)</code>
+ * method.
+ *
+ * @param nr the step to wait for
+ * @param timeout the number of milliseconds to wait
+ */
+ public synchronized void waitForStep(int nr, int timeout) {
+ final int initialTimeout = timeout;
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] waiting for step " + nr + " [" + currentThread() + "] " + info);
+ }
+ while (step < nr && timeout > 0) {
+ try {
+ wait(RESOLUTION);
+ timeout -= RESOLUTION;
+ }
+ catch (InterruptedException e) {}
+ }
+ if (step < nr) {
+ throw new IllegalStateException("Timed out waiting for " + initialTimeout + " ms for step " + nr + ", we are still at step " + step);
+ }
+ if (DEBUG) {
+ String info = getLineInfo(3);
+ STREAM.println("[Ensure " + INSTANCE + "] arrived at step " + nr + " [" + currentThread() + "] " + info);
+ }
+ }
+
+ private String currentThread() {
+ Thread thread = Thread.currentThread();
+ return thread.getId() + " " + thread.getName();
+ }
+
+ public static Runnable createRunnableStep(final Ensure ensure, final int nr) {
+ return new Runnable() { public void run() { ensure.step(nr); }};
+ }
+
+ public synchronized void steps(Steps steps) {
+ steps.next(this);
+ }
+
+ /**
+ * Helper class for naming a list of step numbers. If used with the steps(Steps) method
+ * you can define at which steps in time this point should be passed. That means you can
+ * check methods that will get invoked multiple times during a test.
+ */
+ public static class Steps {
+ private final int[] m_steps;
+ private int m_stepIndex;
+
+ /**
+ * Create a list of steps and initialize the step counter to zero.
+ */
+ public Steps(int... steps) {
+ m_steps = steps;
+ m_stepIndex = 0;
+ }
+
+ /**
+ * Ensure we're at the right step. Will throw an index out of bounds exception if we enter this step more often than defined.
+ */
+ public void next(Ensure ensure) {
+ ensure.step(m_steps[m_stepIndex++]);
+ }
+ }
+
+ /**
+ * Saves a thrown exception that occurred in a different thread. You can only save one exception
+ * at a time this way.
+ */
+ public synchronized void throwable(Throwable throwable) {
+ m_throwable = throwable;
+ }
+
+ /**
+ * Throws a <code>Throwable</code> if one occurred in a different thread and that thread saved it
+ * using the <code>throwable()</code> method.
+ */
+ public synchronized void ensure() throws Throwable {
+ if (m_throwable != null) {
+ throw m_throwable;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/EventImpl.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/EventImpl.java
new file mode 100644
index 0000000..28c5455
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/EventImpl.java
@@ -0,0 +1,71 @@
+/*
+ * 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 test;
+
+import org.apache.felix.dm.context.Event;
+
+/** in real life, this event might contain a service reference and service instance
+ * or something similar
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class EventImpl extends Event { // the actual event object (a Service, a Bundle, a Configuration, etc ...)
+ private final int m_id;
+
+ public EventImpl() {
+ this(1);
+ }
+ /** By constructing events with different IDs, we can simulate different unique instances. */
+ public EventImpl(int id) {
+ this (id, null);
+ }
+
+ public EventImpl(int id, Object event) {
+ super(event);
+ m_id = id;
+ }
+
+
+ @Override
+ public int hashCode() {
+ return m_id;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // an instanceof check here is not "strong" enough with subclasses overriding the
+ // equals: we need to be sure that a.equals(b) == b.equals(a) at all times
+ if (obj != null && obj.getClass().equals(EventImpl.class)) {
+ return ((EventImpl) obj).m_id == m_id;
+ }
+ return false;
+ }
+
+ @Override
+ public int compareTo(Event o) {
+ EventImpl a = this, b = (EventImpl) o;
+ if (a.m_id < b.m_id) {
+ return -1;
+ } else if (a.m_id == b.m_id){
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/SerialExecutorTest.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/SerialExecutorTest.java
new file mode 100644
index 0000000..feb04c4
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/SerialExecutorTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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 test;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.felix.dm.Logger;
+import org.apache.felix.dm.impl.SerialExecutor;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Validates SerialExecutor used by DM implementation.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class SerialExecutorTest extends TestBase {
+ final Random m_rnd = new Random();
+ final int TESTS = 100000;
+
+ @Test
+ public void testSerialExecutor() {
+ info("Testing serial executor");
+ int cores = Math.max(10, Runtime.getRuntime().availableProcessors());
+ ExecutorService threadPool = null;
+
+ try {
+ threadPool = Executors.newFixedThreadPool(cores);
+ final SerialExecutor serial = new SerialExecutor(new Logger(null));
+
+ long timeStamp = System.currentTimeMillis();
+ for (int i = 0; i < TESTS; i++) {
+ final CountDownLatch latch = new CountDownLatch(cores * 2 /* each task reexecutes itself one time */);
+ final SerialTask task = new SerialTask(serial, latch);
+ for (int j = 0; j < cores; j ++) {
+ threadPool.execute(new Runnable() {
+ public void run() {
+ serial.execute(task);
+ }
+ });
+ }
+ Assert.assertTrue("Test " + i + " did not terminate timely", latch.await(20000, TimeUnit.MILLISECONDS));
+ }
+ long now = System.currentTimeMillis();
+ System.out.println("Performed " + TESTS + " tests in " + (now - timeStamp) + " ms.");
+ timeStamp = now;
+ }
+ catch (Throwable t) {
+ t.printStackTrace();
+ Assert.fail("Test failed: " + t.getMessage());
+ }
+ finally {
+ if (threadPool != null) {
+ shutdown(threadPool);
+ }
+ }
+ }
+
+ void shutdown(ExecutorService exec) {
+ exec.shutdown();
+ try {
+ exec.awaitTermination(5, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException e) {
+ }
+ }
+
+ class SerialTask implements Runnable {
+ final AtomicReference<Thread> m_executingThread = new AtomicReference<Thread>();
+ final CountDownLatch m_latch;
+ private boolean m_firstExecution;
+ private final SerialExecutor m_exec;
+
+ SerialTask(SerialExecutor exec, CountDownLatch latch) {
+ m_latch = latch;
+ m_exec = exec;
+ m_firstExecution = true;
+ }
+
+ public void run() {
+ Thread self = Thread.currentThread();
+ if (m_firstExecution) {
+ // The first time we are executed, the previous executing thread stored in our m_executingThread should be null
+ if (!m_executingThread.compareAndSet(null, self)) {
+ System.out.println("detected concurrent call to SerialTask: currThread=" + self
+ + ", other executing thread=" + m_executingThread);
+ return;
+ }
+ } else {
+ // The second time we are executed, the previous executing thread stored in our m_executingThread should be
+ // the current running thread.
+ if (m_executingThread.get() != self) {
+ System.out.println("expect to execute reentrant tasks in same thread, but current thread=" + self
+ + ", while expected is " + m_executingThread);
+ return;
+ }
+ }
+
+ if (m_firstExecution) {
+ m_firstExecution = false;
+ m_exec.execute(this); // Our run method must be called immediately
+ } else {
+ if (! m_executingThread.compareAndSet(self, null)) {
+ System.out.println("detected concurrent call to SerialTask: currThread=" + self
+ + ", other executing thread=" + m_executingThread);
+ return;
+ }
+ m_firstExecution = true;
+ }
+
+ m_latch.countDown();
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java
new file mode 100644
index 0000000..90873e7
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/ServiceRaceTest.java
@@ -0,0 +1,255 @@
+/*
+ * 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 test;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.ComponentState;
+import org.apache.felix.dm.ComponentStateListener;
+import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.impl.ComponentImpl;
+import org.apache.felix.dm.impl.ConfigurationDependencyImpl;
+import org.junit.Assert;
+import org.junit.Test;
+import org.osgi.service.cm.ConfigurationException;
+
+/**
+ * This test class simulates a client having many dependencies being registered/unregistered concurrently.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class ServiceRaceTest extends TestBase {
+ final static int STEP_WAIT = 5000;
+ final static int DEPENDENCIES = 10;
+ final static int LOOPS = 10000;
+
+ // Executor used to bind/unbind service dependencies.
+ ExecutorService m_threadpool;
+ // Timestamp used to log the time consumed to execute 100 tests.
+ long m_timeStamp;
+
+ /**
+ * Creates many service dependencies, and activate/deactivate them concurrently.
+ */
+ @Test
+ public void createParallelComponentRegistgrationUnregistration() {
+ info("Starting createParallelComponentRegistgrationUnregistration test");
+ int cores = Math.max(16, Runtime.getRuntime().availableProcessors());
+ info("using " + cores + " cores.");
+
+ m_threadpool = Executors.newFixedThreadPool(Math.max(cores, DEPENDENCIES + 3 /* start/stop/configure */));
+
+ try {
+ m_timeStamp = System.currentTimeMillis();
+ for (int loop = 0; loop < LOOPS; loop++) {
+ doTest(loop);
+ }
+ }
+ catch (Throwable t) {
+ warn("got unexpected exception", t);
+ }
+ finally {
+ shutdown(m_threadpool);
+ }
+ }
+
+ void shutdown(ExecutorService exec) {
+ exec.shutdown();
+ try {
+ exec.awaitTermination(5, TimeUnit.SECONDS);
+ }
+ catch (InterruptedException e) {
+ }
+ }
+
+ void doTest(int loop) throws Throwable {
+ debug("loop#%d -------------------------", loop);
+
+ final Ensure step = new Ensure(false);
+
+ // Create one client component, which depends on many service dependencies
+ final ComponentImpl client = new ComponentImpl();
+ final Client theClient = new Client(step);
+ client.setImplementation(theClient);
+
+ // Create client service dependencies
+ final SimpleServiceDependency[] dependencies = new SimpleServiceDependency[DEPENDENCIES];
+ for (int i = 0; i < DEPENDENCIES; i++) {
+ dependencies[i] = new SimpleServiceDependency();
+ dependencies[i].setRequired(true);
+ dependencies[i].setCallbacks("add", "remove");
+ client.add(dependencies[i]);
+ }
+ final ConfigurationDependencyImpl confDependency = new ConfigurationDependencyImpl();
+ confDependency.setPid("mypid");
+ client.add(confDependency);
+
+ // Create Configuration (concurrently).
+ // We have to simulate the configuration update, using a component state listener, which will
+ // trigger an update thread, but only once the component is started.
+ final ComponentStateListener listener = new ComponentStateListener() {
+ private volatile Dictionary m_conf;
+
+ public void changed(Component c, ComponentState state) {
+ if (state == ComponentState.WAITING_FOR_REQUIRED && m_conf == null) {
+ m_conf = new Hashtable();
+ m_conf.put("foo", "bar");
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ try {
+ confDependency.updated(m_conf);
+ }
+ catch (ConfigurationException e) {
+ warn("configuration failed", e);
+ }
+ }
+ });
+ }
+ }
+ };
+ client.add(listener);
+
+
+ // Start the client (concurrently)
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ client.start();
+
+ // Activate the client service dependencies concurrently.
+ // We *must* do this after having started the component (in a reality, the dependencies can be
+ // injected only one the tracker has been opened ...
+ for (int i = 0; i < DEPENDENCIES; i++) {
+ final SimpleServiceDependency dep = dependencies[i];
+ final Event added = new EventImpl(i);
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ dep.add(added);
+ }
+ });
+ }
+
+ }
+ });
+
+ // Ensure that client has been started.
+ int expectedStep = 1 /* conf */ + DEPENDENCIES + 1 /* start */;
+ step.waitForStep(expectedStep, STEP_WAIT);
+ Assert.assertEquals(DEPENDENCIES, theClient.getDependencies());
+ Assert.assertNotNull(theClient.getConfiguration());
+ client.remove(listener);
+
+ // Stop the client and all dependencies concurrently.
+ for (int i = 0; i < DEPENDENCIES; i++) {
+ final SimpleServiceDependency dep = dependencies[i];
+ final Event removed = new EventImpl(i);
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ dep.remove(removed);
+ }
+ });
+ }
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ client.stop();
+ }
+ });
+ m_threadpool.execute(new Runnable() {
+ public void run() {
+ try {
+ confDependency.updated(null);
+ }
+ catch (ConfigurationException e) {
+ warn("error while unconfiguring", e);
+ }
+ }
+ });
+
+ // Ensure that client has been stopped, then destroyed, then unbound from all dependencies
+ expectedStep += 2; // stop/destroy
+ expectedStep += DEPENDENCIES; // removed all dependencies
+ expectedStep += 1; // removed configuration
+ step.waitForStep(expectedStep, STEP_WAIT);
+ step.ensure();
+ Assert.assertEquals(0, theClient.getDependencies());
+ Assert.assertNull(theClient.getConfiguration());
+
+ debug("finished one test loop");
+ if ((loop + 1) % 100 == 0) {
+ long duration = System.currentTimeMillis() - m_timeStamp;
+ warn("Performed 100 tests (total=%d) in %d ms.", (loop + 1), duration);
+ m_timeStamp = System.currentTimeMillis();
+ }
+ }
+
+ public class Client {
+ final Ensure m_step;
+ int m_dependencies;
+ volatile Dictionary m_conf;
+
+ public Client(Ensure step) {
+ m_step = step;
+ }
+
+ public void updated(Dictionary conf) throws ConfigurationException {
+ m_conf = conf;
+ if (conf != null) {
+ Assert.assertEquals("bar", conf.get("foo"));
+ m_step.step(1);
+ } else {
+ m_step.step();
+ }
+ }
+
+ synchronized void add() {
+ m_step.step();
+ m_dependencies++;
+ }
+
+ synchronized void remove() {
+ m_step.step();
+ m_dependencies--;
+ }
+
+ void start() {
+ m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */);
+ }
+
+ void stop() {
+ m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */);
+ }
+
+ void destroy() {
+ m_step.step((DEPENDENCIES + 1) /* deps + conf */ + 1 /* start */ + 1 /* stop */ + 1 /* destroy */);
+ }
+
+ synchronized int getDependencies() {
+ return m_dependencies;
+ }
+
+ Dictionary getConfiguration() {
+ return m_conf;
+ }
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/SimpleServiceDependency.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/SimpleServiceDependency.java
new file mode 100644
index 0000000..323fffc
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/SimpleServiceDependency.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package test;
+
+import org.apache.felix.dm.Dependency;
+import org.apache.felix.dm.context.AbstractDependency;
+import org.apache.felix.dm.context.DependencyContext;
+import org.apache.felix.dm.context.Event;
+import org.apache.felix.dm.context.EventType;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class SimpleServiceDependency extends AbstractDependency<Dependency> {
+ @Override
+ public String getType() {
+ return "SimpleServiceDependency";
+ }
+
+ @Override
+ public String getSimpleName() {
+ return "SimpleServiceDependency";
+ }
+
+ @Override
+ public DependencyContext createCopy() {
+ return new SimpleServiceDependency();
+ }
+
+ @Override
+ public void invokeCallback(EventType type, Event ... e) {
+ switch (type) {
+ case ADDED:
+ if (m_add != null) {
+ invoke (m_add, e[0], getInstances());
+ }
+ break;
+ case CHANGED:
+ if (m_change != null) {
+ invoke (m_change, e[0], getInstances());
+ }
+ break;
+ case REMOVED:
+ if (m_remove != null) {
+ invoke (m_remove, e[0], getInstances());
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ public void invoke(String method, Event e, Object[] instances) {
+ // specific for this type of dependency
+ m_component.invokeCallbackMethod(instances, method, new Class[][] { {} }, new Object[][] { {} });
+ }
+
+ public void add(final Event e) {
+ m_component.handleEvent(this, EventType.ADDED, e);
+ }
+
+ public void change(final Event e) {
+ m_component.handleEvent(this, EventType.CHANGED, e);
+ }
+
+ public void remove(final Event e) {
+ m_component.handleEvent(this, EventType.REMOVED, e);
+ }
+
+ public void swap(final Event event, final Event newEvent) {
+ m_component.handleEvent(this, EventType.SWAPPED, event, newEvent);
+ }
+
+ @Override
+ public Class<?> getAutoConfigType() {
+ return null; // we don't support auto config mode.
+ }
+}
diff --git a/dependencymanager/org.apache.felix.dependencymanager/test/test/TestBase.java b/dependencymanager/org.apache.felix.dependencymanager/test/test/TestBase.java
new file mode 100644
index 0000000..948a596
--- /dev/null
+++ b/dependencymanager/org.apache.felix.dependencymanager/test/test/TestBase.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 test;
+
+import static java.lang.System.out;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Base class for all tests.
+ * For now, this class provides logging support.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public class TestBase {
+ final int WARN = 1;
+ final int INFO = 2;
+ final int DEBUG = 3;
+
+ // Set the enabled log level.
+ final int m_level = WARN;
+
+ @SuppressWarnings("unused")
+ void debug(String format, Object ... params) {
+ if (m_level >= DEBUG) {
+ out.println(Thread.currentThread().getName() + " - " + String.format(format, params));
+ }
+ }
+
+ void warn(String format, Object ... params) {
+ warn(format, null, params);
+ }
+
+ @SuppressWarnings("unused")
+ void info(String format, Object ... params) {
+ if (m_level >= INFO) {
+ out.println(Thread.currentThread().getName() + " - " + String.format(format, params));
+ }
+ }
+
+ void warn(String format, Throwable t, Object ... params) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Thread.currentThread().getName()).append(" - ").append(String.format(format, params));
+ if (t != null) {
+ StringWriter buffer = new StringWriter();
+ PrintWriter pw = new PrintWriter(buffer);
+ t.printStackTrace(pw);
+ sb.append(System.getProperty("line.separator"));
+ sb.append(buffer.toString());
+ }
+ System.out.println(sb.toString());
+ }
+}