001: /*
002: * $Id: DescriptorBuilder.java,v 1.28 2007/03/16 09:54:59 agoubard Exp $
003: *
004: * Copyright 2003-2007 Orange Nederland Breedband B.V.
005: * See the COPYRIGHT file for redistribution and use restrictions.
006: */
007: package org.xins.common.service;
008:
009: import java.io.ByteArrayInputStream;
010: import java.io.IOException;
011: import java.net.MalformedURLException;
012: import java.util.StringTokenizer;
013:
014: import org.xins.common.Log;
015: import org.xins.common.MandatoryArgumentChecker;
016: import org.xins.common.collections.InvalidPropertyValueException;
017: import org.xins.common.collections.MissingRequiredPropertyException;
018: import org.xins.common.collections.PropertyReader;
019: import org.xins.common.collections.PropertyReaderUtils;
020: import org.xins.logdoc.ExceptionUtils;
021:
022: /**
023: * Builder that can build a <code>Descriptor</code> object based on a set of
024: * properties.
025: *
026: * <h3>Examples</h3>
027: *
028: * <p>The following example is the definition of a single back-end at
029: * <code>http://somehost/</code>, identified by the property name
030: * <code>"s1"</code>, the time-out is set to 20 seconds:
031: *
032: * <blockquote><code>s1=service, http://somehost/, 20000</code></blockquote>
033: *
034: * <p>The next example is the definition of 4 back-ends, of which one will be
035: * chosen randomly. This setting is identified by the property name
036: * <code>"capi.sso"</code>:
037: *
038: * <blockquote><code># The root definition "capi.sso"
039: * <br>capi.sso=group, random, target1, target2, target3, target4
040: * <br>
041: * <br># Total time-out is 12.5 seconds, no connection time-out and no socket
042: * <br># time-out
043: * <br>capi.sso.target1=service, http://somehost/, 12500
044: * <br>
045: * <br># Total time-out is 12.5 seconds, connection time-out is 4 seconds and
046: * <br># no socket time-out
047: * <br>capi.sso.target2=service, http://othrhost/, 12500, 4000
048: * <br>
049: * <br># Total time-out is 12.5 seconds, connection time-out is 4 seconds,
050: * <br># socket time-out is 2 seconds
051: * <br>capi.sso.target3=service, http://othrhost:2001/, 12500, 4000, 2000
052: * <br>
053: * <br># Total time-out is not set, connection time-out is not set and socket
054: * <br># time-out is 2 seconds
055: * <br>capi.sso.target4=service, http://othrhost:2002/, 0, 0,
056: * 2000</code></blockquote>
057: *
058: * <p>The last example defines 2 back-ends at a more preferred location and 1
059: * at a less-preferred location. Normally one of the 2 back-ends at the
060: * preferred location will be chosen randomly, but if none is available, then
061: * the back-end at the less preferred location will be tried. The time-out for
062: * all back-ends in 8 seconds. The name of the property is <code>"ldap"</code>:
063: *
064: * <blockquote><code>ldap=group, ordered, loc1, host2a
065: * <br>ldap.loc1=group, random, host1a, host1b
066: * <br>ldap.host1a=service, ldap://host1a/, 8000
067: * <br>ldap.host1b=service, ldap://host1b/, 8000
068: * <br>ldap.host2a=service, ldap://host2a/, 8000</code></blockquote>
069: *
070: * @version $Revision: 1.28 $ $Date: 2007/03/16 09:54:59 $
071: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
072: *
073: * @since XINS 1.0.0
074: */
075: public final class DescriptorBuilder {
076:
077: /**
078: * Delimiter between tokens within a property value. This is the comma
079: * character <code>','</code>.
080: */
081: public static final char DELIMITER = ',';
082:
083: /**
084: * Delimiter between property lines. This is the carraige return
085: * character <code>'\n'</code>.
086: */
087: public static final char LINE_DELIMITER = '\n';
088:
089: /**
090: * Delimiters between tokens within a property value.
091: */
092: private static final String DELIMITER_AS_STRING = String
093: .valueOf(DELIMITER);
094:
095: /**
096: * Name identifying an actual target descriptor.
097: */
098: public static final String TARGET_DESCRIPTOR_TYPE = "service";
099:
100: /**
101: * Name identifying a group of descriptors.
102: */
103: public static final String GROUP_DESCRIPTOR_TYPE = "group";
104:
105: /**
106: * Constructs a new <code>DescriptorBuilder</code>.
107: */
108: private DescriptorBuilder() {
109: // empty
110:
111: // NOTE: No tracing is performed, since this constructor is never used
112: }
113:
114: /**
115: * Tokenizes the specified string. The {@link #DELIMITER_AS_STRING} will be
116: * used as the token delimiter. Every token will be one element in the
117: * returned {@link String} array.
118: *
119: * @param s
120: * the {@link String} to tokenize, cannot be <code>null</code>.
121: *
122: * @return
123: * the list of tokens as a {@link String} array, never
124: * <code>null</code>.
125: */
126: private static String[] tokenize(String s) {
127:
128: // Create a StringTokenizer
129: StringTokenizer tokenizer = new StringTokenizer(s,
130: DELIMITER_AS_STRING);
131:
132: // Create a new array to store the tokens in
133: int count = tokenizer.countTokens();
134: String[] tokens = new String[count];
135:
136: // Copy all tokens into the array
137: for (int i = 0; i < count; i++) {
138: tokens[i] = tokenizer.nextToken().trim();
139: }
140:
141: return tokens;
142: }
143:
144: /**
145: * Builds a <code>Descriptor</code> based on the specified set of
146: * properties, for the specified service caller.
147: *
148: * @param caller
149: * the caller to create a {@link Descriptor} for, can be
150: * <code>null</code> if unknown.
151: *
152: * @param properties
153: * the properties to read from, cannot be <code>null</code>.
154: *
155: * @param propertyName
156: * the base for the property names, cannot be <code>null</code>.
157: *
158: * @return
159: * the {@link Descriptor} that was built, never <code>null</code>.
160: *
161: * @throws IllegalArgumentException
162: * if <code>properties == null || propertyName == null</code>.
163: *
164: * @throws MissingRequiredPropertyException
165: * if the property named <code>propertyName</code> cannot be found in
166: * <code>properties</code>, or if a referenced property cannot be found.
167: *
168: * @throws InvalidPropertyValueException
169: * if the property named <code>propertyName</code> is found in
170: * <code>properties</code>, but the format of this property or the
171: * format of a referenced property is invalid.
172: *
173: * @since XINS 1.2.0
174: */
175: public static Descriptor build(ServiceCaller caller,
176: PropertyReader properties, String propertyName)
177: throws IllegalArgumentException,
178: MissingRequiredPropertyException,
179: InvalidPropertyValueException {
180:
181: // Check preconditions
182: MandatoryArgumentChecker.check("properties", properties,
183: "propertyName", propertyName);
184: return build(caller, properties, propertyName, null);
185: }
186:
187: /**
188: * Builds a <code>Descriptor</code> based on the specified set of
189: * properties.
190: *
191: * @param properties
192: * the properties to read from, cannot be <code>null</code>.
193: *
194: * @param propertyName
195: * the base for the property names, cannot be <code>null</code>.
196: *
197: * @return
198: * the {@link Descriptor} that was built, never <code>null</code>.
199: *
200: * @throws IllegalArgumentException
201: * if <code>properties == null || propertyName == null</code>.
202: *
203: * @throws MissingRequiredPropertyException
204: * if the property named <code>propertyName</code> cannot be found in
205: * <code>properties</code>, or if a referenced property cannot be found.
206: *
207: * @throws InvalidPropertyValueException
208: * if the property named <code>propertyName</code> is found in
209: * <code>properties</code>, but the format of this property or the
210: * format of a referenced property is invalid.
211: */
212: public static Descriptor build(PropertyReader properties,
213: String propertyName) throws IllegalArgumentException,
214: MissingRequiredPropertyException,
215: InvalidPropertyValueException {
216:
217: // Check preconditions
218: MandatoryArgumentChecker.check("properties", properties,
219: "propertyName", propertyName);
220: return build((ServiceCaller) null, properties, propertyName);
221: }
222:
223: /**
224: * Builds a <code>Descriptor</code> based on the specified value.
225: *
226: * @param descriptorValue
227: * the value of the descriptor, cannot be <code>null</code>.
228: * the value must have the same value as specified at the top, the lines
229: * should be separated with '\n' and the first line must start with
230: * the name of the property followed by the sign '='.
231: *
232: * @return
233: * the {@link Descriptor} that was built, never <code>null</code>.
234: *
235: * @throws IllegalArgumentException
236: * if <code>descriptorValue == null</code> or the property name cannot
237: * be found in the value.
238: *
239: * @throws MissingRequiredPropertyException
240: * if the property named <code>propertyName</code> cannot be found in
241: * <code>properties</code>, or if a referenced property cannot be found.
242: *
243: * @throws InvalidPropertyValueException
244: * if the property named <code>propertyName</code> is found in
245: * <code>properties</code>, but the format of this property or the
246: * format of a referenced property is invalid.
247: */
248: public static Descriptor build(String descriptorValue)
249: throws IllegalArgumentException,
250: MissingRequiredPropertyException,
251: InvalidPropertyValueException {
252:
253: // Check preconditions
254: MandatoryArgumentChecker.check("descriptorValue",
255: descriptorValue);
256:
257: int equalsPos = descriptorValue.indexOf('=');
258: int crPos = descriptorValue.indexOf(LINE_DELIMITER);
259: if (equalsPos <= 0 || (crPos > 0 && equalsPos > crPos)) {
260: throw new IllegalArgumentException(
261: "No property name found in \"" + descriptorValue
262: + "\".");
263: }
264: String propertyName = descriptorValue.substring(0, equalsPos);
265:
266: final String ENCODING = "ISO-8859-1";
267: try {
268: byte[] bytes = descriptorValue.getBytes(ENCODING);
269: ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
270: PropertyReader properties = PropertyReaderUtils
271: .createPropertyReader(bais);
272: bais.close();
273: return build((ServiceCaller) null, properties, propertyName);
274: } catch (IOException ioe) {
275: throw new InvalidPropertyValueException(propertyName,
276: descriptorValue);
277: }
278: }
279:
280: /**
281: * Builds a <code>Descriptor</code> based on the specified set of
282: * properties, specifying base property and reference.
283: *
284: * @param caller
285: * the service caller to build a descriptor for, or <code>null</code> if
286: * unknown.
287: *
288: * @param properties
289: * the properties to read from, should not be <code>null</code>.
290: *
291: * @param baseProperty
292: * the name of the base property, should not be <code>null</code>.
293: *
294: * @param reference
295: * the name of the reference, relative to the base property, can be
296: * <code>null</code>.
297: *
298: * @return
299: * the {@link Descriptor} that was built, never <code>null</code>.
300: *
301: * @throws NullPointerException
302: * if <code>properties == null</code>.
303: *
304: * @throws MissingRequiredPropertyException
305: * if a required property cannot be found.
306: *
307: * @throws InvalidPropertyValueException
308: * if the property named <code>propertyName</code> is found in
309: * <code>properties</code>, but the format of this property or the
310: * format of a referenced property is invalid.
311: */
312: private static Descriptor build(ServiceCaller caller,
313: PropertyReader properties, String baseProperty,
314: String reference) throws NullPointerException,
315: MissingRequiredPropertyException,
316: InvalidPropertyValueException {
317:
318: // Determine the property name
319: String propertyName = reference == null ? baseProperty
320: : baseProperty + '.' + reference;
321:
322: // Get the value of the property
323: String value = properties.get(propertyName);
324: if (value == null) {
325: throw new MissingRequiredPropertyException(propertyName);
326: }
327:
328: // Tokenize the value
329: String[] tokens = tokenize(value);
330: int tokenCount = tokens.length;
331: if (tokenCount < 3) {
332: throw new InvalidPropertyValueException(propertyName,
333: value, "Expected at least 3 tokens.");
334: }
335:
336: // Determine the type
337: String descriptorType = tokens[0];
338:
339: // Parse target descriptor
340: if (TARGET_DESCRIPTOR_TYPE.equals(descriptorType)) {
341: if (tokenCount > 5) {
342: throw new InvalidPropertyValueException(propertyName,
343: value, "Expected URL and time-out.");
344: }
345:
346: // Determine URL
347: String url = tokens[1];
348:
349: // Determine the total time-out (mandatory)
350: int timeOut;
351: try {
352: timeOut = Integer.parseInt(tokens[2]);
353: } catch (NumberFormatException nfe) {
354: throw new InvalidPropertyValueException(propertyName,
355: value,
356: "Unable to parse total time-out as a 32-bit integer number.");
357: }
358: if (timeOut < 0) {
359: throw new InvalidPropertyValueException(propertyName,
360: value, "Total time-out is negative.");
361: }
362:
363: // Determine the connection time-out (optional)
364: int connectionTimeOut;
365: if (tokenCount > 3) {
366: try {
367: connectionTimeOut = Integer.parseInt(tokens[3]);
368: } catch (NumberFormatException nfe) {
369: throw new InvalidPropertyValueException(
370: propertyName, value,
371: "Unable to parse connection time-out as a 32-bit integer number.");
372: }
373: if (connectionTimeOut < 0) {
374: throw new InvalidPropertyValueException(
375: propertyName, value,
376: "Connection time-out is negative.");
377: }
378: } else {
379: connectionTimeOut = 0;
380: }
381:
382: // Determine the socket time-out (optional)
383: int socketTimeOut;
384: if (tokenCount > 4) {
385: try {
386: socketTimeOut = Integer.parseInt(tokens[4]);
387: } catch (NumberFormatException nfe) {
388: throw new InvalidPropertyValueException(
389: propertyName, value,
390: "Unable to parse socket time-out as a 32-bit integer number.");
391: }
392: if (socketTimeOut < 0) {
393: throw new InvalidPropertyValueException(
394: propertyName, value,
395: "Socket time-out is negative.");
396: }
397: } else {
398: socketTimeOut = 0;
399: }
400:
401: // Construct a TargetDescriptor instance
402: TargetDescriptor td;
403: try {
404: td = new TargetDescriptor(url, timeOut,
405: connectionTimeOut, socketTimeOut);
406: } catch (MalformedURLException exception) {
407: Log.log_1300(exception, url);
408: throw new InvalidPropertyValueException(propertyName,
409: value, "Malformed URL.");
410: }
411:
412: // Test the protocol
413: if (caller != null) {
414: try {
415: caller.testTargetDescriptor(td);
416: } catch (UnsupportedProtocolException cause) {
417: Log.log_1308(url);
418: InvalidPropertyValueException exception = new InvalidPropertyValueException(
419: propertyName, value,
420: "Unsupported protocol.");
421: ExceptionUtils.setCause(exception, cause);
422: throw exception;
423: }
424: }
425:
426: return td;
427:
428: // Parse group descriptor
429: } else if (GROUP_DESCRIPTOR_TYPE.equals(descriptorType)) {
430:
431: GroupDescriptor.Type groupType = GroupDescriptor
432: .getType(tokens[1]);
433: if (groupType == null) {
434: throw new InvalidPropertyValueException(propertyName,
435: value, "Unrecognized group descriptor type \""
436: + tokens[1] + "\".");
437: }
438:
439: int memberCount = tokenCount - 2;
440: if (memberCount < 2) {
441: throw new InvalidPropertyValueException(propertyName,
442: value, "Group descriptor member count is "
443: + memberCount + ", while minimum is 2.");
444: }
445: Descriptor[] members = new Descriptor[memberCount];
446: for (int i = 0; i < memberCount; i++) {
447: members[i] = build(caller, properties, baseProperty,
448: tokens[i + 2]);
449: }
450: return new GroupDescriptor(groupType, members);
451:
452: // Unrecognized descriptor type
453: } else {
454: throw new InvalidPropertyValueException(propertyName,
455: value, "Expected valid descriptor type: either \""
456: + TARGET_DESCRIPTOR_TYPE + "\" or \""
457: + GROUP_DESCRIPTOR_TYPE + "\".");
458: }
459: }
460: }
|