Package org.jibeframework.core.app.method

Source Code of org.jibeframework.core.app.method.ArgumentsResolver$ArgumentHolder

package org.jibeframework.core.app.method;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jibeframework.core.Context;
import org.jibeframework.core.JibeRuntimeException;
import org.jibeframework.core.annotation.ConversationAttribute;
import org.jibeframework.core.annotation.UIComponent;
import org.jibeframework.core.annotation.UIController;
import org.jibeframework.core.app.conversation.Conversation;
import org.jibeframework.core.util.Content;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestParam;

import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

/**
* Responsibility of this service is dynamic parameters injection which happens
* on invocation of annotated methods
*
* @author dhalupa
*
*/
@Component
public class ArgumentsResolver implements InitializingBean, ApplicationContextAware {
  private static final Log logger = LogFactory.getLog(ArgumentsResolver.class);
  private List<ArgumentResolver> resolvers = new ArrayList<ArgumentResolver>();
  private Map<String, RequestParamArgumentResolver> requestParamResolvers = new HashMap<String, RequestParamArgumentResolver>();
  private static ThreadLocal<ArgumentCandidatesCache> argThreadLocal = new ThreadLocal<ArgumentCandidatesCache>();
  private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
  private ApplicationContext applicationContext;
  @Autowired
  @Qualifier("jibe.spring.ConversionService")
  private ConversionService conversionService;

  /**
   * This method will return a list of object that will be used as parameters
   * on method invocation
   *
   * @param method
   * @param actualArguments
   * @return
   */
  @SuppressWarnings("unchecked")
  public Object[] resolveArguments(Method method, Object[] actualArguments) {
    Context context = Context.getCurrentContext();
    Map<String, Object> data = (Map<String, Object>) context.getParams().get("data");
    Class<?>[] parameterTypes = method.getParameterTypes();
    MethodParameter[] parameters = new MethodParameter[parameterTypes.length];
    for (int i = 0; i < parameterTypes.length; i++) {
      parameters[i] = new MethodParameter(method, i);
      parameters[i].initParameterNameDiscovery(parameterNameDiscoverer);
    }
    Object[] args = new Object[parameters.length];
    for (int i = 0; i < parameters.length; i++) {
      String name = parameters[i].getParameterName();
      if (data != null && data.containsKey(name)) {
        Object val = data.get(name);
        if (conversionService.canConvert(val.getClass(), parameters[i].getParameterType())) {
          args[i] = conversionService.convert(val, parameters[i].getParameterType());
        } else {
          args[i] = val;
        }
      }
      if (args[i] == null) {
        for (Annotation paramAnn : parameters[i].getParameterAnnotations()) {
          if (data != null && RequestParam.class.isInstance(paramAnn)) {
            RequestParam requestParam = (RequestParam) paramAnn;
            String paramName = requestParam.value();
            boolean required = requestParam.required();
            String defaultValue = requestParam.defaultValue();

            Object val = data.get(paramName);
            if (val == null) {
              if (defaultValue != null) {
                val = defaultValue;
              } else if (required) {
                throw new JibeRuntimeException(paramName
                    + " parameter has to be present in the request");
              }
            }
            if (conversionService.canConvert(val.getClass(), parameters[i].getParameterType())) {
              args[i] = conversionService.convert(val, parameters[i].getParameterType());
            } else {
              args[i] = val;
            }
            break;
          } else if (ConversationAttribute.class.isInstance(paramAnn)) {
            ConversationAttribute convAttrAnn = (ConversationAttribute) paramAnn;
            if (StringUtils.isEmpty(convAttrAnn.value())) {
              for (Object attr : context.getConversation().getAttributes().values()) {
                if (attr != null && parameters[i].getParameterType().isAssignableFrom(attr.getClass())) {
                  args[i] = attr;
                }
              }
            } else {
              args[i] = context.getConversation().getAttribute(convAttrAnn.value());
            }
          }
        }
      }
      Class<?> parameterType = parameters[i].getParameterType();
      if (args[i] == null) {
        for (int j = 0; j < actualArguments.length; j++) {
          if (actualArguments[j] == null || actualArguments[j].getClass().isAssignableFrom(parameterType)) {
            args[i] = actualArguments[j];
            // remove, so that we do not inject twice
            actualArguments[j] = null;
            break;
          }
        }
      }
      if (args[i] == null) {
        ArgumentCandidatesCache cache = argThreadLocal.get();
        if (cache == null) {
          cache = new ArgumentCandidatesCache();
        }
        Object arg = cache.fetch(parameters[i]);
        if (arg.equals(ArgumentResolver.UNRESOLVED)) {
          for (ArgumentResolver resolver : resolvers) {
            arg = resolver.resolve(parameters[i], context);
            if (!arg.equals(ArgumentResolver.UNRESOLVED)) {
              args[i] = arg;
              cache.add(parameters[i], arg);
            }
          }
        } else {
          args[i] = arg;
        }
      }
      if (args[i] == null) {
        UIComponent uiCmpAnn = parameters[i].getParameterAnnotation(UIComponent.class);
        if (uiCmpAnn != null) {
          args[i] = applicationContext.getBean(uiCmpAnn.value());
        } else {
          UIController cntAnn = parameters[i].getParameterAnnotation(UIController.class);
          if (cntAnn != null) {
            args[i] = applicationContext.getBean(cntAnn.value());
          }
        }
      }
    }

    return args;

  }

