content: package com.alicp.jetcache.anno.field;
  
  import com.alicp.jetcache.anno.CreateCache;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.beans.BeansException;
  import org.springframework.beans.PropertyValues;
  import org.springframework.beans.factory.BeanCreationException;
  import org.springframework.beans.factory.BeanFactory;
  import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
  import org.springframework.beans.factory.annotation.InjectionMetadata;
  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  import org.springframework.stereotype.Component;
  import org.springframework.util.ReflectionUtils;
  import org.springframework.util.StringUtils;
  
  import java.beans.PropertyDescriptor;
  import java.lang.reflect.Field;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.util.LinkedList;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  import java.util.concurrent.locks.ReentrantLock;
  
  /**
   * Created on 2016/12/9.
   *
   * @author huangli
   * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
   */
  @SuppressWarnings("AliMissingOverrideAnnotation")
  @Component // necessary when spring boot version <= 1.2.X
  public class CreateCacheAnnotationBeanPostProcessor extends AutowiredAnnotationBeanPostProcessor {
  
      private static Logger logger = LoggerFactory.getLogger(CreateCacheAnnotationBeanPostProcessor.class);
  
      private ConfigurableListableBeanFactory beanFactory;
  
      private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<String, InjectionMetadata>();
      private final ReentrantLock reentrantLock = new ReentrantLock();
  
      @Override
      public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
          super.setBeanFactory(beanFactory);
          if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
              throw new IllegalArgumentException(
                      "AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory");
          }
          this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
      }
  
      // removed in spring 6
      //@Override
      public PropertyValues postProcessPropertyValues(
              PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException {
          return postProcessProperties(pvs, bean, beanName);
      }
  
      //@Override
      //since 5.1
      @SuppressWarnings("AliMissingOverrideAnnotation")
      public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
          InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
          try {
              metadata.inject(bean, beanName, pvs);
          } catch (BeanCreationException ex) {
              throw ex;
          } catch (Throwable ex) {
              throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
          }
          return pvs;
      }
  
      private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
          // Fall back to class name as cache key, for backwards compatibility with custom callers.
          String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
          // Quick check on the concurrent map first, with minimal locking.
          InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
          if (InjectionMetadata.needsRefresh(metadata, clazz)) {
              reentrantLock.lock();
              try{
                  metadata = this.injectionMetadataCache.get(cacheKey);
                  if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                      if (metadata != null) {
                          clear(metadata, pvs);
                      }
                      try {
                          metadata = buildAutowiringMetadata(clazz);
                          this.injectionMetadataCache.put(cacheKey, metadata);
                      } catch (NoClassDefFoundError err) {
                          throw new IllegalStateException("Failed to introspect bean class [" + clazz.getName() +
                                  "] for autowiring metadata: could not find class that it depends on", err);
                      }
                  }
              }finally {
                  reentrantLock.unlock();
              }
          }
          return metadata;
      }
  
      private volatile boolean clearInited = false;
      private Method cleanMethod;
  
      /**
       * clear method not found in Spring 4.0.
       * @param obj
       * @param param
       */
      private void clear(InjectionMetadata obj, PropertyValues param) {
          if(!clearInited){
              try {
                  cleanMethod = InjectionMetadata.class.getMethod("clear", PropertyValues.class);
              } catch (NoSuchMethodException e) {
              }
              clearInited = true;
          }
          if (cleanMethod != null) {
              try {
                  cleanMethod.invoke(obj, param);
              } catch (IllegalAccessException | InvocationTargetException e) {
                  throw new IllegalStateException(e);
              }
          }
      }
  
      private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
          LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>();
          Class<?> targetClass = clazz;
  
          do {
              final LinkedList<InjectionMetadata.InjectedElement> currElements =
                      new LinkedList<InjectionMetadata.InjectedElement>();
  
              doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {
                  @Override
                  public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                      CreateCache ann = field.getAnnotation(CreateCache.class);
                      if (ann != null) {
                          if (Modifier.isStatic(field.getModifiers())) {
                              if (logger.isWarnEnabled()) {
                                  logger.warn("Autowired annotation is not supported on static fields: " + field);
                              }
                              return;
                          }
                          currElements.add(new AutowiredFieldElement(field, ann));
                      }
                  }
              });
  
              elements.addAll(0, currElements);
              targetClass = targetClass.getSuperclass();
          }
          while (targetClass != null && targetClass != Object.class);
  
          return new InjectionMetadata(clazz, elements);
      }
  
      private void doWithLocalFields(Class clazz, ReflectionUtils.FieldCallback fieldCallback) {
          Field fs[] = clazz.getDeclaredFields();
          for (Field field : fs) {
              try {
                  fieldCallback.doWith(field);
              } catch (IllegalAccessException ex) {
                  throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex);
              }
          }
      }
  
  
      private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
  
          private Field field;
          private CreateCache ann;
  
          public AutowiredFieldElement(Field field, CreateCache ann) {
              super(field, null);
              this.field = field;
              this.ann = ann;
          }
  
          @Override
          protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
              beanFactory.registerDependentBean(beanName, "globalCacheConfig");
              CreateCacheWrapper wrapper = new CreateCacheWrapper(beanFactory, ann, field);
              field.setAccessible(true);
              field.set(bean, wrapper.getCache());
          }
      }
  
  }
  