package online.filter.deploy.struts;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Objects;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import online.filter.deploy.S2HotDeployClassLoader;
import online.filter.deploy.S2HotDeployUtil;

import org.apache.commons.chain2.config.ConfigParser;
import org.apache.commons.chain2.config.xml.XmlConfigParser;
import org.apache.commons.chain2.impl.CatalogFactoryBase;
import org.apache.commons.digester3.Digester;
import org.apache.logging.log4j.LogManager;
import org.apache.struts.Globals;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.RequestProcessor;
import org.apache.struts.config.ConfigRuleSet;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.config.ModuleConfigFactory;
import org.xml.sax.SAXException;

/**
 * HotDeployクラス
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public final class S2HotDeployDevelopFilter implements Filter {

	/** chainconfigファイル */
	private URL[] config = null;
	/** strutsconfigファイル */
	private URL[] struts = null;
	/** サーブレットコンテキスト */
	private ServletContext sc = null;

	/**
	 * @see javax.servlet.Filter#destroy()
	 */
	@Override
	public void destroy() {
		return;
	}

	/**
	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
	 * javax.servlet.ServletResponse, javax.servlet.FilterChain)
	 */
	@Override
	public void doFilter(final ServletRequest request, final ServletResponse response,
					final FilterChain chain) throws IOException, ServletException {
		String clazz = S2HotDeployDevelopFilter.class.getName();

		ClassLoader org = Thread.currentThread().getContextClassLoader();
		try {
			if (request.getAttribute(clazz) == null) {
				ClassLoader cl = getClassLoader(org);
				try {
					Thread.currentThread().setContextClassLoader(cl);

					initConfig();
					initStrutsConfig();

					request.setAttribute(clazz, cl);
					chain.doFilter(request, response);

				} finally {
					request.removeAttribute(clazz);
					CatalogFactoryBase.clear();
				}
			} else {
				ClassLoader cl = ClassLoader.class.cast(request.getAttribute(clazz));
				Thread.currentThread().setContextClassLoader(cl);

				chain.doFilter(request, response);
			}
		} finally {
			Thread.currentThread().setContextClassLoader(org);
		}
	}

	/**
	 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(final FilterConfig filterConfig) throws ServletException {
		this.sc = filterConfig.getServletContext();

		S2HotDeployUtil.setClassPath(filterConfig.getServletContext());
	}

	/**
	 * アクションサーブレット取得
	 *
	 * @return アクションサーブレット
	 */
	private ActionServlet getActionServlet() {
		return ActionServlet.class.cast(this.sc.getAttribute(Globals.ACTION_SERVLET_KEY));
	}

	/**
	 * クラスローダ取得
	 *
	 * @param org 親クラスローダ
	 * @return クラスローダ
	 */
	private ClassLoader getClassLoader(final ClassLoader org) {
		return new S2HotDeployClassLoader(org, null, null);
	}

	/**
	 * 初期設定
	 *
	 * @throws ServletException サーブレット例外
	 */
	private void initConfig() throws ServletException {
		if (this.config == null) {
			String cfg = getActionServlet().getInitParameter("chainConfig");
			this.config = toUrlArray(cfg);
		}

		ConfigParser cp = new XmlConfigParser();
		for (final URL url : this.config) {
			cp.parse(url);
		}
	}

	/**
	 * struts-config初期化
	 *
	 * @throws ServletException サーブレット例外
	 */
	private void initStrutsConfig() throws ServletException {
		ModuleConfig cfg = initModuleConfig();
		this.sc.setAttribute(Globals.MODULE_KEY, cfg);
		RequestProcessor rp = RequestProcessor.class.cast(
						this.sc.getAttribute(Globals.REQUEST_PROCESSOR_KEY + cfg.getPrefix()));
		if (rp != null) {
			rp.init(ActionServlet.class.cast(
							this.sc.getAttribute(Globals.ACTION_SERVLET_KEY)), cfg);
		}
	}

	/**
	 * 初期設定
	 *
	 * @return モジュール設定
	 * @throws ServletException サーブレット例外
	 */
	private ModuleConfig initModuleConfig() throws ServletException {
		if (this.struts == null) {
			String cfg = getActionServlet().getInitParameter("config");
			this.struts = toUrlArray(cfg);
		}

		ModuleConfig mc = null;
		if (0 < this.struts.length) {
			mc = ModuleConfigFactory.createFactory().createModuleConfig("");
			try {
				Digester dgt = initDigester();
				for (final URL url : this.struts) {
					dgt.push(mc);
					dgt.parse(url);
				}
			} catch (final IOException | SAXException ex) {
				LogManager.getLogger().error(ex.getMessage(), ex);
				throw new ServletException(ex);
			}
		}
		return mc;
	}

	/**
	 * URL配列化
	 *
	 * @param cfg パラメタ文字列
	 * @return URL配列
	 * @throws ServletException サーブレット例外
	 */
	private URL[] toUrlArray(final String cfg) throws ServletException {
		if (!Objects.toString(cfg, "").isEmpty()) {
			try {
				String[] cfgs = cfg.split(",");
				URL[] url = new URL[cfgs.length];
				for (int i = 0; i < cfgs.length; i++) {
					url[i] = this.sc.getResource(cfgs[i]);
				}
				return url;
			} catch (final MalformedURLException ex) {
				LogManager.getLogger().error(ex.getMessage(), ex);
				throw new ServletException(ex);
			}
		}
		return new URL[0];
	}

	/**
	 * Digester初期化
	 *
	 * @return Digester
	 */
	private Digester initDigester() {
		Digester dgt = new Digester();
		dgt.setNamespaceAware(true);
		dgt.setValidating(true);
		dgt.setUseContextClassLoader(true);
		dgt.addRuleSet(new ConfigRuleSet());

		URL url = this.getClass().getResource("/org/apache/struts/resources/struts-config_1_3.dtd");
		if (url != null) {
			dgt.register("-//Apache Software Foundation//DTD Struts Configuration 1.3//EN",
							url.toString());
		}

		return dgt;
	}
}
