package com.ofsoft.cms.core.plugin.shiro;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresGuest;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.annotation.RequiresUser;

import com.jfinal.config.Routes;
import com.jfinal.config.Routes.Route;
import com.jfinal.core.ActionKey;
import com.jfinal.core.Controller;
import com.jfinal.log.Log;
import com.jfinal.plugin.IPlugin;

/**
 * Shiro插件，启动时加载所有Shiro访问控制注解。
 */
@SuppressWarnings("unchecked")
public class ShiroPlugin implements IPlugin {
	protected final Log log = Log.getLog(getClass());
	/**
	 * 登录成功时所用的页面。
	 */
	private String successUrl = "/";

	/**
	 * 登录成功时所用的页面。
	 */
	private String loginUrl = "/";

	/**
	 * action的扩展名，比如.action或者 .do
	 */
	private String extName = "";

	/**
	 * 登录成功时所用的页面。
	 */
	private String unauthorizedUrl = "/401.jsp";

	private final String SLASH = "/";

	/**
	 * Shiro的几种访问控制注解
	 */
	private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] {
			RequiresPermissions.class, RequiresRoles.class, RequiresUser.class,
			RequiresGuest.class, RequiresAuthentication.class };

	/**
	 * 路由设定
	 */
	private final Routes routes;

	/**
	 * 构造函数
	 * 
	 * @param routes
	 *            路由设定
	 */
	public ShiroPlugin(Routes routes) {
		this.routes = routes;
	}

	/**
	 * 停止插件
	 */
	public boolean stop() {
		return true;
	}

	/**
	 * 启动插件
	 */
	public boolean start() {
		Set<String> excludedMethodName = buildExcludedMethodName();
		ConcurrentMap<String, AuthzHandler> authzMaps = new ConcurrentHashMap<String, AuthzHandler>();
		// 逐个访问所有注册的Controller，解析Controller及action上的所有Shiro注解。
		// 并依据这些注解，actionKey提前构建好权限检查处理器。
		for (Route route : routes.getRouteItemList()) {
			Class<? extends Controller> controllerClass = route
					.getControllerClass();

			String controllerKey = route.getControllerKey();

			// 获取Controller的所有Shiro注解。
			List<Annotation> controllerAnnotations = getAuthzAnnotations(controllerClass);
			// 逐个遍历方法。
			Method[] methods = controllerClass.getMethods();
			for (Method method : methods) {
				// 排除掉Controller基类的所有方法，并且只关注没有参数的Action方法。
				if (!excludedMethodName.contains(method.getName())
						&& method.getParameterTypes().length == 0) {
					// 若该方法上存在ClearShiro注解，则对该action不进行访问控制检查。
					if (isClearShiroAnnotationPresent(method)) {
						continue;
					}
					// 获取方法的所有Shiro注解。
					List<Annotation> methodAnnotations = getAuthzAnnotations(method);
					// 依据Controller的注解和方法的注解来生成访问控制处理器。
					AuthzHandler authzHandler = createAuthzHandler(
							controllerAnnotations, methodAnnotations);
					// 生成访问控制处理器成功。
					if (authzHandler != null) {
						// 构建ActionKey，参考ActionMapping中实现
						String actionKey = createActionKey(controllerClass,
								method, controllerKey);
						// 添加映射
						authzMaps.put(actionKey, authzHandler);
						log.debug("Shiro authz add [ " + actionKey
								+ ", authzHandler " + authzHandler + "]");
					}
				}
			}
		}
		// 注入到ShiroKit类中。ShiroKit类以单例模式运行。
		ShiroKit.init(authzMaps);
		/**
		 * 设定登录，登录成功，未授权等url地址
		 */
		ShiroKit.setLoginUrl(loginUrl);
		ShiroKit.setSuccessUrl(successUrl);
		ShiroKit.setUnauthorizedUrl(unauthorizedUrl);
		ShiroKit.setExtName(extName);
		return true;
	}

	/**
	 * 从Controller方法中构建出需要排除的方法列表
	 * 
	 * @return
	 */
	private Set<String> buildExcludedMethodName() {
		Set<String> excludedMethodName = new HashSet<String>();
		Method[] methods = Controller.class.getMethods();
		for (Method m : methods) {
			if (m.getParameterTypes().length == 0)
				excludedMethodName.add(m.getName());
		}
		return excludedMethodName;
	}

	/**
	 * 依据Controller的注解和方法的注解来生成访问控制处理器。
	 * 
	 * @param controllerAnnotations
	 *            Controller的注解
	 * @param methodAnnotations
	 *            方法的注解
	 * @return 访问控制处理器
	 */
	private AuthzHandler createAuthzHandler(
			List<Annotation> controllerAnnotations,
			List<Annotation> methodAnnotations) {

		// 没有注解
		if (controllerAnnotations.size() == 0 && methodAnnotations.size() == 0) {
			return null;
		}
		// 至少有一个注解
		List<AuthzHandler> authzHandlers = new ArrayList<AuthzHandler>(
				AUTHZ_ANNOTATION_CLASSES.length);
		for (int index = 0; index < AUTHZ_ANNOTATION_CLASSES.length; index++) {
			authzHandlers.add(null);
		}

		// 逐个扫描注解，若是相应的注解则在相应的位置赋值。
		scanAnnotation(authzHandlers, controllerAnnotations);
		// 逐个扫描注解，若是相应的注解则在相应的位置赋值。函数的注解优先级高于Controller
		scanAnnotation(authzHandlers, methodAnnotations);

		// 去除空值
		List<AuthzHandler> finalAuthzHandlers = new ArrayList<AuthzHandler>();
		for (AuthzHandler a : authzHandlers) {
			if (a != null) {
				finalAuthzHandlers.add(a);
			}
		}
		authzHandlers = null;
		// 存在多个，则构建组合AuthzHandler
		if (finalAuthzHandlers.size() > 1) {
			return new CompositeAuthzHandler(finalAuthzHandlers);
		}
		// 一个的话直接返回
		return finalAuthzHandlers.get(0);
	}

	/**
	 * 逐个扫描注解，若是相应的注解则在相应的位置赋值。 注解的处理是有顺序的，依次为RequiresRoles，RequiresPermissions，
	 * RequiresAuthentication，RequiresUser，RequiresGuest
	 * 
	 * @param authzArray
	 * @param annotations
	 */
	private void scanAnnotation(List<AuthzHandler> authzArray,
			List<Annotation> annotations) {
		if (null == annotations || 0 == annotations.size()) {
			return;
		}
		for (Annotation a : annotations) {
			if (a instanceof RequiresRoles) {
				authzArray.set(0, new RoleAuthzHandler(a));
			} else if (a instanceof RequiresPermissions) {
				authzArray.set(1, new PermissionAuthzHandler(a));
			} else if (a instanceof RequiresAuthentication) {
				authzArray.set(2, AuthenticatedAuthzHandler.me());
			} else if (a instanceof RequiresUser) {
				authzArray.set(3, UserAuthzHandler.me());
			} else if (a instanceof RequiresGuest) {
				authzArray.set(4, GuestAuthzHandler.me());
			}
		}
	}

	/**
	 * 构建actionkey，参考ActionMapping中的实现。
	 * 
	 * @param controllerClass
	 * @param method
	 * @param controllerKey
	 * @return
	 */
	private String createActionKey(Class<? extends Controller> controllerClass,
			Method method, String controllerKey) {
		String methodName = method.getName();
		String actionKey = "";

		ActionKey ak = method.getAnnotation(ActionKey.class);
		if (ak != null) {
			actionKey = ak.value().trim();
			if ("".equals(actionKey))
				throw new IllegalArgumentException(controllerClass.getName()
						+ "." + methodName
						+ "(): The argument of ActionKey can not be blank.");
			if (!actionKey.startsWith(SLASH))
				actionKey = SLASH + actionKey;
		} else if (methodName.equals("index")) {
			actionKey = controllerKey;
		} else {
			actionKey = controllerKey.equals(SLASH) ? SLASH + methodName
					: controllerKey + SLASH + methodName;
		}
		return actionKey;
	}

	/**
	 * 返回该方法的所有访问控制注解
	 * 
	 * @param method
	 * @return
	 */
	private List<Annotation> getAuthzAnnotations(Method method) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
			Annotation a = method.getAnnotation(annClass);
			if (a != null) {
				annotations.add(a);
			}
		}
		return annotations;
	}

	/**
	 * 返回该Controller的所有访问控制注解
	 * 
	 * @param method
	 * @return
	 */
	private List<Annotation> getAuthzAnnotations(
			Class<? extends Controller> targetClass) {
		List<Annotation> annotations = new ArrayList<Annotation>();
		for (Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES) {
			Annotation a = targetClass.getAnnotation(annClass);
			if (a != null) {
				annotations.add(a);
			}
		}
		return annotations;
	}

	/**
	 * 该方法上是否有ClearShiro注解
	 * 
	 * @param method
	 * @return
	 */
	private boolean isClearShiroAnnotationPresent(Method method) {
		Annotation a = method.getAnnotation(ClearShiro.class);
		if (a != null) {
			return true;
		}
		return false;
	}

	/**
	 * 登录成功连接
	 * 
	 * @param successUrl
	 */
	public final void setSuccessUrl(String successUrl) {
		this.successUrl = successUrl;
	}

	/**
	 * 登录连接
	 * 
	 * @param loginUrl
	 */
	public final void setLoginUrl(String loginUrl) {
		this.loginUrl = loginUrl;
	}

	/**
	 * 未授权连接
	 * 
	 * @param unauthorizedUrl
	 */
	public final void setUnauthorizedUrl(String unauthorizedUrl) {
		this.unauthorizedUrl = unauthorizedUrl;
	}

	/**
	 * 扩展名，比如.action／.do
	 * 
	 * @param extName
	 */
	public final void setExtName(String extName) {
		this.extName = extName;
	}
}
