001: /*
002: * $Id: XINSCallRequest.java,v 1.68 2007/09/11 10:14:20 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.client;
008:
009: import java.util.Iterator;
010: import org.apache.log4j.NDC;
011: import org.apache.oro.text.regex.Pattern;
012: import org.apache.oro.text.regex.Perl5Matcher;
013: import org.xins.common.MandatoryArgumentChecker;
014: import org.xins.common.text.PatternUtils;
015: import org.xins.common.xml.Element;
016: import org.xins.common.xml.ElementSerializer;
017: import org.xins.common.collections.PropertyReader;
018: import org.xins.common.collections.PropertyReaderUtils;
019: import org.xins.common.collections.ProtectedPropertyReader;
020: import org.xins.common.http.HTTPCallConfig;
021: import org.xins.common.http.HTTPCallRequest;
022: import org.xins.common.http.HTTPMethod;
023: import org.xins.common.service.CallRequest;
024:
025: /**
026: * Abstraction of a XINS request.
027: *
028: * <p>Note that instances of this class are <em>not</em> thread-safe.
029: *
030: * @version $Revision: 1.68 $ $Date: 2007/09/11 10:14:20 $
031: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
032: *
033: * @since XINS 1.0.0
034: *
035: * @see XINSServiceCaller
036: */
037: public final class XINSCallRequest extends CallRequest {
038:
039: /**
040: * HTTP status code verifier that will only approve 2xx codes.
041: */
042: private static final HTTPStatusCodeVerifier HTTP_STATUS_CODE_VERIFIER = new HTTPStatusCodeVerifier();
043:
044: /**
045: * The pattern for a parameter name, as a character string.
046: */
047: public static final String PARAMETER_NAME_PATTERN_STRING = "[a-zA-Z][a-zA-Z0-9_\\-\\.]*";
048:
049: /**
050: * The pattern for a parameter name.
051: */
052: private static final Pattern PARAMETER_NAME_PATTERN = PatternUtils
053: .createPattern(PARAMETER_NAME_PATTERN_STRING);
054:
055: /**
056: * The name of the HTTP parameter that specifies the diagnostic context
057: * identifier.
058: */
059: private static final String CONTEXT_ID_HTTP_PARAMETER_NAME = "_context";
060:
061: /**
062: * Secret key used to set the HTTP parameters.
063: */
064: private static final Object SECRET_KEY = new Object();
065:
066: /**
067: * Description of this XINS call request. This field cannot be
068: * <code>null</code>, it is initialized during construction.
069: */
070: private String _asString;
071:
072: /**
073: * The name of the function to call. This field cannot be
074: * <code>null</code>.
075: */
076: private final String _functionName;
077:
078: /**
079: * The parameters to pass in the request, and their respective values. This
080: * field can be <code>null</code>.
081: */
082: private final ProtectedPropertyReader _parameters;
083:
084: /**
085: * The data section to pass in the request. This field can be
086: * <code>null</code>.
087: */
088: private Element _dataSection;
089:
090: /**
091: * The parameters to send with the HTTP request. Cannot be
092: * <code>null</code>.
093: */
094: private final ProtectedPropertyReader _httpParams;
095:
096: /**
097: * Pattern matcher.
098: */
099: private final Perl5Matcher _patternMatcher = new Perl5Matcher();
100:
101: /**
102: * Constructs a new <code>XINSCallRequest</code> for the specified function
103: * with no parameters, disallowing fail-over unless the request was
104: * definitely not (yet) accepted by the service.
105: *
106: * @param functionName
107: * the name of the function to call, cannot be <code>null</code>.
108: *
109: * @throws IllegalArgumentException
110: * if <code>functionName == null</code>.
111: */
112: public XINSCallRequest(String functionName)
113: throws IllegalArgumentException {
114: this (functionName, null, null);
115: }
116:
117: /**
118: * Constructs a new <code>XINSCallRequest</code> for the specified function
119: * and parameters, disallowing fail-over unless the request was definitely
120: * not (yet) accepted by the service.
121: *
122: * @param functionName
123: * the name of the function to call, cannot be <code>null</code>.
124: *
125: * @param parameters
126: * the input parameters, if any, can be <code>null</code> if there are
127: * none.
128: *
129: * @throws IllegalArgumentException
130: * if <code>functionName == null</code>.
131: */
132: public XINSCallRequest(String functionName,
133: PropertyReader parameters) throws IllegalArgumentException {
134: this (functionName, parameters, null);
135: }
136:
137: /**
138: * Constructs a new <code>XINSCallRequest</code> for the specified function
139: * and parameters, disallowing fail-over unless the request was definitely
140: * not (yet) accepted by the service.
141: *
142: * @param functionName
143: * the name of the function to call, cannot be <code>null</code>.
144: *
145: * @param parameters
146: * the input parameters, if any, can be <code>null</code> if there are
147: * none.
148: *
149: * @param dataSection
150: * the data section for the input, if any, can be <code>null</code> if
151: * there are none.
152: *
153: * @throws IllegalArgumentException
154: * if <code>functionName == null</code>.
155: *
156: * @since XINS 1.1.0
157: */
158: public XINSCallRequest(String functionName,
159: PropertyReader parameters, Element dataSection)
160: throws IllegalArgumentException {
161:
162: // Check preconditions
163: MandatoryArgumentChecker.check("functionName", functionName);
164:
165: // Store function name, parameters and data section
166: _functionName = functionName;
167: _parameters = new ProtectedPropertyReader(SECRET_KEY);
168: _httpParams = new ProtectedPropertyReader(SECRET_KEY);
169: setParameters(parameters);
170: setDataSection(dataSection);
171:
172: // Note that _asString is lazily initialized.
173: }
174:
175: /**
176: * Constructs a new <code>XINSCallRequest</code> for the specified function
177: * and parameters, possibly allowing fail-over even if the request was
178: * possibly already received by a target service.
179: *
180: * @param functionName
181: * the name of the function to call, cannot be <code>null</code>.
182: *
183: * @param parameters
184: * the input parameters, if any, can be <code>null</code> if there are
185: * none.
186: *
187: * @param failOverAllowed
188: * flag that indicates whether fail-over is in principle allowed, even
189: * if the request was already sent to the other end.
190: *
191: * @throws IllegalArgumentException
192: * if <code>functionName == null</code>.
193: */
194: public XINSCallRequest(String functionName,
195: PropertyReader parameters, boolean failOverAllowed)
196: throws IllegalArgumentException {
197: this (functionName, parameters, failOverAllowed, null);
198: }
199:
200: /**
201: * Constructs a new <code>XINSCallRequest</code> for the specified function
202: * and parameters, possibly allowing fail-over, optionally specifying the
203: * HTTP method to use.
204: *
205: * @param functionName
206: * the name of the function to call, cannot be <code>null</code>.
207: *
208: * @param parameters
209: * the input parameters, if any, can be <code>null</code> if there are
210: * none.
211: *
212: * @param failOverAllowed
213: * flag that indicates whether fail-over is in principle allowed, even
214: * if the request was already sent to the other end.
215: *
216: * @param method
217: * the HTTP method to use, or <code>null</code> if a default should be
218: * used.
219: *
220: * @throws IllegalArgumentException
221: * if <code>functionName == null</code> or if <code>parameters</code>
222: * contains a name that does not match the constraints for a parameter
223: * name, see {@link #PARAMETER_NAME_PATTERN_STRING} or if it equals
224: * <code>"function"</code>, which is currently still reserved.
225: */
226: public XINSCallRequest(String functionName,
227: PropertyReader parameters, boolean failOverAllowed,
228: HTTPMethod method) throws IllegalArgumentException {
229:
230: this (functionName, parameters);
231:
232: // Create an associated XINSCallConfig object
233: XINSCallConfig callConfig = new XINSCallConfig();
234:
235: // Configure fail-over
236: callConfig.setFailOverAllowed(failOverAllowed);
237:
238: // Configure the HTTP method
239: if (method != null) {
240: callConfig.setHTTPMethod(method);
241: }
242:
243: // Apply the configuration
244: setXINSCallConfig(callConfig);
245: }
246:
247: /**
248: * Describes this request.
249: *
250: * @return
251: * the description of this request, never <code>null</code>.
252: */
253: public String describe() {
254:
255: // Lazily initialize the description of this call request object
256: if (_asString == null) {
257: StringBuffer description = new StringBuffer(193);
258: description.append("XINS ");
259: if (getXINSCallConfig() != null) {
260: description.append(getXINSCallConfig().getHTTPMethod()
261: .toString());
262: } else {
263: description.append("(no config)");
264: }
265: description.append(" HTTP request ");
266:
267: // Function name
268: description.append("_function=");
269: description.append(_functionName);
270:
271: // Parameters
272: if (_parameters != null && _parameters.size() > 0) {
273: description.append(PropertyReaderUtils.toString(
274: _parameters, "-", "&", ""));
275: }
276:
277: // Diagnostic context identifier
278: String contextID = _httpParams
279: .get(CONTEXT_ID_HTTP_PARAMETER_NAME);
280: if (contextID != null && contextID.length() > 0) {
281: description.append("&_context=");
282: description.append(contextID);
283: }
284: _asString = description.toString();
285: }
286:
287: return _asString;
288: }
289:
290: /**
291: * Returns the XINS call configuration.
292: *
293: * @return
294: * the XINS call configuration object, or <code>null</code>.
295: *
296: * @since XINS 1.1.0
297: */
298: public XINSCallConfig getXINSCallConfig() {
299: return (XINSCallConfig) getCallConfig();
300: }
301:
302: /**
303: * Sets the associated XINS call configuration.
304: *
305: * @param callConfig
306: * the XINS call configuration object to associate with this request, or
307: * <code>null</code>.
308: *
309: * @since XINS 1.1.0
310: */
311: public void setXINSCallConfig(XINSCallConfig callConfig) {
312: setCallConfig(callConfig);
313: }
314:
315: /**
316: * Returns the name of the function to call.
317: *
318: * @return
319: * the name of the function to call, never <code>null</code>.
320: */
321: public String getFunctionName() {
322: return _functionName;
323: }
324:
325: /**
326: * Initializes the set of parameters. The implementation of this method
327: * first removes all parameters and then adds the standard parameters.
328: */
329: private void initParameters() {
330:
331: // Remove all existing parameters
332: _parameters.clear(SECRET_KEY);
333: _httpParams.clear(SECRET_KEY);
334:
335: // Since XINS 1.0.1: Use XINS 1.0 standard calling convention
336: _httpParams.set(SECRET_KEY, "_convention", "_xins-std");
337:
338: // Add the diagnostic context ID to the parameter list, if there is one
339: String contextID = NDC.peek();
340: if (contextID != null && contextID.length() > 0) {
341: _httpParams.set(SECRET_KEY, CONTEXT_ID_HTTP_PARAMETER_NAME,
342: contextID);
343: }
344:
345: // Add the function to the parameter list
346: _httpParams.set(SECRET_KEY, "_function", _functionName);
347:
348: // XXX: For backwards compatibility, also add the parameter "function"
349: // to the list of HTTP parameters. This is, however, very likely to
350: // change in the future.
351: _httpParams.set(SECRET_KEY, "function", _functionName);
352:
353: // Reset _asString so it will be re-initialized as necessary
354: _asString = null;
355: }
356:
357: /**
358: * Sets the parameters for this function, replacing any existing
359: * parameters. First the existing parameters are cleaned and then all
360: * the specified parameters are copied to the internal set one-by-one. If
361: * any of the parameters has an invalid name, then the internal parameter
362: * set is cleaned and then an exception is thrown.
363: *
364: * @param parameters
365: * the input parameters, if any, can be <code>null</code> if there are
366: * none.
367: *
368: * @throws IllegalArgumentException
369: * if <code>parameters</code> contains a name that does not match the
370: * constraints for a parameter name, see
371: * {@link #PARAMETER_NAME_PATTERN_STRING} or if it equals
372: * <code>"function"</code>, which is currently still reserved.
373: *
374: * @since XINS 1.1.0
375: */
376: public void setParameters(PropertyReader parameters)
377: throws IllegalArgumentException {
378:
379: // Clear the parameters
380: initParameters();
381:
382: // Check and copy all parameters
383: if (parameters != null) {
384: Iterator names = parameters.getNames();
385: while (names.hasNext()) {
386:
387: // Get the name and value
388: String name = (String) names.next();
389: String value = parameters.get(name);
390:
391: // Set the combination (this may fail)
392: setParameter(name, value);
393: }
394: }
395:
396: // Add the function to the parameter list
397: _httpParams.set(SECRET_KEY, "_function", _functionName);
398:
399: // XXX: For backwards compatibility, also add the parameter "function"
400: // to the list of HTTP parameters. This is, however, very likely to
401: // change in the future.
402: _httpParams.set(SECRET_KEY, "function", _functionName);
403:
404: // Reset _asString so it will be re-initialized as necessary
405: _asString = null;
406: }
407:
408: /**
409: * Sets the parameter with the specified name.
410: *
411: * @param name
412: * the parameter name, cannot be <code>null</code>.
413: *
414: * @param value
415: * the new value for the parameter, can be <code>null</code>.
416: *
417: * @throws IllegalArgumentException
418: * if <code>name</code> does not match the constraints for a parameter
419: * name, see {@link #PARAMETER_NAME_PATTERN_STRING} or if it equals
420: * <code>"function"</code>, which is currently still reserved.
421: *
422: * @since XINS 1.2.0
423: */
424: public void setParameter(String name, String value)
425: throws IllegalArgumentException {
426:
427: // Check preconditions
428: MandatoryArgumentChecker.check("name", name);
429:
430: // Name cannot violate the pattern
431: if (!_patternMatcher.matches(name, PARAMETER_NAME_PATTERN)) {
432: // XXX: Consider using a different kind of exception for this
433: // specific case. For backwards compatibility, this exception
434: // class must be converted to an IllegalArgumentException in
435: // some cases or otherwise it should subclass
436: // IllegalArgumentException.
437:
438: String message = "The parameter name \"" + name
439: + "\" does not match the pattern \""
440: + PARAMETER_NAME_PATTERN_STRING + "\".";
441: throw new IllegalArgumentException(message);
442:
443: // Name cannot be "function"
444: } else if ("function".equals(name)) {
445: throw new IllegalArgumentException(
446: "Parameter name \"function\" is reserved.");
447:
448: // Name is considered valid, store it
449: } else {
450: _parameters.set(SECRET_KEY, name, value);
451: _httpParams.set(SECRET_KEY, name, value);
452: }
453: }
454:
455: /**
456: * Gets all parameters to pass with the call, with their respective values.
457: *
458: * @return
459: * the parameters, or <code>null</code> if there are none.
460: */
461: public PropertyReader getParameters() {
462: return _parameters;
463: }
464:
465: /**
466: * Gets the value of the specified parameter.
467: *
468: * @param name
469: * the parameter name, not <code>null</code>.
470: *
471: * @return
472: * string containing the value of the parameter, not <code>null</code>.
473: *
474: * @throws IllegalArgumentException
475: * if <code>name == null</code>.
476: */
477: public String getParameter(String name)
478: throws IllegalArgumentException {
479:
480: // Check preconditions
481: MandatoryArgumentChecker.check("name", name);
482:
483: return (_parameters == null) ? null : _parameters.get(name);
484: }
485:
486: /**
487: * Sets the data section for the input.
488: *
489: * @param dataSection
490: * the data section for the input, or <code>null</code> if there is
491: * none.
492: *
493: * @since XINS 1.1.0
494: */
495: public void setDataSection(Element dataSection) {
496:
497: // Store the data section
498: _dataSection = dataSection;
499:
500: // Add the data section to the HTTP parameter list
501: if (dataSection != null) {
502: ElementSerializer serializer = new ElementSerializer();
503: String xmlDataSection = serializer.serialize(dataSection);
504: _httpParams.set(SECRET_KEY, "_data", xmlDataSection);
505: }
506: }
507:
508: /**
509: * Retrieves the data section for the input.
510: *
511: * @return
512: * the data section for the input, or <code>null</code> if there is
513: * none.
514: *
515: * @since XINS 1.1.0
516: */
517: public Element getDataSection() {
518: return _dataSection;
519: }
520:
521: /**
522: * Returns an <code>HTTPCallRequest</code> that can be used to execute this
523: * XINS request.
524: *
525: * @return
526: * this request converted to an {@link HTTPCallRequest}, never
527: * <code>null</code>.
528: */
529: HTTPCallRequest getHTTPCallRequest() {
530:
531: // Construct an HTTP call request
532: HTTPCallRequest httpRequest = new HTTPCallRequest(_httpParams,
533: HTTP_STATUS_CODE_VERIFIER);
534:
535: // If there is a XINS call config, create an HTTP call config
536: XINSCallConfig xinsConfig = getXINSCallConfig();
537: if (xinsConfig != null) {
538: HTTPCallConfig httpConfig = new HTTPCallConfig();
539: httpConfig.setFailOverAllowed(xinsConfig
540: .isFailOverAllowed());
541: httpConfig.setMethod(xinsConfig.getHTTPMethod());
542: httpRequest.setHTTPCallConfig(httpConfig);
543: }
544:
545: return httpRequest;
546: }
547:
548: /**
549: * HTTP status code verifier that will only approve 2xx codes.
550: *
551: * @version $Revision: 1.68 $ $Date: 2007/09/11 10:14:20 $
552: * @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
553: *
554: * @since XINS 1.0.0
555: */
556: private static final class HTTPStatusCodeVerifier implements
557: org.xins.common.http.HTTPStatusCodeVerifier {
558:
559: /**
560: * Constructs a new <code>HTTPStatusCodeVerifier</code>.
561: */
562: private HTTPStatusCodeVerifier() {
563: // empty
564: }
565:
566: /**
567: * Checks if the specified HTTP status code is considered acceptable or
568: * unacceptable.
569: *
570: * <p>The implementation of this method in class
571: * {@link XINSCallRequest.HTTPStatusCodeVerifier} returns
572: * <code>true</code> only for 2xx status codes.
573: *
574: * @param code
575: * the HTTP status code to check.
576: *
577: * @return
578: * <code>true</code> if <code>code >= 200 && code <=
579: * 299</code>.
580: */
581: public boolean isAcceptable(int code) {
582: return (code >= 200) && (code <= 299);
583: }
584: }
585: }
|