001/**
002 * Copyright 2014 Tampere University of Technology, Pori Department
003 * 
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 *   http://www.apache.org/licenses/LICENSE-2.0
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package core.tut.pori.context;
017
018import java.lang.annotation.Annotation;
019import java.lang.reflect.Constructor;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Method;
022import java.util.Arrays;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028
029import org.apache.commons.lang3.exception.ExceptionUtils;
030import org.apache.commons.lang3.tuple.Pair;
031import org.apache.log4j.Logger;
032import org.springframework.beans.BeansException;
033import org.springframework.context.support.ClassPathXmlApplicationContext;
034
035import core.tut.pori.http.Definitions;
036import core.tut.pori.http.Response;
037import core.tut.pori.http.ServiceRequest;
038import core.tut.pori.http.Response.Status;
039import core.tut.pori.http.annotations.HTTPAuthenticationParameter;
040import core.tut.pori.http.annotations.HTTPHeaderParameter;
041import core.tut.pori.http.annotations.HTTPMethodParameter;
042import core.tut.pori.http.annotations.HTTPService;
043import core.tut.pori.http.annotations.HTTPServiceMethod;
044import core.tut.pori.http.headers.HTTPHeader;
045import core.tut.pori.http.parameters.AuthenticationParameter;
046import core.tut.pori.http.parameters.HTTPParameter;
047import core.tut.pori.users.UserIdentity;
048import core.tut.pori.utils.StringUtils;
049
050/**
051 * This class initializes and handles the Service registry. 
052 * 
053 * Instances of the Service objects can be retrieved using this class, though generally the instances do not have to be called directly by any class or object.
054 * 
055 * The service initialization happens automatically based on the rest servlet configuration and annotated classes, 
056 * the services are automatically invoked when called through the associated web application URI paths.
057 *
058 * One should not initialize this handler directly, as an instantiated version is available from ServiceInitializer.
059 */
060public class ServiceHandler {
061  private static final Logger LOGGER = Logger.getLogger(ServiceHandler.class);
062  private static final String SERVLET_CONFIGURATION_FILE = "rest-servlet.xml";
063  private ClassPathXmlApplicationContext _context = null;
064  private Map<String, Service> _services = null;  // service name-service map
065
066  /**
067   * 
068   * @throws BeansException on failure
069   */
070  public ServiceHandler() throws BeansException{
071    initialize();
072  }
073
074  /**
075   * 
076   * @throws BeansException on failure
077   */
078  private void initialize() throws BeansException{
079    LOGGER.debug("Initializing handler...");
080    Date started = new Date();
081    _context = new ClassPathXmlApplicationContext(core.tut.pori.properties.SystemProperty.CONFIGURATION_FILE_PATH+SERVLET_CONFIGURATION_FILE);
082
083    LOGGER.debug("Class Path XML Context initialized in "+StringUtils.getDurationString(started, new Date()));
084
085    Map<String, Object> services = _context.getBeansWithAnnotation(HTTPService.class);
086    int count = services.size();
087    LOGGER.info("Found "+count+" service(s).");
088    _services = new HashMap<>(count);
089
090    for(Iterator<Object> iter = services.values().iterator();iter.hasNext();){
091      addService(iter.next());
092    }
093
094    LOGGER.debug("Service Handler initialized in "+StringUtils.getDurationString(started, new Date()));
095  }
096
097  /**
098   * close this Service handler and release are resources associated with it
099   */
100  public void close(){
101    _context.close();
102    _context = null;
103    _services = null;
104  }
105
106  /**
107   * remove leading and trailing separator / from the name if any are present
108   * 
109   * @param name
110   * @return the name or null if name was invalid
111   */
112  private String clearSeparators(String name) {
113    if(org.apache.commons.lang3.StringUtils.isBlank(name)){
114      return null;
115    }
116
117    boolean startsWith = name.startsWith(Definitions.SEPARATOR_URI_PATH);
118    boolean endsWith = name.endsWith(Definitions.SEPARATOR_URI_PATH);
119    if(startsWith || endsWith){
120      name = name.substring((startsWith ? 1 : 0), (endsWith ? name.length()-1 : name.length()));  // chop from beginning and end if required
121      if(org.apache.commons.lang3.StringUtils.isBlank(name)){
122        name = null;
123      }
124    }
125    return name;
126  }
127
128  /**
129   * Invoke a service described by the given request.
130   * 
131   * @param serviceRequest
132   * @return a Response object with status notifying about the result of the invocation.
133   */
134  public Response invoke(ServiceRequest serviceRequest){
135    if(!ServiceRequest.isValid(serviceRequest)){
136      return new Response(Status.BAD_REQUEST);
137    }
138    String serviceName = serviceRequest.getServiceName();
139    Service service = _services.get(serviceName);
140    if(service == null){
141      return new Response(Status.NOT_FOUND, "No such service: "+serviceName);
142    }
143
144    String methodName = serviceRequest.getMethodName();
145    String httpMethod = serviceRequest.getHttpMethod();
146    ServiceMethod method = service.getMethod(httpMethod, methodName);
147    if(method == null){
148      return new Response(Status.NOT_FOUND, "No such method: "+httpMethod+" "+methodName);
149    }
150
151    int parameterCount = method.getParameterCount();
152    if(parameterCount < 1){ // no required arguments
153      return invoke(method.getMethod(), null, method.getReturnType(), service.getServiceObject());
154    }
155
156    Object[] args = new Object[parameterCount];
157
158    try { // catch instantation exceptions, which should never really happen
159      AuthParameter authParam = method.getAuthParam();
160      if(!setAuthParam(authParam, args, serviceRequest)){ // check if authentication is required
161        Response response = method.getReturnType().newInstance();
162        if(authParam.isShowLoginPrompt()){
163          response.setStatus(Status.UNAUTHORIZED);
164        }else{
165          response.setStatus(Status.FORBIDDEN);
166        }
167        return response;
168      }
169
170      try{ // check if the given parameters are valid
171        setHeaderParams(args, method.getHeaderParams(), serviceRequest);
172        setMethodParams(args, method.getMethodParams(), serviceRequest);
173      }catch(IllegalArgumentException ex){
174        LOGGER.debug(ex, ex);
175        Response response = method.getReturnType().newInstance();
176        response.setStatus(Status.BAD_REQUEST);
177        response.setMessage(ex.getMessage());
178        return response;
179      }
180    } catch (InstantiationException | IllegalAccessException ex) { // should not happen
181      LOGGER.error(ExceptionUtils.getStackTrace(ex));
182      return new Response(Status.INTERNAL_SERVER_ERROR);
183    }
184
185    return invoke(method.getMethod(), args, method.getReturnType(), service.getServiceObject());
186  }
187
188  /**
189   * 
190   * @param method
191   * @param methodArgs
192   * @param returnType
193   * @param serviceObject
194   * @return response
195   */
196  private Response invoke(Method method, Object[] methodArgs, Class<? extends Response> returnType, Object serviceObject){
197    try{
198      try {
199        Object retval = method.invoke(serviceObject, methodArgs);
200        if(retval == null){
201          return returnType.newInstance();  // return default OK for void
202        }else{
203          return (Response) retval; // this is checked by the initializer to be the only possible return type
204        }
205      } catch (InvocationTargetException ex) {
206        Throwable cause = ex.getCause();
207        LOGGER.error(ex, cause);  // print exception and the actual cause
208        Response response = returnType.newInstance();
209        if(cause instanceof IllegalArgumentException){  // accept as bad request
210          response.setStatus(Status.BAD_REQUEST);
211          response.setMessage(cause.getMessage());
212        }else{  // this should have been caught by the implementation
213          response.setStatus(Status.INTERNAL_SERVER_ERROR);
214        }
215        return response;
216      } 
217    } catch (InstantiationException | IllegalAccessException | IllegalArgumentException ex) { // should not happen
218      LOGGER.error(ExceptionUtils.getStackTrace(ex));
219      return new Response(Status.INTERNAL_SERVER_ERROR);
220    }
221  }
222
223  /**
224   * 
225   * @param methodArgs methodArgs argument list for the method to be called, the parameter will be set to this array to its correct index if needed
226   * @param params can be null
227   * @param serviceRequest
228   * @throws IllegalArgumentException on bad request argument
229   */
230  private void setMethodParams(Object[] methodArgs, Map<String,MethodParameter> params, ServiceRequest serviceRequest) throws IllegalArgumentException{
231    if(params == null){
232      return;
233    }
234
235    Map<String,List<String>> paramMap = serviceRequest.getRawParameters();
236
237    try {
238      for(Map.Entry<String, MethodParameter> e : params.entrySet()){
239        String paramName = e.getKey();
240        MethodParameter param = e.getValue();
241        HTTPParameter p = param.getParameter().newInstance();
242        p.setParameterName(paramName);
243        if(param.isBodyParameter()){
244          p.initialize(serviceRequest.getBody());
245        }else{
246          List<String> values = (paramMap == null ? null : paramMap.get(paramName));
247          if(param.isRequired() && values == null){
248            throw new IllegalArgumentException("Requested parameter "+paramName+" was not found.");
249          }
250
251          if(values == null){
252            List<String> defaultValues = param.getDefaultValues();
253            if(defaultValues != null){  // if default value has been given
254              if(defaultValues.size() == 1){
255                p.initializeRaw(defaultValues.get(0));
256              }else{
257                p.initializeRaw(defaultValues);
258              }
259            } 
260          }else if(values.size() == 1){
261            p.initializeRaw(values.get(0));
262          }else{
263            p.initializeRaw(values);
264          }
265        }
266        methodArgs[param.getParameterIndex()] = p;
267      }
268    } catch (InstantiationException | IllegalAccessException ex) {  // this should not happen
269      LOGGER.error(ex, ex);
270      throw new IllegalArgumentException("Failed to parse parameters.");
271    }
272  }
273
274  /**
275   * 
276   * @param methodArgs argument list for the method to be called, the parameter will be set to this array to its correct index if needed
277   * @param params can be null
278   * @param serviceRequest
279   * @throws IllegalArgumentException on bad request argument
280   */
281  private void setHeaderParams(Object[] methodArgs, Map<String, HeaderParameter> params, ServiceRequest serviceRequest) throws IllegalArgumentException{
282    if(params == null){
283      return;
284    }
285
286    try {
287      for(Map.Entry<String, HeaderParameter> e : params.entrySet()){
288        String headerName = e.getKey();
289        HeaderParameter param = e.getValue();
290        String value = serviceRequest.getHeaderValue(headerName);
291        if(param.isRequired() && value == null){
292          throw new IllegalArgumentException("Requested Header field "+headerName+" was not found.");
293        }
294
295        HTTPHeader header = param.getParameter().newInstance();
296        header.setName(headerName);
297        if(value == null){
298          value = param.getDefaultValue();
299        }
300        header.setValue(value);
301        methodArgs[param.getParameterIndex()] = header;
302      }
303    } catch (InstantiationException | IllegalAccessException ex) {  // should not happen
304      LOGGER.error(ex, ex);
305      throw new IllegalArgumentException("Failed to parse HTTP headers.");
306    }
307  }
308
309  /**
310   * helper method for setting the authentication parameter to method arguments
311   * 
312   * @param authParam can be null
313   * @param methodArgs argument list for the method to be called, the parameter will be set to this array to its correct index if needed
314   * @param serviceRequest
315   * @return true on success (=user was authenticated, and authentication was required)
316   */
317  private boolean setAuthParam(AuthParameter authParam, Object[] methodArgs, ServiceRequest serviceRequest){
318    if(authParam != null){  // authentication is at least requested
319      UserIdentity authenticatedUser = serviceRequest.getAuthenticatedUser();
320      if(authParam.isRequired() && authenticatedUser == null){  // authentication is required, but not provided
321        return false;
322      }else{
323        try {
324          AuthenticationParameter p = authParam.getParameter().newInstance();
325          p.setUserIdentity(authenticatedUser);
326          p.setSession(serviceRequest.getSession());
327          methodArgs[authParam.getParameterIndex()] = p;
328        } catch (InstantiationException | IllegalAccessException ex) {  // this should not happen...
329          LOGGER.error(ex, ex);
330          return false; // ...but if it does, do not allow further user access
331        }
332      }
333    }
334    return true;
335  }
336
337  /**
338   * 
339   * @param object
340   * @throws IllegalArgumentException
341   */
342  private void addService(Object object) throws IllegalArgumentException {
343    Class<?> cls = object.getClass();
344    HTTPService serviceAnnotation = cls.getAnnotation(HTTPService.class);
345
346    Service service = new Service(object);
347    Method[] methods = cls.getMethods();
348    for(int i=0;i<methods.length;++i){
349      HTTPServiceMethod methodAnnotation = methods[i].getAnnotation(HTTPServiceMethod.class);
350      if(methodAnnotation != null){
351        String[] am = methodAnnotation.acceptedMethods();
352        if(am == null || am.length < 1){
353          throw new IllegalArgumentException("The method "+methods[i].toString()+" does not allow any of HTTP Methods.");
354        }
355        for(int j=0;j<am.length;++j){ // separate to different pairs based on HTTP method
356          service.addMethod(methods[i],clearSeparators(methodAnnotation.name()), am[j]);
357        }
358      } // if
359    } // for
360    if(methods.length < 1){
361      LOGGER.warn("Ignored service "+cls.toString()+": no valid methods defined.");
362      return;
363    }
364
365    String name = clearSeparators(serviceAnnotation.name());
366    if(name == null){
367      throw new IllegalArgumentException("Invalid service name for "+cls.toString());
368    }else if(_services.containsKey(name)){
369      throw new IllegalArgumentException("Duplicate service name "+name+" for "+cls.toString());
370    }else{
371      _services.put(name, service);
372    }
373  }
374
375  /**
376   * Defines a single service invokable by the handler.
377   *
378   */
379  private static class Service{
380    private Map<Pair<String, String>, ServiceMethod> _methods = null; // method name/httpMethod-method map, where httpMethod is the HTTP verb, e.g. POST or GET
381    private Object _service = null; // service object represented as spring bean
382
383    /**
384     * 
385     * @param service
386     */
387    public Service(Object service){
388      _service = service;
389      _methods = new HashMap<>();
390    }
391
392    /**
393     * 
394     * @param method
395     * @param methodName
396     * @param httpMethod e.g. POST or GET
397     * @throws IllegalArgumentException on bad methodName
398     */
399    public void addMethod(Method method, String methodName, String httpMethod) throws IllegalArgumentException{
400      if(methodName == null){
401        throw new IllegalArgumentException("Invalid methodName for "+method.toString());
402      }else{
403        Pair<String, String> methodPair = Pair.of(methodName, httpMethod);
404        if(_methods.containsKey(methodPair)){     
405          throw new IllegalArgumentException("Duplicate methodName: "+methodName+" and/or method type "+httpMethod+" for "+method.toString());
406        }else{
407          _methods.put(methodPair, new ServiceMethod(method));
408        }
409      }
410
411    }
412
413    /**
414     * 
415     * @param httpMethod
416     * @param methodName
417     * @return the method or null if none found
418     */
419    public ServiceMethod getMethod(String httpMethod, String methodName){
420      return _methods.get(Pair.of(methodName, httpMethod));
421    }
422
423    /**
424     * 
425     * @return the service object
426     */
427    public Object getServiceObject(){
428      return _service;
429    }
430  } // class Service
431
432  /**
433   * Defines a single method invokable through a Service.
434   *
435   */
436  private static class ServiceMethod{
437    private AuthParameter _authParam = null;
438    private Map<String,HeaderParameter> _headerParams = null; // parameter name-parameter_type map
439    private Method _method = null;
440    private Map<String,MethodParameter> _methodParams = null; // parameter name-parameter_type map
441    private int _parameterCount = 0;
442    private Class<? extends Response> _returnType = null;
443
444    /**
445     * 
446     * @param method
447     * @throws IllegalArgumentException on bad Method
448     */
449    public ServiceMethod(Method method) throws IllegalArgumentException{
450      _method = method;
451      initialize();
452    }
453
454    /**
455     * 
456     * @throws IllegalArgumentException on bad parameter
457     */
458    @SuppressWarnings("unchecked")
459    private void initialize() throws IllegalArgumentException{
460      Class<?> responseClass= _method.getReturnType();
461      if(responseClass.equals(Void.TYPE)){
462        _returnType = Response.class;
463      }else if(!Response.class.isAssignableFrom(responseClass)){  // check for valid return type
464        throw new IllegalArgumentException("Return type not "+Void.TYPE.toString()+" or "+Response.class.toString()+" for "+_method.toString());
465      }else{
466        _returnType = (Class<? extends Response>) responseClass;
467      }
468
469      Annotation[][] annotations = _method.getParameterAnnotations(); // get annotation "map"
470      if(annotations.length < 1){ // no parameters
471        return;
472      }
473      _methodParams = new HashMap<>();
474      _headerParams = new HashMap<>();
475
476      Class<?>[] paramTypes = _method.getParameterTypes();  // get types of the parameters
477      boolean bodyParameterGiven = false;
478      for(int i=0;i<annotations.length;++i){  // go through types
479        if(AuthenticationParameter.class.isAssignableFrom(paramTypes[i])){  // check for authentication parameter
480          if(_authParam != null){
481            throw new IllegalArgumentException("Duplicate "+AuthenticationParameter.class.toString()+" in "+_method.toString());
482          }
483
484          HTTPAuthenticationParameter parameterAnnotation = null;
485          for(Annotation annotation : annotations[i]){  // check that the required annotation is present
486            if(annotation.annotationType() == HTTPAuthenticationParameter.class){
487              parameterAnnotation = (HTTPAuthenticationParameter)annotation;
488              break;
489            }
490          }
491          if(parameterAnnotation == null){  // the annotation is missing
492            throw new IllegalArgumentException("Annotation "+HTTPAuthenticationParameter.class.toString()+" is missing from: "+paramTypes[i].toString()+" in "+_method.toString());
493          }
494
495          _authParam = new AuthParameter((Class<? extends AuthenticationParameter>) paramTypes[i], i, parameterAnnotation.required(), parameterAnnotation.showLoginPrompt()); // not really "unchecked" cast
496        }else if(HTTPParameter.class.isAssignableFrom(paramTypes[i])){  // if this is a http parameter
497          HTTPMethodParameter parameterAnnotation = null;
498          for(Annotation annotation : annotations[i]){  // check that the required annotation is present
499            if(annotation.annotationType() == HTTPMethodParameter.class){
500              parameterAnnotation = (HTTPMethodParameter)annotation;
501              break;
502            }
503          }
504          if(parameterAnnotation == null){  // the annotation is missing
505            throw new IllegalArgumentException("Annotation "+HTTPMethodParameter.class.toString()+" is missing from: "+paramTypes[i].toString()+" in "+_method.toString());
506          }
507
508          boolean isBody = parameterAnnotation.bodyParameter();
509          if(isBody){
510            if(bodyParameterGiven){
511              throw new IllegalArgumentException("Duplicate HTTP body paramater in "+_method.toString());
512            }else{
513              bodyParameterGiven = true;
514            }
515          }
516
517          String name = parameterAnnotation.name();
518          if(org.apache.commons.lang3.StringUtils.isBlank(name)){
519            throw new IllegalArgumentException("Invalid parameter name "+name+" in "+_method.toString());
520          }else if(_methodParams.containsKey(name)){
521            throw new IllegalArgumentException("Duplicate parameter name "+name+" in "+_method.toString());
522          }
523
524          _methodParams.put(name, new MethodParameter(parameterAnnotation.defaultValue(), isBody, (Class<? extends HTTPParameter>) paramTypes[i], i,parameterAnnotation.required())); // not really "unchecked" cast
525        }else if(HTTPHeader.class.isAssignableFrom(paramTypes[i])){ // this is a header
526          HTTPHeaderParameter parameterAnnotation = null;
527          for(Annotation annotation : annotations[i]){  // check that the required annotation is present
528            if(annotation.annotationType() == HTTPHeaderParameter.class){
529              parameterAnnotation = (HTTPHeaderParameter)annotation;
530              break;
531            }
532          }
533          if(parameterAnnotation == null){  // the annotation is missing
534            throw new IllegalArgumentException("Annotation "+HTTPHeaderParameter.class.toString()+" is missing from: "+paramTypes[i].toString()+" in "+_method.toString());
535          }
536
537          String name = parameterAnnotation.name();
538          if(org.apache.commons.lang3.StringUtils.isBlank(name)){
539            throw new IllegalArgumentException("Invalid header name "+name+" in "+_method.toString());
540          }else if(_headerParams.containsKey(name)){
541            throw new IllegalArgumentException("Duplicate header name "+name+" in "+_method.toString());
542          }
543
544          _headerParams.put(name, new HeaderParameter(parameterAnnotation.defaultValue(), (Class<? extends HTTPHeader>) paramTypes[i], i, parameterAnnotation.required())); // not really "unchecked" cast
545        }else{  // unknown type
546          throw new IllegalArgumentException(paramTypes[i].toString()+" is not subclass of "+HTTPParameter.class.toString()+" in "+_method.toString());
547        }
548
549        Constructor<?>[] constructors = paramTypes[i].getConstructors();
550        for(int j=0;j<constructors.length;++j){ // check that no-args constructor is present for the argument
551          if(constructors[j].getParameterTypes().length < 1){
552            constructors = null;
553            break;
554          }
555        }
556        if(constructors != null){
557          throw new IllegalArgumentException("No no-args constructor available for the type: "+paramTypes[i].toString()+" in "+_method.toString());
558        }
559      } // for types
560      if(_headerParams.isEmpty()){
561        _headerParams = null; // empty list not needed
562      }
563      if(_methodParams.isEmpty()){
564        _methodParams = null; // empty list not needed
565      }
566      _parameterCount = annotations.length; // get the argument count
567    }
568
569    /**
570     * @return the method
571     */
572    public Method getMethod() {
573      return _method;
574    }
575
576    /**
577     * @return the methodParams
578     */
579    public Map<String, MethodParameter> getMethodParams() {
580      return _methodParams;
581    }
582
583    /**
584     * @return the headerParams
585     */
586    public Map<String, HeaderParameter> getHeaderParams() {
587      return _headerParams;
588    }
589
590    /**
591     * @return the authParam
592     */
593    public AuthParameter getAuthParam() {
594      return _authParam;
595    }
596
597    /**
598     * @return the parameter count
599     */
600    public int getParameterCount() {
601      return _parameterCount;
602    }
603
604    /**
605     * @return the returnType
606     */
607    public Class<? extends Response> getReturnType() {
608      return _returnType;
609    }
610  } // class ServiceMethod
611
612  /**
613   * Method parameter base class 
614   *
615   */
616  private static abstract class Parameter{
617    private int _parameterIndex = -1;
618
619    /**
620     * 
621     * @param parameterIndex the index of this parameter in the method declaration
622     */
623    public Parameter(int parameterIndex){
624      _parameterIndex = parameterIndex;
625    }
626
627    /**
628     * @return the parameterIndex
629     */
630    public int getParameterIndex() {
631      return _parameterIndex;
632    }
633  } // class Parameter
634
635  /**
636   * Defines a single method parameter assigned to service method.
637   * 
638   */
639  private static class MethodParameter extends Parameter{
640    private List<String> _defaultValues = null;
641    private boolean _isBodyParameter = false;
642    private Class<? extends HTTPParameter> _parameter = null; // for instantiating new classes for parsing process, if null, null will be returned as value
643    private boolean _required = false;
644
645    /**
646     * 
647     * @param defaultValue
648     * @param isBodyParameter
649     * @param parameterClass
650     * @param parameterIndex
651     * @param required
652     */
653    public MethodParameter(String defaultValue, boolean isBodyParameter, Class<? extends HTTPParameter> parameterClass, int parameterIndex, boolean required){
654      super(parameterIndex);
655      _parameter = parameterClass;
656      if(org.apache.commons.lang3.StringUtils.isBlank(defaultValue)){
657        _defaultValues = null;
658      }else{
659        _defaultValues = Arrays.asList(org.apache.commons.lang3.StringUtils.split(defaultValue, Definitions.SEPARATOR_URI_QUERY_PARAM_VALUES));
660      }
661      _required = required;
662      _isBodyParameter = isBodyParameter;
663    }
664
665    /**
666     * @return the parameter
667     */
668    public Class<? extends HTTPParameter> getParameter() {
669      return _parameter;
670    }
671
672    /**
673     * @return the defaultValue or null if none available
674     */
675    public List<String> getDefaultValues() {
676      return _defaultValues;
677    }
678
679    /**
680     * @return the required
681     */
682    public boolean isRequired() {
683      return _required;
684    }
685
686    /**
687     * @return the isBodyParameter
688     */
689    public boolean isBodyParameter() {
690      return _isBodyParameter;
691    }
692  } // class MethodParamater
693
694  /**
695   * A special authentication parameter, which defines whether user authentication should be required or not upon method invocation.
696   *
697   */
698  private static class AuthParameter extends Parameter{
699    private Class<? extends AuthenticationParameter> _parameter = null;
700    private boolean _required = false;
701    private boolean _showLoginPrompt = false;
702
703    /**
704     * 
705     * @param parameter
706     * @param parameterIndex
707     * @param required
708     * @param showLoginPrompt
709     */
710    public AuthParameter(Class<? extends AuthenticationParameter> parameter, int parameterIndex, boolean required, boolean showLoginPrompt){
711      super(parameterIndex);
712      _parameter = parameter;
713      _required = required;
714      _showLoginPrompt = showLoginPrompt;
715    }
716
717    /**
718     * @return the parameter
719     */
720    public Class<? extends AuthenticationParameter> getParameter() {
721      return _parameter;
722    }
723
724    /**
725     * @return the required
726     */
727    public boolean isRequired() {
728      return _required;
729    }
730
731    /**
732     * @return the showLoginPrompt
733     */
734    public boolean isShowLoginPrompt() {
735      return _showLoginPrompt;
736    }
737  } // class AuthParameter
738
739  /**
740   * Defines a header parameter inside a Service method.
741   *
742   */
743  private static class HeaderParameter extends Parameter{
744    private String _defaultValue = null;
745    private Class<? extends HTTPHeader> _parameter = null;
746    private boolean _required = false;
747
748    /**
749     * 
750     * @param defaultValue
751     * @param parameter
752     * @param parameterIndex
753     * @param required
754     */
755    public HeaderParameter(String defaultValue, Class<? extends HTTPHeader> parameter, int parameterIndex, boolean required){
756      super(parameterIndex);
757      _parameter = parameter;
758      _defaultValue = defaultValue;
759      if(org.apache.commons.lang3.StringUtils.isBlank(_defaultValue)){
760        _defaultValue = null;
761      }
762      _required = required;
763    }
764
765    /**
766     * @return the parameter
767     */
768    public Class<? extends HTTPHeader> getParameter() {
769      return _parameter;
770    }
771
772    /**
773     * @return the defaultValue
774     */
775    public String getDefaultValue() {
776      return _defaultValue;
777    }
778
779    /**
780     * @return the required
781     */
782    public boolean isRequired() {
783      return _required;
784    }
785  } // class HeaderParameter
786}