/**
   * Searches for {@link URLQueryParameter} annotations on a single field.
   *
   * @param field Field to scan
   * @param classMappingIds The mapping IDs of the class this method belongs to
   */
  private void processFieldAnnotations(final Field field, final String[] classMappingIds) {
    // Is there a @URLQueryParameter annotation?
    URLQueryParameter queryParamAnnotation = field.getAnnotation(URLQueryParameter.class);

    if (queryParamAnnotation != null) {

      // create a QueryParamSpec from the annotation
      QueryParamSpec queryParam = new QueryParamSpec();
      queryParam.setFieldName(field.getName());
      queryParam.setOwnerClass(field.getDeclaringClass());
      queryParam.setName(queryParamAnnotation.value());
      queryParam.setOnPostback(queryParamAnnotation.onPostback());

      // check which mapping the action belongs to
      if (!isBlank(queryParamAnnotation.mappingId())) {
        // action belongs to the mapping mentioned with mappingId attribute
        queryParam.setMappingIds(new String[] {queryParamAnnotation.mappingId().trim()});
      } else if ((classMappingIds != null) && (classMappingIds.length > 0)) {
        // use the mappings found on the class
        queryParam.setMappingIds(classMappingIds);
      } else {
        throw new IllegalArgumentException(
            "Unable to find a suitable mapping "
                + "for the query-parameter definied on field '"
                + field.getName()
                + "' in class '"
                + field.getDeclaringClass().getName()
                + "'. Either place a @URLMapping annotation on the "
                + "class or reference a foreign mapping using the 'mappingId' attribute.");
      }

      // check if there is also a validation annotation placed on the field
      URLValidator validationAnnotation = field.getAnnotation(URLValidator.class);

      // check if annotation has been found
      if (validationAnnotation != null) {

        // set validation options on the QueryParamSpec object
        queryParam.setValidatorIds(validationAnnotation.validatorIds());
        queryParam.setOnError(validationAnnotation.onError());
        queryParam.setValidator(validationAnnotation.validator());
      }

      // check if there is also a converter annotation placed on the field
      URLConverter converterAnnotation = field.getAnnotation(URLConverter.class);
      if (converterAnnotation != null) {
        queryParam.setConverterId(converterAnnotation.converterId().trim());
      }

      // add the new spec object to the list of specs
      queryParamList.add(queryParam);
    }
  }
  /**
   * Process a single {@link URLMapping} annotation.
   *
   * @param clazz The class that the annotation was found on
   * @param mappingAnnotation The annotation to process
   * @return The mapping ID of the mapping found
   */
  private String processPrettyMappingAnnotation(
      final Class clazz, final URLMapping mappingAnnotation) {

    // log class name
    if (log.isTraceEnabled()) {
      log.trace("Found @URLMapping annotation on class: " + clazz.getName());
    }

    // create UrlMapping from annotation
    UrlMapping mapping = new UrlMapping();
    mapping.setId(mappingAnnotation.id());
    mapping.setParentId(mappingAnnotation.parentId());
    mapping.setPattern(mappingAnnotation.pattern());
    mapping.setViewId(mappingAnnotation.viewId());
    mapping.setOutbound(mappingAnnotation.outbound());
    mapping.setOnPostback(mappingAnnotation.onPostback());

    // register mapping
    Object existingMapping = urlMappings.put(mapping.getId(), mapping);

    // fail if a mapping with this ID already existed
    if (existingMapping != null) {
      throw new IllegalArgumentException("Duplicated mapping id: " + mapping.getId());
    }

    // At bean name to lookup map if it has been specified
    if ((mappingAnnotation.beanName() != null) && (mappingAnnotation.beanName().length() > 0)) {
      beanNameMap.put(clazz, mappingAnnotation.beanName());
    }

    // process validations
    for (URLValidator validationAnnotation : mappingAnnotation.validation()) {

      // index attribute is required in this case
      if (validationAnnotation.index() < 0) {
        throw new IllegalArgumentException(
            "Please set the index of the path parameter you want to validate with the @URLValidator specified on mapping: "
                + mapping.getId());
      }

      // prepare PathValidator
      PathValidator pathValidator = new PathValidator();
      pathValidator.setIndex(validationAnnotation.index());
      pathValidator.setOnError(validationAnnotation.onError());
      pathValidator.setValidatorIds(join(validationAnnotation.validatorIds(), " "));

      // optional validator method
      if (!isBlank(validationAnnotation.validator())) {
        pathValidator.setValidatorExpression(
            new ConstantExpression(validationAnnotation.validator()));
      }

      // add PathValidator to the mapping
      mapping.getPathValidators().add(pathValidator);
    }

    // process converters
    for (URLConverter converterAnnotation : mappingAnnotation.converter()) {

      // index attribute is required in this case
      if (converterAnnotation.index() < 0) {
        throw new IllegalArgumentException(
            "Please set the index of the path parameter you want to convert with the @URLConverter specified on mapping: "
                + mapping.getId());
      }

      // prepare PathValidator
      PathConverter pathConverter = new PathConverter();
      pathConverter.setIndex(converterAnnotation.index());
      pathConverter.setConverterId(StringUtils.trimToNull(converterAnnotation.converterId()));

      // add PathValidator to the mapping
      mapping.addPathConverter(pathConverter);
    }

    // return mapping id
    return mapping.getId().trim();
  }