DescriptorBuilder.java |
/* * $Id: DescriptorBuilder.java,v 1.33 2012/05/12 15:14:47 agoubard Exp $ * * See the COPYRIGHT file for redistribution and use restrictions. */ package org.xins.common.service; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.util.Map; import java.util.StringTokenizer; import org.xins.common.Log; import org.xins.common.MandatoryArgumentChecker; import org.xins.common.collections.InvalidPropertyValueException; import org.xins.common.collections.MapStringUtils; import org.xins.common.collections.MissingRequiredPropertyException; /** * Builder that can build a <code>Descriptor</code> object based on a set of * properties. * * <h3>Examples</h3> * * <p>The following example is the definition of a single back-end at * <code>http://somehost/</code>, identified by the property name * <code>"s1"</code>, the time-out is set to 20 seconds: * * <blockquote><code>s1=service, http://somehost/, 20000</code></blockquote> * * <p>The next example is the definition of 4 back-ends, of which one will be * chosen randomly. This setting is identified by the property name * <code>"capi.sso"</code>: * * <blockquote><code># The root definition "capi.sso" * <br>capi.sso=group, random, target1, target2, target3, target4 * <br> * <br># Total time-out is 12.5 seconds, no connection time-out and no socket * <br># time-out * <br>capi.sso.target1=service, http://somehost/, 12500 * <br> * <br># Total time-out is 12.5 seconds, connection time-out is 4 seconds and * <br># no socket time-out * <br>capi.sso.target2=service, http://othrhost/, 12500, 4000 * <br> * <br># Total time-out is 12.5 seconds, connection time-out is 4 seconds, * <br># socket time-out is 2 seconds * <br>capi.sso.target3=service, http://othrhost:2001/, 12500, 4000, 2000 * <br> * <br># Total time-out is not set, connection time-out is not set and socket * <br># time-out is 2 seconds * <br>capi.sso.target4=service, http://othrhost:2002/, 0, 0, * 2000</code></blockquote> * * <p>The last example defines 2 back-ends at a more preferred location and 1 * at a less-preferred location. Normally one of the 2 back-ends at the * preferred location will be chosen randomly, but if none is available, then * the back-end at the less preferred location will be tried. The time-out for * all back-ends in 8 seconds. The name of the property is <code>"ldap"</code>: * * <blockquote><code>ldap=group, ordered, loc1, host2a * <br>ldap.loc1=group, random, host1a, host1b * <br>ldap.host1a=service, ldap://host1a/, 8000 * <br>ldap.host1b=service, ldap://host1b/, 8000 * <br>ldap.host2a=service, ldap://host2a/, 8000</code></blockquote> * * @version $Revision: 1.33 $ $Date: 2012/05/12 15:14:47 $ * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a> * * @since XINS 1.0.0 */ public final class DescriptorBuilder { /** * Delimiter between tokens within a property value. This is the comma * character <code>','</code>. */ public static final char DELIMITER = ','; /** * Delimiter between property lines. This is the carriage return * character <code>'\n'</code>. */ public static final char LINE_DELIMITER = '\n'; /** * Delimiters between tokens within a property value. */ private static final String DELIMITER_AS_STRING = String.valueOf(DELIMITER); /** * Name identifying an actual target descriptor. */ public static final String TARGET_DESCRIPTOR_TYPE = "service"; /** * Name identifying a group of descriptors. */ public static final String GROUP_DESCRIPTOR_TYPE = "group"; /** * Constructs a new <code>DescriptorBuilder</code>. */ private DescriptorBuilder() { // empty // NOTE: No tracing is performed, since this constructor is never used } /** * Tokenizes the specified string. The {@link #DELIMITER_AS_STRING} will be * used as the token delimiter. Every token will be one element in the * returned {@link String} array. * * @param s * the {@link String} to tokenize, cannot be <code>null</code>. * * @return * the list of tokens as a {@link String} array, never * <code>null</code>. */ private static String[] tokenize(String s) { // Create a StringTokenizer StringTokenizer tokenizer = new StringTokenizer(s, DELIMITER_AS_STRING); // Create a new array to store the tokens in int count = tokenizer.countTokens(); String[] tokens = new String[count]; // Copy all tokens into the array for (int i = 0; i < count; i++) { tokens[i] = tokenizer.nextToken().trim(); } return tokens; } /** * Builds a <code>Descriptor</code> based on the specified set of * properties, for the specified service caller. * * @param caller * the caller to create a {@link Descriptor} for, can be * <code>null</code> if unknown. * * @param properties * the properties to read from, cannot be <code>null</code>. * * @param propertyName * the base for the property names, cannot be <code>null</code>. * * @return * the {@link Descriptor} that was built, never <code>null</code>. * * @throws IllegalArgumentException * if <code>properties == null || propertyName == null</code>. * * @throws MissingRequiredPropertyException * if the property named <code>propertyName</code> cannot be found in * <code>properties</code>, or if a referenced property cannot be found. * * @throws InvalidPropertyValueException * if the property named <code>propertyName</code> is found in * <code>properties</code>, but the format of this property or the * format of a referenced property is invalid. * * @since XINS 1.2.0 */ public static Descriptor build(ServiceCaller caller, Map<String, String> properties, String propertyName) throws IllegalArgumentException, MissingRequiredPropertyException, InvalidPropertyValueException { // Check preconditions MandatoryArgumentChecker.check("properties", properties, "propertyName", propertyName); return build(caller, properties, propertyName, null); } /** * Builds a <code>Descriptor</code> based on the specified set of * properties. * * @param properties * the properties to read from, cannot be <code>null</code>. * * @param propertyName * the base for the property names, cannot be <code>null</code>. * * @return * the {@link Descriptor} that was built, never <code>null</code>. * * @throws IllegalArgumentException * if <code>properties == null || propertyName == null</code>. * * @throws MissingRequiredPropertyException * if the property named <code>propertyName</code> cannot be found in * <code>properties</code>, or if a referenced property cannot be found. * * @throws InvalidPropertyValueException * if the property named <code>propertyName</code> is found in * <code>properties</code>, but the format of this property or the * format of a referenced property is invalid. */ public static Descriptor build(Map<String, String> properties, String propertyName) throws IllegalArgumentException, MissingRequiredPropertyException, InvalidPropertyValueException { // Check preconditions MandatoryArgumentChecker.check("properties", properties, "propertyName", propertyName); return build((ServiceCaller) null, properties, propertyName); } /** * Builds a <code>Descriptor</code> based on the specified value. * * @param descriptorValue * the value of the descriptor, cannot be <code>null</code>. * the value must have the same value as specified at the top, the lines * should be separated with '\n' and the first line must start with * the name of the property followed by the sign '='. * * @return * the {@link Descriptor} that was built, never <code>null</code>. * * @throws IllegalArgumentException * if <code>descriptorValue == null</code> or the property name cannot * be found in the value. * * @throws MissingRequiredPropertyException * if the property named <code>propertyName</code> cannot be found in * <code>properties</code>, or if a referenced property cannot be found. * * @throws InvalidPropertyValueException * if the property named <code>propertyName</code> is found in * <code>properties</code>, but the format of this property or the * format of a referenced property is invalid. */ public static Descriptor build(String descriptorValue) throws IllegalArgumentException, MissingRequiredPropertyException, InvalidPropertyValueException { // Check preconditions MandatoryArgumentChecker.check("descriptorValue", descriptorValue); int equalsPos = descriptorValue.indexOf('='); int crPos = descriptorValue.indexOf(LINE_DELIMITER); if (equalsPos <= 0 || (crPos > 0 && equalsPos > crPos)) { throw new IllegalArgumentException( "No property name found in \"" + descriptorValue + "\"."); } String propertyName = descriptorValue.substring(0, equalsPos); final String ENCODING = "ISO-8859-1"; try { byte[] bytes = descriptorValue.getBytes(ENCODING); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); Map<String, String> properties = MapStringUtils.createMapString(bais); bais.close(); return build((ServiceCaller) null, properties, propertyName); } catch (IOException ioe) { throw new InvalidPropertyValueException(propertyName, descriptorValue); } } /** * Builds a <code>Descriptor</code> based on the specified set of * properties, specifying base property and reference. * * @param caller * the service caller to build a descriptor for, or <code>null</code> if * unknown. * * @param properties * the properties to read from, should not be <code>null</code>. * * @param baseProperty * the name of the base property, should not be <code>null</code>. * * @param reference * the name of the reference, relative to the base property, can be * <code>null</code>. * * @return * the {@link Descriptor} that was built, never <code>null</code>. * * @throws NullPointerException * if <code>properties == null</code>. * * @throws MissingRequiredPropertyException * if a required property cannot be found. * * @throws InvalidPropertyValueException * if the property named <code>propertyName</code> is found in * <code>properties</code>, but the format of this property or the * format of a referenced property is invalid. */ private static Descriptor build(ServiceCaller caller, Map<String, String> properties, String baseProperty, String reference) throws NullPointerException, MissingRequiredPropertyException, InvalidPropertyValueException { // Determine the property name String propertyName = reference == null ? baseProperty : baseProperty + '.' + reference; // Get the value of the property String value = properties.get(propertyName); if (value == null) { throw new MissingRequiredPropertyException(propertyName); } // Tokenize the value String[] tokens = tokenize(value); int tokenCount = tokens.length; if (tokenCount < 3) { throw new InvalidPropertyValueException(propertyName, value, "Expected at least 3 tokens."); } // Determine the type String descriptorType = tokens[0]; // Parse target descriptor if (TARGET_DESCRIPTOR_TYPE.equals(descriptorType)) { if (tokenCount > 5) { throw new InvalidPropertyValueException(propertyName, value, "Expected URL and time-out."); } // Determine URL String url = tokens[1]; // Determine the total time-out (mandatory) int timeOut; try { timeOut = Integer.parseInt(tokens[2]); } catch (NumberFormatException nfe) { throw new InvalidPropertyValueException(propertyName, value, "Unable to parse total time-out as a 32-bit integer number."); } if (timeOut < 0) { throw new InvalidPropertyValueException(propertyName, value, "Total time-out is negative."); } // Determine the connection time-out (optional) int connectionTimeOut; if (tokenCount > 3) { try { connectionTimeOut = Integer.parseInt(tokens[3]); } catch (NumberFormatException nfe) { throw new InvalidPropertyValueException(propertyName, value, "Unable to parse connection time-out as a 32-bit integer number."); } if (connectionTimeOut < 0) { throw new InvalidPropertyValueException(propertyName, value, "Connection time-out is negative."); } } else { connectionTimeOut = 0; } // Determine the socket time-out (optional) int socketTimeOut; if (tokenCount > 4) { try { socketTimeOut = Integer.parseInt(tokens[4]); } catch (NumberFormatException nfe) { throw new InvalidPropertyValueException(propertyName, value, "Unable to parse socket time-out as a 32-bit integer number."); } if (socketTimeOut < 0) { throw new InvalidPropertyValueException(propertyName, value, "Socket time-out is negative."); } } else { socketTimeOut = 0; } // Construct a TargetDescriptor instance TargetDescriptor td; try { td = new TargetDescriptor(url, timeOut, connectionTimeOut, socketTimeOut); } catch (MalformedURLException exception) { Log.log_1300(exception, url); throw new InvalidPropertyValueException(propertyName, value, "Malformed URL."); } // Test the protocol if (caller != null) { try { caller.testTargetDescriptor(td); } catch (UnsupportedProtocolException cause) { Log.log_1308(url); InvalidPropertyValueException exception = new InvalidPropertyValueException(propertyName, value, "Unsupported protocol."); exception.initCause(cause); throw exception; } } return td; // Parse group descriptor } else if (GROUP_DESCRIPTOR_TYPE.equals(descriptorType)) { GroupDescriptor.Type groupType = GroupDescriptor.getType(tokens[1]); if (groupType == null) { throw new InvalidPropertyValueException(propertyName, value, "Unrecognized group descriptor type \"" + tokens[1] + "\"."); } int memberCount = tokenCount - 2; if (memberCount < 2) { throw new InvalidPropertyValueException(propertyName, value, "Group descriptor member count is " + memberCount + ", while minimum is 2."); } Descriptor[] members = new Descriptor[memberCount]; for (int i = 0; i < memberCount; i++) { members[i] = build(caller, properties, baseProperty, tokens[i + 2]); } return new GroupDescriptor(groupType, members); // Unrecognized descriptor type } else { throw new InvalidPropertyValueException(propertyName, value, "Expected valid descriptor type: either \"" + TARGET_DESCRIPTOR_TYPE + "\" or \"" + GROUP_DESCRIPTOR_TYPE + "\"."); } } }