1 package org.springframework.osgi.extender.internal.dependencies.shutdown;
2
3 import java.io.Serializable;
4 import java.util.Comparator;
5 import java.util.HashSet;
6 import java.util.Set;
7
8 import org.apache.commons.logging.Log;
9 import org.apache.commons.logging.LogFactory;
10 import org.osgi.framework.Bundle;
11 import org.osgi.framework.ServiceReference;
12 import org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext;
13 import org.springframework.osgi.service.exporter.OsgiServicePropertiesResolver;
14 import org.springframework.osgi.util.OsgiServiceReferenceUtils;
15 import org.springframework.osgi.util.OsgiStringUtils;
16 import org.springframework.util.ObjectUtils;
17
18 /**
19 * Null safe service-based dependency sorter for bundles. Sorts bundles based on
20 * their services using the following algorithm:
21 * <p/>
22 * <ol>
23 * <li> if two bundles are connected (transitively) then the bundle that exports
24 * the service with lowest ranking id, will be considered lower. </li>
25 * <li> if the ranks are equal, then the service id (which is guaranteed to be
26 * unique) will be considered. </li>
27 * <li> if the bundles are not related then they will be sorted based on their
28 * symbolic name. </li>
29 * </ol>
30 *
31 * @author Hal Hildebrand
32 * @author Andy Piper
33 * @author Costin Leau
34 */
35 public class BundleDependencyComparator implements Comparator, Serializable {
36
37 private static final long serialVersionUID = -108354908478230663L;
38
39 private static final Log log = LogFactory.getLog(BundleDependencyComparator.class);
40
41 public int compare(Object a, Object b) {
42 boolean trace = log.isTraceEnabled();
43
44 Bundle bundle1 = (Bundle) a;
45 Bundle bundle2 = (Bundle) b;
46
47 if (trace)
48 log.trace("comparing bundle1 [" + OsgiStringUtils.nullSafeNameAndSymName(bundle1) + "] w/ bundle2 ["
49 + OsgiStringUtils.nullSafeNameAndSymName(bundle2) + "]..");
50
51 if (bundle1 == null) {
52 if (bundle2 == null) {
53 return 0;
54 }
55 else {
56
57 return 1;
58 }
59 }
60 else if (bundle2 == null) {
61
62 return -1;
63 }
64
65
66 if (bundle1.equals(bundle2)) {
67 return 0;
68 }
69
70
71
72
73 boolean b1Lower = references(bundle2, bundle1);
74 boolean b2Lower = references(bundle1, bundle2);
75
76 if (b1Lower && !b2Lower) {
77
78 return 1;
79 }
80 else if (b2Lower && !b1Lower) {
81
82 return -1;
83 }
84
85 else if (b1Lower && b2Lower && log.isInfoEnabled()) {
86 log.info("circular service dependency detected between ["
87 + OsgiStringUtils.nullSafeNameAndSymName(bundle1) + ", "
88 + OsgiStringUtils.nullSafeNameAndSymName(bundle2) + "]");
89 }
90
91
92
93
94 int compare = compareUsingServiceRankingAndId(bundle1, bundle2);
95
96 if (trace)
97 log.trace("comparison of [" + OsgiStringUtils.nullSafeNameAndSymName(bundle1)
98 + ", " + OsgiStringUtils.nullSafeNameAndSymName(bundle2) + "] based on service ranking/id was won by bundle "
99 + (compare > 0 ? "1" : "2"));
100 return compare;
101 }
102
103 /**
104 * Answer whether Bundle b is referenced by Bundle a or bundle A references
105 * Bundle B. This is the same as b -> a (i.e. a service owned by B is used
106 * by A).
107 */
108 protected boolean references(Bundle a, Bundle b) {
109 boolean ref = references(a, b, new HashSet());
110 if (log.isTraceEnabled())
111 log.trace("[" + OsgiStringUtils.nullSafeNameAndSymName(a) + "] " + (ref ? "->" : "!->")
112 + "[" + OsgiStringUtils.nullSafeNameAndSymName(b) + "]");
113 return ref;
114 }
115
116 /**
117 * Answer whether Bundle b is transitively referenced by Bundle a
118 */
119 protected boolean references(Bundle a, Bundle b, Set seen) {
120 if (seen.contains(b)) {
121 return false;
122 }
123 seen.add(b);
124 ServiceReference[] services = b.getRegisteredServices();
125 if (services == null) {
126 return false;
127 }
128 for (int i = 0; i < services.length; i++) {
129
130 if (isSpringManagedService(services[i])) {
131 Bundle[] referingBundles = services[i].getUsingBundles();
132 if (referingBundles != null) {
133 for (int j = 0; j < referingBundles.length; j++) {
134 if (a.equals(referingBundles[j])) {
135 return true;
136 }
137 else if (references(a, referingBundles[j], seen)) {
138 return true;
139 }
140 }
141 }
142 }
143 }
144 return false;
145 }
146
147 /**
148 * Simple method checking whether the given service reference points to a
149 * spring managed service or not. Checks for
150 *
151 * @param reference reference to the OSGi service
152 * @return true if the service is spring managed, false otherwise
153 */
154 private boolean isSpringManagedService(ServiceReference reference) {
155 if (reference == null)
156 return false;
157 return (reference.getProperty(OsgiServicePropertiesResolver.BEAN_NAME_PROPERTY_KEY) != null
158 || reference.getProperty(ConfigurableOsgiBundleApplicationContext.APPLICATION_CONTEXT_SERVICE_PROPERTY_NAME) != null);
159 }
160
161 private ServiceReference[] excludeNonSpringManagedServices(ServiceReference[] references) {
162 if (ObjectUtils.isEmpty(references))
163 return references;
164
165 int count = 0;
166 for (int i = 0; i < references.length; i++) {
167 if (!isSpringManagedService(references[i]))
168 references[i] = null;
169 else
170 count++;
171 }
172
173 if (count == references.length)
174 return references;
175
176 ServiceReference[] refs = new ServiceReference[count];
177 int j = 0;
178 for (int i = 0; i < references.length; i++) {
179 if (references[i] != null) {
180 refs[j] = references[i];
181 j++;
182 }
183 }
184
185 return refs;
186 }
187
188 /**
189 * Answer whether Bundle a is higher or lower depending on the ranking and
190 * id of its exported services. This is used as a tie-breaker for circular
191 * references.
192 */
193 protected int compareUsingServiceRankingAndId(Bundle a, Bundle b) {
194 ServiceReference[] aservices = excludeNonSpringManagedServices(a.getRegisteredServices());
195 ServiceReference[] bservices = excludeNonSpringManagedServices(b.getRegisteredServices());
196
197 boolean trace = log.isTraceEnabled();
198
199
200 if (ObjectUtils.isEmpty(aservices) && ObjectUtils.isEmpty(bservices)) {
201 if (trace)
202 log.trace("both services have 0 services; sorting based on id");
203 return (int) (a.getBundleId() - b.getBundleId());
204 }
205 else if (aservices == null) {
206 return -1;
207 }
208 else if (bservices == null) {
209 return 1;
210 }
211
212
213
214
215
216
217
218 int aRank = findHighestServiceRanking(aservices);
219 int bRank = findHighestServiceRanking(bservices);
220
221
222
223 if (aRank != bRank) {
224 int compare = -(bRank - aRank);
225 if (trace) {
226 int min = (compare > 0 ? (int) bRank : (int) aRank);
227 log.trace("sorting based on lowest-service-ranking won by bundle" + (compare > 0 ? "1" : "2")
228 + " w/ service id " + min);
229 }
230
231 return -(bRank - aRank);
232 }
233
234
235 long aMaxId = findHighestServiceId(aservices);
236 long bMaxId = findHighestServiceId(bservices);
237
238 if (aMaxId != bMaxId) {
239 int compare = (int) (bMaxId - aMaxId);
240 if (trace) {
241 int max = (compare > 0 ? (int) bMaxId : (int) aMaxId);
242 log.trace("sorting based on highest-service-id won by bundle " + (compare > 0 ? "1" : "2")
243 + " w/ service id " + max);
244 }
245
246 return compare;
247 }
248
249 return (int) (a.getBundleId() - b.getBundleId());
250 }
251
252 /**
253 * Find the highest service ranking. This might come as unexpected however,
254 * since a bundle can export multiple services, we have to find the minimum
255 * between the maximum services in each bundle - i.e. the bundle with the
256 * highest service ranking will win.
257 *
258 * @param refs
259 */
260 private int findHighestServiceRanking(ServiceReference[] refs) {
261 int maxRank = Integer.MIN_VALUE;
262 for (int i = 0; i < refs.length; i++) {
263 int currentRank = OsgiServiceReferenceUtils.getServiceRanking(refs[i]);
264 if (currentRank > maxRank)
265 maxRank = currentRank;
266 }
267
268 return maxRank;
269 }
270
271 private long findHighestServiceId(ServiceReference[] refs) {
272 long maxId = Long.MIN_VALUE;
273 for (int i = 0; i < refs.length; i++) {
274 long currentId = OsgiServiceReferenceUtils.getServiceId(refs[i]);
275 if (currentId > maxId)
276 maxId = currentId;
277 }
278
279 return maxId;
280 }
281 }