  public static void addToArgsCache(Object object) {
    argThreadLocal.get().add(object.getClass(), object);
  }

  public static void addToArgsCache(Class<?> genericType, Class<?>[] actualTypes, Object object) {
    argThreadLocal.get().add(genericType, actualTypes, object);
  }

  public void initializeArgumentCandidates(Context context) {
    ArgumentCandidatesCache cache = new ArgumentCandidatesCache();
    argThreadLocal.set(cache);
    for (String paramName : this.requestParamResolvers.keySet()) {
      if (context.getParams().containsKey(paramName)) {
        this.requestParamResolvers.get(paramName)
            .addToArgumentsCache(cache, context.getParams().get(paramName));
      }
    }
  }

  /**
   * Registers a resolver for a particular parameter type
   *
   * @param resolver
   */
  public void registerResolver(ArgumentResolver resolver) {
    this.resolvers.add(resolver);
  }

  /**
   * Registers a resolver that will be invoked to add a parameter candidate
   * into the candidates cache if http request contains parameter with
   * registered name
   *
   * @param paramName
   *            parameter name to be registered
   * @param resolver
   */
  public void registerRequestParamResolver(String paramName, RequestParamArgumentResolver resolver) {
    this.requestParamResolvers.put(paramName, resolver);
  }

  public void afterPropertiesSet() throws Exception {
    registerResolver(new ArgumentResolver() {
      public Object resolve(MethodParameter parameter, Context context) {
        if (Context.class.isAssignableFrom(parameter.getParameterType())) {
          return context;
        }
        return UNRESOLVED;
      }

    });
    registerResolver(new ArgumentResolver() {
      public Object resolve(MethodParameter parameter, Context context) {
        if (HttpServletResponse.class.isAssignableFrom(parameter.getParameterType())) {
          return context.getServletResponse();
        }
        return UNRESOLVED;
      }

    });
    registerResolver(new ArgumentResolver() {
      public Object resolve(MethodParameter parameter, Context context) {
        if (Conversation.class.isAssignableFrom(parameter.getParameterType())) {
          return context.getConversation();
        }
        return UNRESOLVED;
      }

    });
    registerResolver(new ArgumentResolver() {
      public Object resolve(MethodParameter parameter, Context context) {
        if (HttpServletRequest.class.isAssignableFrom(parameter.getParameterType())) {
          return context.getServletRequest();
        }
        return UNRESOLVED;
      }

    });
    registerResolver(new ArgumentResolver() {
      public Object resolve(MethodParameter parameter, Context context) {
        if (Content.class.isAssignableFrom(parameter.getParameterType())) {
          return context.getUploadedContent();
        }
        return UNRESOLVED;
      }

    });
    registerResolver(new ArgumentResolver() {
      public Object resolve(MethodParameter parameter, Context context) {
        if (HttpSession.class.isAssignableFrom(parameter.getParameterType())) {
          return context.getSession();
        }
        return UNRESOLVED;
      }
    });
    registerResolver(new ArgumentResolver() {
      public Object resolve(MethodParameter parameter, Context context) {
        if (ArgumentCandidatesCache.class.isAssignableFrom(parameter.getParameterType())) {
          return argThreadLocal.get();
        }
        return UNRESOLVED;
      }
    });
  }

  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;

  }

  public static class ArgumentCandidatesCache {

    private List<ArgumentHolder> cache = new ArrayList<ArgumentHolder>();

    protected void add(MethodParameter parameter, Object object) {
      final Type type = parameter.getGenericParameterType();
      CollectionUtils.filter(this.cache, new Predicate() {
        public boolean evaluate(Object object) {
          ArgumentHolder holder = (ArgumentHolder) object;
          return !holder.match(type);
        }
      });
      cache.add(new ArgumentHolder(parameter, object));
    }

    public void add(final Class<?> type, Object object) {
      CollectionUtils.filter(this.cache, new Predicate() {
        public boolean evaluate(Object object) {
          ArgumentHolder holder = (ArgumentHolder) object;
          return !holder.match(type);
        }
      });
      cache.add(new ArgumentHolder(type, object));
    }

    public void add(Class<?> genericType, Class<?>[] actualTypes, Object object) {
      cache.add(new ArgumentHolder(genericType, actualTypes, object));
    }

    protected Object fetch(MethodParameter parameter) {
      for (ArgumentHolder holder : cache) {
        if (holder.match(parameter.getGenericParameterType())) {
          return holder.getObject();
        }
      }
      return ArgumentResolver.UNRESOLVED;
    }

    @Override
    public String toString() {
      return cache.toString();
    }

  }

  public static class ArgumentHolder {
    Class<?> type = null;
    Class<?>[] actualArguments = null;
    Object object;

    public ArgumentHolder(Class<?> type, Object object) {
      this.type = type;
      this.object = object;
    }

    public ArgumentHolder(Class<?> genericType, Class<?>[] actualTypes, Object object) {
      this.type = genericType;
      this.actualArguments = actualTypes;
      this.object = object;
    }

    protected ArgumentHolder(MethodParameter parameter, Object object) {
      Type genericType = parameter.getGenericParameterType();
      if (genericType instanceof Class<?>) {
        this.type = (Class<?>) genericType;
      } else {
        ParameterizedTypeImpl parameterizedType = (ParameterizedTypeImpl) genericType;
        this.type = parameterizedType.getRawType();
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        this.actualArguments = new Class<?>[actualTypeArguments.length];
        for (int i = 0; i < actualTypeArguments.length; i++) {
          if (actualTypeArguments[i] instanceof Class<?>) {
            this.actualArguments[i] = (Class<?>) actualTypeArguments[i];
          } else {
            throw new JibeRuntimeException("Nested generics are not supported");
          }
        }
      }
      this.object = object;
    }

    protected Object getObject() {
      return object;
    }

    protected boolean match(Type parameterType) {
      if (parameterType instanceof Class<?>) {
        return !parameterType.equals(Object.class) && (((Class<?>) parameterType).isAssignableFrom(this.type));
      } else {
        ParameterizedTypeImpl parameterized = (ParameterizedTypeImpl) parameterType;
        if (parameterized.getRawType().isAssignableFrom(this.type)) {
          Type[] actualTypeArgumentsToTest = parameterized.getActualTypeArguments();
          for (int i = 0; i < actualTypeArgumentsToTest.length; i++) {
            if (actualTypeArgumentsToTest[i] instanceof Class<?>) {
              if (!actualArguments[i].isAssignableFrom((Class<?>) actualTypeArgumentsToTest[i])) {
                return false;
              }
            }
          }
          return true;
        }
      }
      return false;
    }

    @Override
    public String toString() {
      return "ArgumentHolder [" + type.getName() + "]";
    }

  }

}
TOP

Related Classes of org.jibeframework.core.app.method.ArgumentsResolver$ArgumentHolder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.