package project.filter.develop.struts;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.PrivilegedAction;
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 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;

import online.filter.develop.S2HotDeployClassLoader;

/**
 * HotDeployフィルタ
 *
 * @author Tadashi Nakayama
 * @version 1.0.0
 */
public class S2HotDeployDevelopFilter implements Filter, HbS2HotDeploy {

	/** 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#init(javax.servlet.FilterConfig)
	 */
	@Override
	public void init(final FilterConfig filterConfig) throws ServletException {
		this.sc = filterConfig.getServletContext();

		setClassPath(filterConfig.getServletContext());
	}

	/**
	 * @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 {
		doChain(request, response, chain,
			cl -> {
				try {
					Thread.currentThread().setContextClassLoader(cl);
					initConfig();
					initStrutsConfig();
					chain.doFilter(request, response);
				} finally {
					CatalogFactoryBase.clear();
				}
			}
		);
	}

	/**
	 * @see online.filter.develop.S2HotDeploy#getClassLoaderSupplier(java.lang.ClassLoader)
	 */
	@Override
	public PrivilegedAction<ClassLoader> getClassLoaderSupplier(final ClassLoader org) {
		return () -> new S2HotDeployClassLoader(org, null, null);
	}

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

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

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

	/**
	 * struts-config初期化
	 *
	 * @throws ServletException サーブレット例外
	 */
	private void initStrutsConfig() throws ServletException {
		final ModuleConfig cfg = initModuleConfig();
		this.sc.setAttribute(Globals.MODULE_KEY, cfg);
		final 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) {
			final String cfg = getActionServlet().getInitParameter("config");
			this.struts = toUrlArray(cfg);
		}

		ModuleConfig mc = null;
		if (0 < this.struts.length) {
			mc = ModuleConfigFactory.createFactory().createModuleConfig("");
			try {
				final 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 {
				final String[] cfgs = cfg.split(",");
				final 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() {
		final Digester dgt = new Digester();
		dgt.setNamespaceAware(true);
		dgt.setValidating(true);
		dgt.setUseContextClassLoader(true);
		dgt.addRuleSet(new ConfigRuleSet());

		final URL url = S2HotDeployDevelopFilter.class.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;
	}
}
