View Javadoc

1   /*
2    * Copyright 2006-2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.osgi.extender.support.internal;
18  
19  import java.util.Dictionary;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.osgi.framework.Bundle;
24  import org.osgi.framework.Version;
25  import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext;
26  import org.springframework.util.Assert;
27  import org.springframework.util.ObjectUtils;
28  import org.springframework.util.StringUtils;
29  
30  /**
31   * Utility class for dealing with the extender configuration and OSGi bundle
32   * manifest headers.
33   * 
34   * Defines Spring/OSGi constants and methods for configuring Spring application
35   * context.
36   * 
37   * @author Costin Leau
38   * 
39   */
40  public abstract class ConfigUtils {
41  
42  	private static final Log log = LogFactory.getLog(ConfigUtils.class);
43  
44  	public static final String EXTENDER_VERSION = "SpringExtender-Version";
45  
46  	private static final String LEFT_CLOSED_INTERVAL = "[";
47  
48  	private static final String LEFT_OPEN_INTERVAL = "(";
49  
50  	private static final String RIGHT_CLOSED_INTERVAL = "]";
51  
52  	private static final String RIGHT_OPEN_INTERVAL = ")";
53  
54  	private static final String COMMA = ",";
55  
56  	public static final String CONFIG_WILDCARD = "*";
57  
58  	/**
59  	 * Manifest entry name for configuring Spring application context.
60  	 */
61  	public static final String SPRING_CONTEXT_HEADER = "Spring-Context";
62  
63  	/**
64  	 * Directive for publishing Spring application context as a service.
65  	 */
66  	public static final String DIRECTIVE_PUBLISH_CONTEXT = "publish-context";
67  
68  	/**
69  	 * Directive for indicating wait-for time when satisfying mandatory
70  	 * dependencies defined in seconds
71  	 */
72  	public static final String DIRECTIVE_TIMEOUT = "timeout";
73  
74  	public static final String DIRECTIVE_TIMEOUT_VALUE_NONE = "none";
75  
76  	/**
77  	 * Create asynchronously directive.
78  	 */
79  	public static final String DIRECTIVE_CREATE_ASYNCHRONOUSLY = "create-asynchronously";
80  
81  	/**
82  	 * Wait for dependencies or directly start the context.
83  	 */
84  	public static final String DIRECTIVE_WAIT_FOR_DEPS = "wait-for-dependencies";
85  
86  	/**
87  	 * {@link #DIRECTIVE_WAIT_FOR_DEPS} default.
88  	 */
89  	public static final boolean DIRECTIVE_WAIT_FOR_DEPS_DEFAULT = true;
90  
91  	public static final String EQUALS = ":=";
92  
93  	/**
94  	 * Token used for separating directives inside a header.
95  	 */
96  	public static final String DIRECTIVE_SEPARATOR = ";";
97  
98  	public static final String CONTEXT_LOCATION_SEPARATOR = ",";
99  
100 	public static final boolean DIRECTIVE_PUBLISH_CONTEXT_DEFAULT = true;
101 
102 	public static final boolean DIRECTIVE_CREATE_ASYNCHRONOUSLY_DEFAULT = true;
103 
104 	public static final long DIRECTIVE_TIMEOUT_DEFAULT = 5 * 60; // 5 minutes
105 
106 	public static final long DIRECTIVE_NO_TIMEOUT = -2L; // Indicates wait
107 
108 
109 	// forever
110 
111 	public static boolean matchExtenderVersionRange(Bundle bundle, Version versionToMatch) {
112 		Assert.notNull(bundle);
113 		// get version range
114 		String range = (String) bundle.getHeaders().get(EXTENDER_VERSION);
115 
116 		boolean trace = log.isTraceEnabled();
117 
118 		// empty value = empty version = *
119 		if (!StringUtils.hasText(range))
120 			return true;
121 
122 		if (trace)
123 			log.trace("discovered " + EXTENDER_VERSION + " header w/ value=" + range);
124 
125 		// do we have a range or not ?
126 		range = StringUtils.trimWhitespace(range);
127 
128 		// a range means one comma
129 		int commaNr = StringUtils.countOccurrencesOf(range, COMMA);
130 
131 		// no comma, no intervals
132 		if (commaNr == 0) {
133 			Version version = Version.parseVersion(range);
134 
135 			return versionToMatch.equals(version);
136 		}
137 
138 		if (commaNr == 1) {
139 
140 			// sanity check
141 			if (!((range.startsWith(LEFT_CLOSED_INTERVAL) || range.startsWith(LEFT_OPEN_INTERVAL)) && (range.endsWith(RIGHT_CLOSED_INTERVAL) || range.endsWith(RIGHT_OPEN_INTERVAL)))) {
142 				throw new IllegalArgumentException("range [" + range + "] is invalid");
143 			}
144 
145 			boolean equalMin = range.startsWith(LEFT_CLOSED_INTERVAL);
146 			boolean equalMax = range.endsWith(RIGHT_CLOSED_INTERVAL);
147 
148 			// remove interval brackets
149 			range = range.substring(1, range.length() - 1);
150 
151 			// split the remaining string in two pieces
152 			String[] pieces = StringUtils.split(range, COMMA);
153 
154 			if (trace)
155 				log.trace("discovered low/high versions : " + ObjectUtils.nullSafeToString(pieces));
156 
157 			Version minVer = Version.parseVersion(pieces[0]);
158 			Version maxVer = Version.parseVersion(pieces[1]);
159 
160 			if (trace)
161 				log.trace("comparing version " + versionToMatch + " w/ min=" + minVer + " and max=" + maxVer);
162 
163 			boolean result = true;
164 
165 			int compareMin = versionToMatch.compareTo(minVer);
166 
167 			if (equalMin)
168 				result = (result && (compareMin >= 0));
169 			else
170 				result = (result && (compareMin > 0));
171 
172 			int compareMax = versionToMatch.compareTo(maxVer);
173 
174 			if (equalMax)
175 				result = (result && (compareMax <= 0));
176 			else
177 				result = (result && (compareMax < 0));
178 
179 			return result;
180 		}
181 
182 		// more then one comma means incorrect range
183 
184 		throw new IllegalArgumentException("range [" + range + "] is invalid");
185 	}
186 
187 	/**
188 	 * Return the {@value #SPRING_CONTEXT_HEADER} if present from the given
189 	 * dictionary.
190 	 * 
191 	 * @param headers
192 	 * @return
193 	 */
194 	public static String getSpringContextHeader(Dictionary headers) {
195 		Object header = null;
196 		if (headers != null)
197 			header = headers.get(SPRING_CONTEXT_HEADER);
198 		return (header != null ? header.toString().trim() : null);
199 	}
200 
201 	/**
202 	 * Return the directive value as a String. If the directive does not exist
203 	 * or is invalid (wrong format) a null string will be returned.
204 	 * 
205 	 * @param header
206 	 * @param directive
207 	 * @return
208 	 */
209 	public static String getDirectiveValue(String header, String directive) {
210 		Assert.notNull(header, "not-null header required");
211 		Assert.notNull(directive, "not-null directive required");
212 		String[] directives = StringUtils.tokenizeToStringArray(header, DIRECTIVE_SEPARATOR);
213 
214 		for (int i = 0; i < directives.length; i++) {
215 			String[] splittedDirective = StringUtils.delimitedListToStringArray(directives[i].trim(), EQUALS);
216 			if (splittedDirective.length == 2 && splittedDirective[0].equals(directive))
217 				return splittedDirective[1];
218 		}
219 
220 		return null;
221 	}
222 
223 	/**
224 	 * Shortuct method to retrieve directive values. Used internally by the
225 	 * dedicated getXXX.
226 	 * 
227 	 * @param directiveName
228 	 * @return
229 	 */
230 	private static String getDirectiveValue(Dictionary headers, String directiveName) {
231 		String header = getSpringContextHeader(headers);
232 		if (header != null) {
233 			String directive = getDirectiveValue(header, directiveName);
234 			if (directive != null)
235 				return directive;
236 		}
237 		return null;
238 	}
239 
240 	/**
241 	 * Shortcut for finding the boolean value for
242 	 * {@link #DIRECTIVE_PUBLISH_CONTEXT} directive using the given headers.
243 	 * Assumes the headers belong to a Spring powered bundle.
244 	 * 
245 	 * @param headers
246 	 * @return
247 	 */
248 	public static boolean getPublishContext(Dictionary headers) {
249 		String value = getDirectiveValue(headers, DIRECTIVE_PUBLISH_CONTEXT);
250 		return (value != null ? Boolean.valueOf(value).booleanValue() : DIRECTIVE_PUBLISH_CONTEXT_DEFAULT);
251 	}
252 
253 	/**
254 	 * Shortcut for finding the boolean value for
255 	 * {@link #DIRECTIVE_CREATE_ASYNCHRONOUSLY} directive using the given
256 	 * headers.
257 	 * 
258 	 * Assumes the headers belong to a Spring powered bundle.
259 	 * 
260 	 * @param headers
261 	 * @return
262 	 */
263 	public static boolean getCreateAsync(Dictionary headers) {
264 		String value = getDirectiveValue(headers, DIRECTIVE_CREATE_ASYNCHRONOUSLY);
265 		return (value != null ? Boolean.valueOf(value).booleanValue() : DIRECTIVE_CREATE_ASYNCHRONOUSLY_DEFAULT);
266 	}
267 
268 	/**
269 	 * Shortcut for finding the boolean value for {@link #DIRECTIVE_TIMEOUT}
270 	 * directive using the given headers.
271 	 * 
272 	 * Assumes the headers belong to a Spring powered bundle. Returns the
273 	 * timeout (in seconds) for which the application context should wait to
274 	 * have its dependencies satisfied.
275 	 * 
276 	 * @param headers
277 	 * @return
278 	 */
279 	public static long getTimeOut(Dictionary headers) {
280 		String value = getDirectiveValue(headers, DIRECTIVE_TIMEOUT);
281 
282 		if (value != null) {
283 			if (DIRECTIVE_TIMEOUT_VALUE_NONE.equalsIgnoreCase(value)) {
284 				return DIRECTIVE_NO_TIMEOUT;
285 			}
286 			return Long.valueOf(value).longValue();
287 		}
288 
289 		return DIRECTIVE_TIMEOUT_DEFAULT;
290 	}
291 
292 	/**
293 	 * Shortcut for finding the boolean value for
294 	 * {@link #DIRECTIVE_WAIT_FOR_DEPS} directive using the given headers.
295 	 * Assumes the headers belong to a Spring powered bundle.
296 	 * 
297 	 * @param headers
298 	 * @return
299 	 */
300 	public static boolean getWaitForDependencies(Dictionary headers) {
301 		String value = getDirectiveValue(headers, DIRECTIVE_WAIT_FOR_DEPS);
302 
303 		return (value != null ? Boolean.valueOf(value).booleanValue() : DIRECTIVE_WAIT_FOR_DEPS_DEFAULT);
304 	}
305 
306 	/**
307 	 * Returns the location headers (if any) specified by the Spring-Context
308 	 * header (if available). The returned Strings can be sent to a
309 	 * {@link org.springframework.core.io.ResourceLoader} for loading the
310 	 * configurations.
311 	 * 
312 	 * @param headers bundle headers
313 	 * @return array of locations specified (if any)
314 	 */
315 	public static String[] getHeaderLocations(Dictionary headers) {
316 		String header = getSpringContextHeader(headers);
317 
318 		String[] ctxEntries;
319 		if (StringUtils.hasText(header) && !(';' == header.charAt(0))) {
320 			// get the config locations
321 			String locations = StringUtils.tokenizeToStringArray(header, DIRECTIVE_SEPARATOR)[0];
322 			// parse it into individual token
323 			ctxEntries = StringUtils.tokenizeToStringArray(locations, CONTEXT_LOCATION_SEPARATOR);
324 
325 			// replace * with a 'digestable' location
326 			for (int i = 0; i < ctxEntries.length; i++) {
327 				if (CONFIG_WILDCARD.equals(ctxEntries[i]))
328 					ctxEntries[i] = OsgiBundleXmlApplicationContext.DEFAULT_CONFIG_LOCATION;
329 			}
330 		}
331 		else {
332 			ctxEntries = new String[0];
333 		}
334 
335 		return ctxEntries;
336 	}
337 }