
package com.hotent.base.util;

import com.hotent.base.exception.BaseException;
import com.hotent.base.groovy.IScript;
import com.hotent.base.groovy.IUserScript;
import com.hotent.base.jms.impl.JmsProducerEmptyImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.*;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类描述 动态加载外部jar包的自定义类加载器
 * 
 *
 */
public class ExtendClassLoader extends URLClassLoader {

	private static final Log logger = LogFactory.getLog(ExtendClassLoader.class);

	// 属于本类加载器加载的jar包
	private JarFile jarFile;

	// 保存已经加载过的Class对象
	@SuppressWarnings("rawtypes")
	private static Map<String, Class> cacheClassMap = new HashMap<>();

	private static Map<String, String> cacheClassMd5Map = new HashMap<>();

	// 保存本类加载器加载的class字节码
	private Map<String, byte[]> classBytesMap = new HashMap<>();

	@SuppressWarnings({ "rawtypes" })
	public static Map<String, Class> resolutionJarClass(byte[] jarBytes) throws IOException {
		File tempFile = File.createTempFile("tmp", ".jar");
		FileOutputStream fos = new FileOutputStream(tempFile);
		fos.write(jarBytes);
		fos.close();
		ExtendClassLoader classLoader = new ExtendClassLoader(new URL[] { tempFile.toURI().toURL() },Thread.currentThread().getContextClassLoader());
		Map<String, Class> init = classLoader.init();
		classLoader.close();
		tempFile.delete();
		return init;
	}

