/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 * is available at http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * VMware Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.extender.support.internal; import java.util.Dictionary; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.osgi.framework.Bundle; import org.osgi.framework.Version; import org.eclipse.gemini.blueprint.context.support.OsgiBundleXmlApplicationContext; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * Utility class for dealing with the extender configuration and OSGi bundle * manifest headers. * * Defines Spring/OSGi constants and methods for configuring Spring application * context. * * @author Costin Leau * */ public abstract class ConfigUtils { private static final Log log = LogFactory.getLog(ConfigUtils.class); public static final String EXTENDER_VERSION = "SpringExtender-Version"; private static final String LEFT_CLOSED_INTERVAL = "["; private static final String LEFT_OPEN_INTERVAL = "("; private static final String RIGHT_CLOSED_INTERVAL = "]"; private static final String RIGHT_OPEN_INTERVAL = ")"; private static final String COMMA = ","; public static final String CONFIG_WILDCARD = "*"; /** * Manifest entry name for configuring Spring application context. */ public static final String SPRING_CONTEXT_HEADER = "Spring-Context"; /** * Directive for publishing Spring application context as a service. */ public static final String DIRECTIVE_PUBLISH_CONTEXT = "publish-context"; /** * Directive for indicating wait-for time when satisfying mandatory * dependencies defined in seconds */ public static final String DIRECTIVE_TIMEOUT = "timeout"; public static final String DIRECTIVE_TIMEOUT_VALUE_NONE = "none"; /** * Create asynchronously directive. */ public static final String DIRECTIVE_CREATE_ASYNCHRONOUSLY = "create-asynchronously"; /** * Wait for dependencies or directly start the context. */ public static final String DIRECTIVE_WAIT_FOR_DEPS = "wait-for-dependencies"; /** * {@link #DIRECTIVE_WAIT_FOR_DEPS} default. */ public static final boolean DIRECTIVE_WAIT_FOR_DEPS_DEFAULT = true; public static final String EQUALS = ":="; /** * Token used for separating directives inside a header. */ public static final String DIRECTIVE_SEPARATOR = ";"; public static final String CONTEXT_LOCATION_SEPARATOR = ","; public static final boolean DIRECTIVE_PUBLISH_CONTEXT_DEFAULT = true; public static final boolean DIRECTIVE_CREATE_ASYNCHRONOUSLY_DEFAULT = true; public static final long DIRECTIVE_TIMEOUT_DEFAULT = 5 * 60; // 5 minutes public static final long DIRECTIVE_NO_TIMEOUT = -2L; // Indicates wait forever public static boolean matchExtenderVersionRange(Bundle bundle, String header, Version versionToMatch) { Assert.notNull(bundle); // get version range String range = (String) bundle.getHeaders().get(header); boolean trace = log.isTraceEnabled(); // empty value = empty version = * if (!StringUtils.hasText(range)) return true; if (trace) log.trace("discovered " + header + " header w/ value=" + range); // do we have a range or not ? range = StringUtils.trimWhitespace(range); // a range means one comma int commaNr = StringUtils.countOccurrencesOf(range, COMMA); // no comma, no intervals if (commaNr == 0) { Version version = Version.parseVersion(range); return versionToMatch.equals(version); } if (commaNr == 1) { // sanity check if (!((range.startsWith(LEFT_CLOSED_INTERVAL) || range.startsWith(LEFT_OPEN_INTERVAL)) && (range.endsWith(RIGHT_CLOSED_INTERVAL) || range.endsWith(RIGHT_OPEN_INTERVAL)))) { throw new IllegalArgumentException("range [" + range + "] is invalid"); } boolean equalMin = range.startsWith(LEFT_CLOSED_INTERVAL); boolean equalMax = range.endsWith(RIGHT_CLOSED_INTERVAL); // remove interval brackets range = range.substring(1, range.length() - 1); // split the remaining string in two pieces String[] pieces = StringUtils.split(range, COMMA); if (trace) log.trace("discovered low/high versions : " + ObjectUtils.nullSafeToString(pieces)); Version minVer = Version.parseVersion(pieces[0]); Version maxVer = Version.parseVersion(pieces[1]); if (trace) log.trace("comparing version " + versionToMatch + " w/ min=" + minVer + " and max=" + maxVer); boolean result = true; int compareMin = versionToMatch.compareTo(minVer); if (equalMin) result = (result && (compareMin >= 0)); else result = (result && (compareMin > 0)); int compareMax = versionToMatch.compareTo(maxVer); if (equalMax) result = (result && (compareMax <= 0)); else result = (result && (compareMax < 0)); return result; } // more then one comma means incorrect range throw new IllegalArgumentException("range [" + range + "] is invalid"); } /** * Return the {@value #SPRING_CONTEXT_HEADER} if present from the given * dictionary. * * @param headers * @return */ public static String getSpringContextHeader(Dictionary headers) { Object header = null; if (headers != null) header = headers.get(SPRING_CONTEXT_HEADER); return (header != null ? header.toString().trim() : null); } /** * Return the directive value as a String. If the directive does not exist * or is invalid (wrong format) a null string will be returned. * * @param header * @param directive * @return */ public static String getDirectiveValue(String header, String directive) { Assert.notNull(header, "not-null header required"); Assert.notNull(directive, "not-null directive required"); String[] directives = StringUtils.tokenizeToStringArray(header, DIRECTIVE_SEPARATOR); for (int i = 0; i < directives.length; i++) { String[] splittedDirective = StringUtils.delimitedListToStringArray(directives[i].trim(), EQUALS); if (splittedDirective.length == 2 && splittedDirective[0].equals(directive)) return splittedDirective[1]; } return null; } /** * Shortcut method to retrieve directive values. Used internally by the * dedicated getXXX. * * @param directiveName * @return */ private static String getDirectiveValue(Dictionary headers, String directiveName) { String header = getSpringContextHeader(headers); if (header != null) { String directive = getDirectiveValue(header, directiveName); if (directive != null) return directive; } return null; } /** * Returns true if the given directive is present or false otherwise. * * @param headers * @param directiveName * @return */ public static boolean isDirectiveDefined(Dictionary headers, String directiveName) { String header = getSpringContextHeader(headers); if (header != null) { String directive = getDirectiveValue(header, directiveName); return (directive != null); } return false; } /** * Shortcut for finding the boolean value for * {@link #DIRECTIVE_PUBLISH_CONTEXT} directive using the given headers. * Assumes the headers belong to a Spring powered bundle. * * @param headers * @return */ public static boolean getPublishContext(Dictionary headers) { String value = getDirectiveValue(headers, DIRECTIVE_PUBLISH_CONTEXT); return (value != null ? Boolean.valueOf(value).booleanValue() : DIRECTIVE_PUBLISH_CONTEXT_DEFAULT); } /** * Shortcut for finding the boolean value for * {@link #DIRECTIVE_CREATE_ASYNCHRONOUSLY} directive using the given * headers. * * Assumes the headers belong to a Spring powered bundle. * * @param headers * @return */ public static boolean getCreateAsync(Dictionary headers) { String value = getDirectiveValue(headers, DIRECTIVE_CREATE_ASYNCHRONOUSLY); return (value != null ? Boolean.valueOf(value).booleanValue() : DIRECTIVE_CREATE_ASYNCHRONOUSLY_DEFAULT); } /** * Shortcut for finding the boolean value for {@link #DIRECTIVE_TIMEOUT} * directive using the given headers. * * Assumes the headers belong to a Spring powered bundle. Returns the * timeout (in seconds) for which the application context should wait to * have its dependencies satisfied. * * @param headers * @return */ public static long getTimeOut(Dictionary headers) { String value = getDirectiveValue(headers, DIRECTIVE_TIMEOUT); if (value != null) { if (DIRECTIVE_TIMEOUT_VALUE_NONE.equalsIgnoreCase(value)) { return DIRECTIVE_NO_TIMEOUT; } return Long.valueOf(value).longValue(); } return DIRECTIVE_TIMEOUT_DEFAULT; } /** * Shortcut for finding the boolean value for * {@link #DIRECTIVE_WAIT_FOR_DEPS} directive using the given headers. * Assumes the headers belong to a Spring powered bundle. * * @param headers * @return */ public static boolean getWaitForDependencies(Dictionary headers) { String value = getDirectiveValue(headers, DIRECTIVE_WAIT_FOR_DEPS); return (value != null ? Boolean.valueOf(value).booleanValue() : DIRECTIVE_WAIT_FOR_DEPS_DEFAULT); } /** * Returns the location headers (if any) specified by the Spring-Context * header (if available). The returned Strings can be sent to a * {@link org.springframework.core.io.ResourceLoader} for loading the * configurations. * * @param headers bundle headers * @return array of locations specified (if any) */ public static String[] getHeaderLocations(Dictionary headers) { return getLocationsFromHeader(getSpringContextHeader(headers), OsgiBundleXmlApplicationContext.DEFAULT_CONFIG_LOCATION); } /** * Similar to {@link #getHeaderLocations(Dictionary)} but looks at a * specified header directly. * * @param header header to look at * @param defaultValue default locations if none is specified * @return */ public static String[] getLocationsFromHeader(String header, String defaultValue) { String[] ctxEntries; if (StringUtils.hasText(header) && !(';' == header.charAt(0))) { // get the config locations String locations = StringUtils.tokenizeToStringArray(header, DIRECTIVE_SEPARATOR)[0]; // parse it into individual token ctxEntries = StringUtils.tokenizeToStringArray(locations, CONTEXT_LOCATION_SEPARATOR); // replace * with a 'digestable' location for (int i = 0; i < ctxEntries.length; i++) { if (CONFIG_WILDCARD.equals(ctxEntries[i])) ctxEntries[i] = defaultValue; } } else { ctxEntries = new String[0]; } return ctxEntries; } }