old
Brought to you by:
yagupoli
package yl.yljwl; import yl.ylcommons.YLGenericPair; import yl.ylcommons.YLMsg; import yl.ylcommons.YLUtils; import yl.ylwebcommons.YLWebUtils; import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.http.*; import java.io.IOException; import java.math.BigDecimal; /** * Created by yl on 22/11/17. */ @MultipartConfig public class JWLBootstrapServlet extends HttpServlet { @Override public void init() throws ServletException { try { getApp(); } catch(Exception e) { YLUtils.<ServletException>reThrow(e, ServletException.class); } getServletContext().addListener(new HttpSessionListener() { private JWLAppContext appContext; @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { final HttpSession session = httpSessionEvent.getSession(); final int maxInactiveInterval = session.getMaxInactiveInterval(); session.setMaxInactiveInterval(-1); synchronized(session) { setAppContext(session, maxInactiveInterval, appContext); httpSessionEvent.getSession().notifyAll(); } } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { //appContext = getAppContext(httpSessionEvent.getSession()); } }); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); process(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); process(req, resp); } private void process(HttpServletRequest req, HttpServletResponse resp) { JWLAppContext appContext; YLGenericPair<String, String> pagePath; JWLApp app = null; try { app = getApp(); } catch(Exception ignore) { //cannot throw - already checked in init method } final HttpSession session = req.getSession(); boolean redirect = false; JWLPageContext pageContext; JWLPageResolver pageResolver; synchronized(session) { if(session.isNew()) { appContext = getAppContext(session); if(appContext == null) { YLUtils.wait(session, -1); } } appContext = getAppContext(session); //} app.setAppContext(appContext); pagePath = getPagePath(req); if(appContext.isMultiTabApp()) { if(YLUtils.compare(pagePath.getValue1(), "*") == 0) { pagePath.setValue1(appContext.getTabScopeBean(null).getValue1()); redirect = true; } else { pagePath.setValue1(appContext.getTabScopeBean(pagePath.getValue1()).getValue1()); } } else { redirect = YLUtils.compare(pagePath.getValue1(), "*") != 0; pagePath.setValue1("*"); } app.setTabId(pagePath.getValue1()); pageContext = new JWLPageContext(req, resp, pagePath.getValue1()); pageResolver = app.getPageResolver(); } synchronized(app.getTabScopeBean()) { try { if(!appContext.isInitialized()) { app.onInit(); } final Object exp = req.getAttribute(JWLAbstractErrorPage.ERROR_PAGE_PARAMETER_NAME_EXCEPTION); final String[] exp1 = req.getParameterMap().get(JWLAbstractErrorPage.ERROR_PAGE_PARAMETER_NAME_EXCEPTION); if(exp != null) { JWLAbstractPage.sendError(app, pageContext, (Exception) exp, req, resp); } else if(!YLUtils.isEmpty(exp1)) { JWLAbstractPage.sendError(app, pageContext, new JWLExceptionAjax(Integer.parseInt(exp1[0]), exp1[1]), req, resp); } else if(JWLAbstractErrorPage.isException(app.getTabScopeBean())) { JWLAbstractPage.sendError(app, pageContext, null, req, resp); } else { if(pagePath.getValue2() == null) { final JWLPageType pageType = pageResolver.getHomePage(app); pageType.setUrlParameters(pageContext.getUrlQueryParams()); pageResolver.resolveClass2Path(app, pageType); YLWebUtils.sendRedirect(resp, JWLAbstractPage.getPageURL(pageType, pageContext)); return; } JWLPageType pageType = pageResolver.getPageType(app); pageType.setUrlParameters(pageContext.getUrlQueryParams()); pageType.setPath(pagePath.getValue2()); pageType = pageResolver.resolvePath2Class(app, pageType); if(pageType.getType() == null) { YLWebUtils.sendRedirect(resp, JWLAbstractPage.getPageURL(pageType, pageContext)); return; } if(redirect) { pageResolver.resolveClass2Path(app, pageType); YLWebUtils.sendRedirect(resp, JWLAbstractPage.getPageURL(pageType, pageContext)); return; } final JWLAbstractPage page = pageType.createPage(); page.setApp(app); page.setPageContext(pageContext); req.setAttribute(JWLConstants.getSysName(JWLAbstractPage.class, "curPage"), page); page.process(req, resp); } } catch(Exception e) { log(new YLMsg(YLMsg.MsgType.ERR, getClass(), "process", e).toString()); e.printStackTrace(); if(resp.getStatus() != HttpServletResponse.SC_FOUND) { JWLAbstractPage.sendError(app, pageContext, e, req, resp); } } } } private JWLAppContext getAppContext(HttpSession session) { return (JWLAppContext) session.getAttribute(JWLConstants.getSysName(JWLAppContext.class, "appContext")); } private void setAppContext(HttpSession session, int maxInactiveInterval, JWLAppContext appContext) { if(appContext == null) { final String sMultiTabApp = getServletConfig().getInitParameter(JWLConstants.JWLBootstrapServletInitParam_MULTI_TAB_APP); boolean bMultiTabApp = false; if(!YLUtils.isEmpty(sMultiTabApp) && YLUtils.compare(Boolean.TRUE.toString().toLowerCase(), sMultiTabApp.toLowerCase()) == 0) { bMultiTabApp = true; } appContext = new JWLAppContext(bMultiTabApp, maxInactiveInterval); appContext.setServletContext(getServletContext()); String s = getServletConfig().getInitParameter(JWLConstants.JWLBootstrapServletInitParam_AJAX_PROGRESS_INDICATOR_CSS_CLASS); appContext.setAjaxProgressIndicatorCssClass(YLUtils.isEmpty(s) ? "JWLAjaxProgressIndicator" : s); final BigDecimal n = YLUtils.parseNumber(getServletConfig().getInitParameter(JWLConstants.JWLBootstrapServletInitParam_AJAX_TIMEOUT_IN_MILLISECONDS)); if(n == null || n.intValue() <= 0) { appContext.setAjaxTimeoutInMilliseconds(Integer.MAX_VALUE); } else { appContext.setAjaxTimeoutInMilliseconds(n.intValue()); } } final JWLAppContext ac = appContext; appContext.startGC(() -> { JWLApp app = null; try { app = getApp(); } catch(Exception ignore) { } app.setAppContext(ac); ac.cleanUp(app); }); session.setAttribute(JWLConstants.getSysName(JWLAppContext.class, "appContext"), appContext); } private JWLApp getApp() throws Exception { final String appClass = getServletConfig().getInitParameter(JWLConstants.JWLBootstrapServletInitParam_APP_CLASS); final Class clazz = Class.forName(appClass); if(!YLUtils.isRelated(JWLApp.class, clazz)) { throw new JWLException(YLUtils.formatMsg("class {} should extend class {}", YLUtils.fqn(clazz), YLUtils.fqn(JWLApp.class))); } return (JWLApp) YLUtils.newInstance(clazz); } static YLGenericPair<String, String> getPagePath(HttpServletRequest req) { if(!req.getServletPath().endsWith(JWLConstants.JWLBootstrapServlet_URL_PATTERN.substring(1)) || YLUtils.compare(req.getServletPath(), "/" + JWLConstants.JWLBootstrapServlet_URL_PATTERN.substring(1)) == 0) { return new YLGenericPair<>(); } final int ndx = req.getServletPath().lastIndexOf("/"); String tabId = req.getServletPath().substring(ndx + 1); tabId = tabId.substring(0, tabId.indexOf(JWLConstants.JWLBootstrapServlet_URL_PATTERN.substring(1))); return new YLGenericPair<>(tabId, ndx == 0 ? null : req.getServletPath().substring(1, ndx)); } } package yl.yljwl; import yl.ylcommons.YLGenericBean; import yl.ylcommons.YLUtils; import java.net.URL; import java.util.Map; /** * Created by yl on Nov 22 2017 */ public abstract class JWLApp { private JWLAppContext appContext; private String tabId; protected void onSessionDestroyed() throws JWLException { } protected void onInit() throws JWLException { } public int getAjaxTimeoutInMilliseconds() { return appContext.getAjaxTimeoutInMilliseconds(); } public String getAjaxProgressIndicatorCssClass() { return appContext.getAjaxProgressIndicatorCssClass(); } public abstract JWLPageResolver getPageResolver(); public URL getResourceURL(String path) throws JWLException { final String p = path.startsWith("/") ? path : "/" + path; try { URL url = appContext.getServletContext().getResource(p); if(url == null) { url = YLUtils.getURL(path, Thread.currentThread().getContextClassLoader()); } return url; } catch(Exception e) { YLUtils.<JWLException>reThrow(e, JWLException.class); return null; } } public URL getResourceURL(Class parent, String name) throws JWLException { String s = parent.getPackage().getName().replace('.', '/'); if(!YLUtils.isEmpty(s)) { s += '/'; } s += name; return getResourceURL(s); } void setAppContext(JWLAppContext appContext) { this.appContext = appContext; } void setTabId(String tabId) { this.tabId = tabId; } public YLGenericBean getAppScopeBean() { return appContext.getAppScopeBean(); } public YLGenericBean getTabScopeBean() { return appContext.getTabScopeBean(tabId).getValue2(); } YLGenericBean getSysScopeBean() { return appContext.getSysScopeBean(tabId); } public Map<String, String> getContextParams() { return appContext.getContextParams(); } public Object lookup(String name) throws JWLException { return appContext.lookup(name); } } package yl.yljwl; import yl.ylcommons.*; import javax.naming.InitialContext; import javax.servlet.ServletContext; import java.io.IOException; import java.io.Serializable; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Created by yl on Nov 22 2017 */ class JWLAppContext implements Serializable { private Map<String, String> contextParams = new HashMap<>(); private YLGenericBean appScopeBean = new YLGenericBeanImpl(); private Map<String, YLGenericBean> tabScopeBeans = new HashMap<>(); private boolean multiTabApp; private transient ScheduledThreadPoolExecutor gc; private boolean initialized; private transient Lock lock = new ReentrantLock(); private transient ServletContext servletContext; private boolean gcStarted; private int maxInactiveInterval; private String ajaxProgressIndicatorCssClass; private int ajaxTimeoutInMilliseconds; JWLAppContext(boolean multiTabApp, int maxInactiveInterval) { this.multiTabApp = multiTabApp; this.maxInactiveInterval = maxInactiveInterval; createGC(); } void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; for(Enumeration<String> en = this.servletContext.getInitParameterNames(); en.hasMoreElements(); ) { final String name = en.nextElement(); contextParams.put(name, servletContext.getInitParameter(name)); } } ServletContext getServletContext() { return servletContext; } boolean isMultiTabApp() { return multiTabApp; } YLGenericBean getAppScopeBean() { return appScopeBean; } String getAjaxProgressIndicatorCssClass() { return ajaxProgressIndicatorCssClass; } void setAjaxProgressIndicatorCssClass(String ajaxProgressIndicatorCssClass) { this.ajaxProgressIndicatorCssClass = ajaxProgressIndicatorCssClass; } int getAjaxTimeoutInMilliseconds() { return ajaxTimeoutInMilliseconds; } void setAjaxTimeoutInMilliseconds(int ajaxTimeoutInMilliseconds) { this.ajaxTimeoutInMilliseconds = ajaxTimeoutInMilliseconds; } boolean isInitialized() { final boolean ret = initialized; initialized = true; return ret; } YLGenericPair<String, YLGenericBean> getTabScopeBean(String tabId) { final YLGenericPair<String, YLGenericBean> ret = new YLGenericPair<>(); try { lock.lock(); if(!multiTabApp) { ret.setValue2(getAppScopeBean()); } else if(tabId == null) { int id = 0; final YLGenericPair<Boolean, Integer> v = appScopeBean.get(JWLConstants.getSysName(JWLAppContext.class, "tabId")); if(!v.getValue1() || v.getValue2() == null) { id += 1; } else { id = v.getValue2() + 1; } appScopeBean.set(JWLConstants.getSysName(JWLAppContext.class, "tabId"), id); ret.setValue1(String.valueOf(id)); ret.setValue2(new YLGenericBeanImpl()); tabScopeBeans.put(ret.getValue1(), ret.getValue2()); } else { ret.setValue1(tabId); ret.setValue2(tabScopeBeans.get(ret.getValue1())); if(ret.getValue2() == null) { ret.setValue2(new YLGenericBeanImpl()); tabScopeBeans.put(ret.getValue1(), ret.getValue2()); } } } finally { ret.getValue2().set(JWLConstants.getSysName(JWLAppContext.class, "lastAccessedTime"), Instant.now()); lock.unlock(); } return ret; } YLGenericBean getSysScopeBean(String tabId) { final YLGenericBean tabScopeBean = getTabScopeBean(tabId).getValue2(); final YLGenericPair<Boolean, YLGenericBean> ret = tabScopeBean.get(JWLConstants.getSysName(JWLAppContext.class, "sysScopeBean")); if(!ret.getValue1()) { ret.setValue2(new YLGenericBeanImpl()); tabScopeBean.set(JWLConstants.getSysName(JWLAppContext.class, "sysScopeBean"), ret.getValue2()); } return ret.getValue2(); } Map<String, String> getContextParams() { return contextParams; } Object lookup(String name) throws JWLException { try { return InitialContext.doLookup(name); } catch(Exception e) { YLUtils.<JWLException>reThrow(e, JWLException.class); return null; } } private void createGC() { gc = new ScheduledThreadPoolExecutor(1, r -> { final Thread ret = new Thread(r); ret.setDaemon(true); return ret; }); } void startGC(Runnable r) { if(!gcStarted) { gcStarted = true; gc.scheduleWithFixedDelay(r, maxInactiveInterval, maxInactiveInterval, TimeUnit.SECONDS); } } void cleanUp(JWLApp app) { try { lock.lock(); final Instant now = Instant.now(); if(isMultiTabApp()) { for(Iterator<Map.Entry<String, YLGenericBean>> it = tabScopeBeans.entrySet().iterator(); it.hasNext(); ) { final Map.Entry<String, YLGenericBean> en = it.next(); final Instant lastAccessedTime = (Instant) en.getValue().get(JWLConstants.getSysName(JWLAppContext.class, "lastAccessedTime")).getValue2(); if(lastAccessedTime != null && lastAccessedTime.until(now, ChronoUnit.SECONDS) >= maxInactiveInterval) { app.setTabId(en.getKey()); try { app.onSessionDestroyed(); it.remove(); } catch(JWLException e) { System.err.print(new YLMsg(YLMsg.MsgType.ERR, app.getClass(), "onSessionDestroyed", e).toString()); } } } } else { final Instant lastAccessedTime = (Instant) getAppScopeBean().get(JWLConstants.getSysName(JWLAppContext.class, "lastAccessedTime")).getValue2(); if(lastAccessedTime != null && lastAccessedTime.until(now, ChronoUnit.SECONDS) >= maxInactiveInterval) { app.setTabId("*"); try { app.onSessionDestroyed(); appScopeBean = new YLGenericBeanImpl(); } catch(JWLException e) { System.err.print(new YLMsg(YLMsg.MsgType.ERR, app.getClass(), "onSessionDestroyed", e).toString()); } } } } finally { lock.unlock(); } } private void writeObject(java.io.ObjectOutputStream out) throws ClassNotFoundException, IOException { YLUtils.serializeObject(this, out); } private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, IOException { final JWLAppContext o = YLUtils.deserializeObject(getClass(), in); contextParams = new HashMap<>(o.contextParams); appScopeBean = o.appScopeBean; tabScopeBeans = o.tabScopeBeans; multiTabApp = o.multiTabApp; initialized = o.initialized; gcStarted = o.gcStarted; maxInactiveInterval = o.maxInactiveInterval; ajaxProgressIndicatorCssClass = o.ajaxProgressIndicatorCssClass; ajaxTimeoutInMilliseconds = o.ajaxTimeoutInMilliseconds; createGC(); lock = new ReentrantLock(); } }