	// 构造
	private ExtendClassLoader(URL[] urls, ClassLoader parent) {
		super(urls, parent);
		URL url = urls[0];
		String path = url.getPath();
		try {
			jarFile = new JarFile(path);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	// 重写loadClass方法
	// 改写loadClass方式
	@Override
	public Class<?> loadClass(String name) throws ClassNotFoundException {
		if (findLoadedClass(name) == null) {
			return super.loadClass(name);
		} else {
			return cacheClassMap.get(name);
		}
	}

	/**
	 * 方法描述 初始化类加载器，保存字节码
	 * 
	 * @method init
	 */
	@SuppressWarnings("rawtypes")
	private Map<String, Class> init() {
		Map<String, Class> classMap = new HashMap<>();
		Map<String, String> Md5Map = new HashMap<>();
		// 解析jar包每一项
		Enumeration<JarEntry> en = jarFile.entries();
		try {
			while (en.hasMoreElements()) {
				JarEntry je = en.nextElement();
				String name = je.getName();
				// 这里添加了路径扫描限制
				if (name.endsWith(".class")) {
					String className = name.replace(".class", "").replaceAll("/", ".");
					try (InputStream input = jarFile.getInputStream(je)) {
						MessageDigest digest = null;
						try {
							digest = MessageDigest.getInstance("MD5");
						} catch (NoSuchAlgorithmException e) {
							e.printStackTrace();
						}
						ByteArrayOutputStream baos = new ByteArrayOutputStream();
						int bufferSize = 4096;
						byte[] buffer = new byte[bufferSize];
						int bytesNumRead = 0;
						while ((bytesNumRead = input.read(buffer)) != -1) {
							baos.write(buffer, 0, bytesNumRead);
							digest.update(buffer, 0, bytesNumRead);
						}
						byte[] classBytes = baos.toByteArray();
						BigInteger bigInt = new BigInteger(1, digest.digest());
						Md5Map.put(className, bigInt.toString(16));
						classBytesMap.put(className, classBytes);
					}

				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				jarFile.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		// 将jar中的每一个class字节码进行Class载入
		for (Entry<String, byte[]> entry : classBytesMap.entrySet()) {
			String key = entry.getKey();
			Class<?> aClass = null;
			try {
				aClass = loadClass(key);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}

			// 未找到类，或者不是实现了脚本接口的跳过
			if (aClass == null
					|| (!IUserScript.class.isAssignableFrom(aClass) && !IScript.class.isAssignableFrom(aClass))) {
				continue;
			}
			// 实现了脚本接口要注册到上下文的必须加ExtendClassCheck注解
			/*
			 * if (aClass.getAnnotation(ExtendClassCheck.class) == null) { throw
			 * new BaseException("发布外部脚本类失败，外部脚本类必须加ExtendClassCheck注解"); }
			 */
			classMap.put(Md5Map.get(key), aClass);

		}
		return classMap;

	}

	/**
	 * 方法描述 初始化spring bean
	 * 
	 * @method initBean
	 */
	@SuppressWarnings("rawtypes")
	public static void registBean(List<Map<String, Class>> list) {
		// 获取BeanFactory
		DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) AppUtil
				.getApplicaitonContext().getAutowireCapableBeanFactory();
		defaultListableBeanFactory.setAllowBeanDefinitionOverriding(true);
		for (Map<String, Class> collection : list) {
			for (Iterator<Entry<String, Class>> iterator = collection.entrySet().iterator(); iterator.hasNext();) {
				Entry<String, Class> next = (Entry<String, Class>) iterator.next();
				Class cla = next.getValue();
				if (isSpringBeanClass(cla)) {
					// 将变量首字母置小写
					String beanName = getSimpleName(cla.getName());
					if (AppUtil.getBean(beanName) != null && !cacheClassMap.containsKey(beanName)) {
						throw new RuntimeException(String.format("注册外部bean【%s】失败，和系统已有的bean重复", cla.getClass().getName()));
					}
					try {
						defaultListableBeanFactory.destroySingleton(beanName);
						defaultListableBeanFactory.removeBeanDefinition(beanName);
					} catch (Exception e) {
					}
					defaultListableBeanFactory.registerBeanDefinition(beanName, getBeanDefinition(cla));
					Object registed = AppUtil.getBean(beanName);
					if (BeanUtils.isNotEmpty(registed)) {
						cacheClassMd5Map.put(beanName, next.getKey());
						cacheClassMap.put(beanName, next.getValue());
						logger.info("regist extend bean success:" + beanName);
					} else {
						logger.error("regist extend bean fail and try again:" + beanName);
						registBeanAgain(defaultListableBeanFactory, next);
					}
				}
			}
		}
		defaultListableBeanFactory.clearMetadataCache();
	}

	@SuppressWarnings("rawtypes")
	public static void registBeanAgain(DefaultListableBeanFactory defaultListableBeanFactory,
			Entry<String, Class> next) {
		Class cla = next.getValue();
		String beanName = getSimpleName(cla.getName());
		defaultListableBeanFactory.registerBeanDefinition(beanName, getBeanDefinition(JmsProducerEmptyImpl.class));
		AppUtil.getBean(beanName);
		try {
			defaultListableBeanFactory.destroySingleton(beanName);
			defaultListableBeanFactory.removeBeanDefinition(beanName);
		} catch (Exception e) {
		}
		defaultListableBeanFactory.registerBeanDefinition(beanName, getBeanDefinition(cla));
		Object registed = AppUtil.getBean(beanName);
		if (BeanUtils.isNotEmpty(registed)) {
			cacheClassMd5Map.put(beanName, next.getKey());
			cacheClassMap.put(beanName, cla);
			logger.info("regist extend bean success:" + beanName);
		} else {
			logger.error("regist extend bean again fail:" + beanName);
			throw new BaseException(String.format("扩展类【%s】注册失败", beanName));
		}
	}

	@SuppressWarnings("rawtypes")
	private static BeanDefinition getBeanDefinition(Class cla) {
		BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(cla);
		BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
		// 设置当前bean定义对象是单利的
		beanDefinition.setScope("singleton");
		beanDefinition.setLazyInit(false);
		return beanDefinition;
	}

	public static String getSimpleName(String className) {
		String beanName = StringUtils.uncapitalize(className);
		beanName = beanName.substring(beanName.lastIndexOf(".") + 1);
		beanName = StringUtils.uncapitalize(beanName);
		return beanName;
	}

	/**
	 * 方法描述 判断class对象是否带有spring的注解
	 * 
	 * @method isSpringBeanClass
	 * @param cla
	 *            jar中的每一个class
	 * @return true 是spring bean false 不是spring bean
	 */
	public static boolean isSpringBeanClass(Class<?> cla) {
		if (cla == null) {
			return false;
		}
		// 是否是接口
		if (cla.isInterface()) {
			return false;
		}

		// 是否是抽象类
		if (Modifier.isAbstract(cla.getModifiers())) {
			return false;
		}

		if (cla.getAnnotation(Component.class) != null) {
			return true;
		}
		if (cla.getAnnotation(Repository.class) != null) {
			return true;
		}
		if (cla.getAnnotation(Service.class) != null) {
			return true;
		}

		return false;
	}

	@SuppressWarnings("rawtypes")
	public Map<String, Class> getClassMap() {
		return cacheClassMap;
	}

	public static Map<String, String> getClassMd5Map() {
		return cacheClassMd5Map;
	}

	@SuppressWarnings("rawtypes")
	public static Class getClassByName(String name) {
		name = getSimpleName(name);
		if (cacheClassMap.containsKey(name)) {
			return cacheClassMap.get(name);
		}
		return null;
	}

	public static String getClassMd5ByName(String name) {
		name = getSimpleName(name);
		if (cacheClassMd5Map.containsKey(name) && cacheClassMap.containsKey(name)) {
			return cacheClassMd5Map.get(name);
		}
		return null;
	}

	public static void removeRegistedBean(String beanNames) {
		if (StringUtil.isEmpty(beanNames)) {
			return;
		}
		DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) AppUtil.getApplicaitonContext().getAutowireCapableBeanFactory();
		for (String beanName : beanNames.split(",")) {
			if (cacheClassMd5Map.containsKey(beanName) && cacheClassMap.containsKey(beanName)) {
				try {
					defaultListableBeanFactory.destroySingleton(beanName);
					defaultListableBeanFactory.removeBeanDefinition(beanName);
				} catch (Exception e) {
				}
				if (AppUtil.getBean(beanName) == null) {
					cacheClassMd5Map.remove(beanName);
					cacheClassMap.remove(beanName);
				}
			}
		}
	}

}