@Override
  protected Object retrieveAction(
      GroovyObject controller, String actionName, HttpServletResponse response) {

    Class<?> controllerClass = AopProxyUtils.ultimateTargetClass(controller);

    Method mAction =
        ReflectionUtils.findMethod(
            controllerClass, actionName, MethodGrailsControllerHelper.NOARGS);
    if (mAction != null) {
      ReflectionUtils.makeAccessible(mAction);
      if (mAction.getAnnotation(Action.class) != null) {
        return mAction;
      }
    }

    try {
      return controller.getProperty(actionName);
    } catch (MissingPropertyException mpe) {
      try {
        response.sendError(HttpServletResponse.SC_NOT_FOUND);
        return null;
      } catch (IOException e) {
        throw new ControllerExecutionException("I/O error sending 404 error", e);
      }
    }
  }
  public final Collection<ConfigAttribute> getAttributes(Object object) {
    if (object instanceof MethodInvocation) {
      MethodInvocation mi = (MethodInvocation) object;
      Object target = mi.getThis();
      Class<?> targetClass = null;

      if (target != null) {
        targetClass =
            target instanceof Class<?>
                ? (Class<?>) target
                : AopProxyUtils.ultimateTargetClass(target);
      }
      Collection<ConfigAttribute> attrs = getAttributes(mi.getMethod(), targetClass);
      if (attrs != null && !attrs.isEmpty()) {
        return attrs;
      }
      if (target != null && !(target instanceof Class<?>)) {
        attrs = getAttributes(mi.getMethod(), target.getClass());
      }
      return attrs;
    }

    throw new IllegalArgumentException("Object must be a non-null MethodInvocation");
  }
  @Override
  protected void doFilter(
      HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws Exception {
    // TODO need blocking cache stuff from CachingFilter
    initContext();

    try {

      Object controller = lookupController(getContext().getControllerClass());
      if (controller == null) {
        log.debug(
            "Not a controller request {}:{} {}",
            request.getMethod(),
            request.getRequestURI(),
            getContext());
        chain.doFilter(request, response);
        return;
      }

      Class<?> controllerClass = AopProxyUtils.ultimateTargetClass(controller);
      if (controllerClass == null) {
        controllerClass = controller.getClass();
      }
      Method method = getContext().getMethod();
      if (method == null) {
        log.debug(
            "No cacheable method found for {}:{} {}",
            request.getMethod(),
            request.getRequestURI(),
            getContext());
        chain.doFilter(request, response);
        return;
      }
      Collection<CacheOperation> cacheOperations =
          cacheOperationSource.getCacheOperations(method, controllerClass, true);

      if (CollectionUtils.isEmpty(cacheOperations)) {
        log.debug(
            "No cacheable annotation found for {}:{} {}",
            request.getMethod(),
            request.getRequestURI(),
            getContext());
        chain.doFilter(request, response);
        return;
      }

      Map<String, Collection<CacheOperationContext>> operationsByType =
          createOperationContext(cacheOperations, method, controllerClass, request);

      // start with evictions
      if (inspectBeforeCacheEvicts(operationsByType.get(EVICT))) {
        chain.doFilter(request, response);
        return;
      }

      // follow up with cacheable
      CacheStatus status = inspectCacheables(operationsByType.get(CACHEABLE));

      Map<CacheOperationContext, Object> updates =
          inspectCacheUpdates(operationsByType.get(UPDATE));

      if (status != null) {
        if (status.updateRequired) {
          updates.putAll(status.updates);
        }
        // render cached response
        else {
          logRequestDetails(request, getContext(), "Caching enabled for request");
          PageInfo pageInfo = buildCachedPageInfo(request, response, status);
          writeResponse(request, response, pageInfo);
          return;
        }
      }

      logRequestDetails(request, getContext(), "Caching enabled for request");
      PageInfo pageInfo = buildNewPageInfo(request, response, chain, status, operationsByType);
      writeResponse(request, response, pageInfo);

      inspectAfterCacheEvicts(operationsByType.get(EVICT));

      if (!updates.isEmpty()) {
        Collection<Cache> caches = new ArrayList<Cache>();
        for (Map.Entry<CacheOperationContext, Object> entry : updates.entrySet()) {
          for (Cache cache : entry.getKey().getCaches()) {
            caches.add(cache);
          }
        }
        update(caches, pageInfo, status, calculateKey(request));
      }
    } finally {
      destroyContext();
    }
  }
 @SuppressWarnings("unchecked")
 protected final <T> Class<T> getClass(T object) {
   return (Class<T>) AopProxyUtils.ultimateTargetClass(object);
 }