View Javadoc

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  				// Sort nulls first
57  				return 1;
58  			}
59  		}
60  		else if (bundle2 == null) {
61  			// Sort nulls first
62  			return -1;
63  		}
64  
65  		// At this point, we know that bundle1 and bundle2 are not null
66  		if (bundle1.equals(bundle2)) {
67  			return 0;
68  		}
69  
70  		// At this point, bundle1 and bundle2 are not null and not equal, here
71  		// we
72  		// compare them to see which is "higher" in the dependency graph
73  		boolean b1Lower = references(bundle2, bundle1);
74  		boolean b2Lower = references(bundle1, bundle2);
75  
76  		if (b1Lower && !b2Lower) {
77  			// b2->b1
78  			return 1;
79  		}
80  		else if (b2Lower && !b1Lower) {
81  			// b1->b2
82  			return -1;
83  		}
84  		// Do a decent job of informing the user about a circularity
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  		// Deal with circular references and unrelated bundles.
91  		// both bundles refer to themselves
92  		// b2 -> b1
93  		// b1 -> b2
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 			// filter on spring managed services
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 		// this case should not occur
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 		// Look for the *lowest* highest ranked service in each bundle
213 		// i.e. take a look at each bundle, find the highest ranking service
214 		// and compare it to the other bundle
215 		// this means that the service with the highest ranking service will
216 		// be shutdown last
217 
218 		int aRank = findHighestServiceRanking(aservices);
219 		int bRank = findHighestServiceRanking(bservices);
220 
221 		// since we are looking for the minimum, invert the substraction
222 		// (the higher bundle is the one with the lowest rank)
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 		// Look for the highest id in each bundle (i.e. started last).
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 }