/** * Internal method to load class methods annotated with {@link MethodMapping} */
  private void loadAnnotatedMethods() {
    Method[] methods = getClass().getDeclaredMethods();
    boolean isUsingAuthAnnot = false;

    for (int i = 0; i < methods.length; i++) {
      Method method = methods[i];
      MethodMapping methodMapped = method.getAnnotation(MethodMapping.class);
      AuthorizeInvocation authorizeInvocation = method.getAnnotation(AuthorizeInvocation.class);

      isUsingAuthAnnot = isUsingAuthAnnot || authorizeInvocation != null;

      if (methodMapped != null) {
        HttpMethod httpMethod = methodMapped.httpMethod();
        MethodMappingInfo urlMappingInfo =
            new MethodMappingInfo(method, getMimeTypeResolver(method));

        if (!isMimeTypesSupported(urlMappingInfo.getMimeInputFormat())
            || !isMimeTypesSupported(urlMappingInfo.getMimeOutputFormat()))
          throw new WicketRuntimeException(
              "Mapped methods use a MIME type not supported by obj serializer/deserializer!");

        mappedMethods.addValue(
            urlMappingInfo.getSegmentsCount() + "_" + httpMethod.getMethod(), urlMappingInfo);
      }
    }
    // if AuthorizeInvocation has been found but no role-checker has been
    // configured, throw an exception
    if (isUsingAuthAnnot && roleCheckingStrategy == null)
      throw new WicketRuntimeException(
          "Annotation AuthorizeInvocation is used but no role-checking strategy has been set for the controller!");
  }
  /**
   * * Handles a REST request invoking one of the methods annotated with {@link MethodMapping}. If
   * the annotated method returns a value, this latter is automatically serialized to a given string
   * format (like JSON, XML, etc...) and written to the web response.<br>
   * If no method is found to serve the current request, a 400 HTTP code is returned to the client.
   * Similarly, a 401 HTTP code is return if the user doesn't own one of the roles required to
   * execute an annotated method (See {@link AuthorizeInvocation}).
   */
  @Override
  public final void respond(Attributes attributes) {
    PageParameters pageParameters = attributes.getParameters();
    WebResponse response = (WebResponse) attributes.getResponse();
    HttpMethod httpMethod = HttpUtils.getHttpMethod((WebRequest) RequestCycle.get().getRequest());
    int indexedParamCount = pageParameters.getIndexedCount();

    // mapped method are stored concatenating the number of the segments of
    // their URL and their HTTP method (see annotation MethodMapping)
    List<MethodMappingInfo> mappedMethodsCandidates =
        mappedMethods.get(indexedParamCount + "_" + httpMethod.getMethod());

    MethodMappingInfo mappedMethod =
        selectMostSuitedMethod(mappedMethodsCandidates, pageParameters);

    if (mappedMethod != null) {
      if (!hasAny(mappedMethod.getRoles())) {
        response.sendError(401, "User is not allowed to invoke method on server.");
        return;
      }

      onBeforeMethodInvoked(mappedMethod, attributes);
      Object result = invokeMappedMethod(mappedMethod, attributes);
      onAfterMethodInvoked(mappedMethod, attributes, result);

      // if the invoked method returns a value, it is written to response
      if (result != null) {
        serializeObjectToResponse(response, result, mappedMethod.getMimeOutputFormat());
      }
    } else {
      response.sendError(
          400,
          "No suitable method found for URL '"
              + extractUrlFromRequest()
              + "' and HTTP method "
              + httpMethod);
    }
  }
  /**
   * Method invoked to select the most suited method to serve the current request.
   *
   * @param mappedMethods List of {@link MethodMappingInfo} containing the informations of mapped
   *     methods.
   * @param pageParameters The PageParameters of the current request.
   * @return The "best" method found to serve the request.
   */
  private MethodMappingInfo selectMostSuitedMethod(
      List<MethodMappingInfo> mappedMethods, PageParameters pageParameters) {
    int highestScore = 0;
    MultiMap<Integer, MethodMappingInfo> mappedMethodByScore =
        new MultiMap<Integer, MethodMappingInfo>();

    // no method mapped
    if (mappedMethods == null || mappedMethods.size() == 0) return null;

    /**
     * To select the "best" method, a score is assigned to every mapped method. To calculate the
     * score method calculateScore is executed for every segment.
     */
    for (MethodMappingInfo mappedMethod : mappedMethods) {
      List<AbstractURLSegment> segments = mappedMethod.getSegments();
      int score = 0;

      for (AbstractURLSegment segment : segments) {
        int i = segments.indexOf(segment);
        String currentActualSegment =
            AbstractURLSegment.getActualSegment(pageParameters.get(i).toString());

        int partialScore = segment.calculateScore(currentActualSegment);

        if (partialScore == 0) {
          score = -1;
          break;
        }

        score += partialScore;
      }

      if (score >= highestScore) {
        highestScore = score;
        mappedMethodByScore.addValue(score, mappedMethod);
      }
    }
    // if we have more than one method with the highest score, throw
    // ambiguous exception.
    if (mappedMethodByScore.get(highestScore) != null
        && mappedMethodByScore.get(highestScore).size() > 1)
      throwAmbiguousMethodsException(mappedMethodByScore.get(highestScore));

    return mappedMethodByScore.getFirstValue(highestScore);
  }