package com.servoy.j2db.server.ngclient; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.json.JSONObject; import org.sablo.WebEntry; import org.sablo.specification.NGPackageSpecification; import org.sablo.specification.WebComponentSpecProvider; import org.sablo.specification.WebLayoutSpecification; import org.sablo.websocket.IWebsocketSessionFactory; import org.sablo.websocket.WebsocketSessionManager; import com.servoy.j2db.AbstractActiveSolutionHandler; import com.servoy.j2db.FlattenedSolution; import com.servoy.j2db.MessagesResourceBundle; import com.servoy.j2db.persistence.Form; import com.servoy.j2db.persistence.IRepository; import com.servoy.j2db.persistence.Solution; import com.servoy.j2db.persistence.SolutionMetaData; import com.servoy.j2db.server.ngclient.property.types.Types; import com.servoy.j2db.server.ngclient.template.FormLayoutGenerator; import com.servoy.j2db.server.ngclient.template.FormLayoutStructureGenerator; import com.servoy.j2db.server.ngclient.template.FormTemplateGenerator; import com.servoy.j2db.server.shared.ApplicationServerRegistry; import com.servoy.j2db.server.shared.IApplicationServer; import com.servoy.j2db.util.Debug; import com.servoy.j2db.util.HTTPUtils; import com.servoy.j2db.util.Utils; /** * Filter and entrypoint for webapp * @author jcompagner */ @WebFilter(urlPatterns = { "/solutions/*", "/spec/*" }) @SuppressWarnings("nls") public class NGClientEntryFilter extends WebEntry { public static final String SOLUTIONS_PATH = "solutions/"; public static final String FORMS_PATH = "forms/"; public static final String ANGULAR_JS = "js/angular_1.5.0.js"; public static final String BOOTSTRAP_CSS = "css/bootstrap/css/bootstrap.css"; private static final String[] INDEX_3TH_PARTY_CSS = { // "js/bootstrap-window/css/bootstrap-window.css" }; private static final String[] INDEX_3TH_PARTY_JS = { // "js/jquery-1.11.1.js", // "js/jquery.maskedinput.js", // ANGULAR_JS, // "js/angular-sanitize_1.5.0.js", // "js/angular-translate-2.8.1.js", // "js/angular-webstorage.js", // "js/angularui/ui-bootstrap-tpls-0.12.0.js", // "js/numeral.js", // "js/languages.js", // "js/angular-file-upload/dist/angular-file-upload.min.js", // "js/bootstrap-window/js/Window.js", // "js/bootstrap-window/js/WindowManager.js", // "js/bindonce.js" }; private static final String[] INDEX_SABLO_JS = { // "sablo/lib/reconnecting-websocket.js", // "sablo/js/websocket.js", // "sablo/js/sablo_app.js" }; private static final String[] INDEX_SERVOY_JS = { // "js/servoy.js", // "js/servoyWindowManager.js", // "js/servoyformat.js", // "js/servoytooltip.js", // "js/fileupload.js", // "js/servoy-components.js", // "js/servoy_app.js" }; private String[] locations; private String[] services; private FilterConfig filterConfig; public NGClientEntryFilter() { super(WebsocketSessionFactory.CLIENT_ENDPOINT); } @Override public void init(final FilterConfig fc) throws ServletException { this.filterConfig = fc; //when started in developer - init is done in the ResourceProvider filter if (!ApplicationServerRegistry.get().isDeveloperStartup()) { InputStream is = null; try { is = fc.getServletContext().getResourceAsStream("/WEB-INF/components.properties"); Properties properties = new Properties(); properties.load(is); locations = properties.getProperty("locations").split(";"); } catch (Exception e) { Debug.error("Exception during init components.properties reading", e); } finally { Utils.closeInputStream(is); } try { is = fc.getServletContext().getResourceAsStream("/WEB-INF/services.properties"); Properties properties = new Properties(); properties.load(is); services = properties.getProperty("locations").split(";"); } catch (Exception e) { Debug.error("Exception during init services.properties reading", e); } finally { Utils.closeInputStream(is); } Types.getTypesInstance().registerTypes(); super.init(fc); } } @Override public String[] getWebComponentBundleNames() { return locations; } @Override public String[] getServiceBundleNames() { return services; } /** * Get form script references, useful for debugging * @param fs the flattened solution * @return the form script contributions */ private Collection<String> getFormScriptReferences(FlattenedSolution fs) { List<String> formScripts = new ArrayList<>(); if (Boolean.valueOf(System.getProperty("servoy.generateformscripts", "false")).booleanValue()) { Iterator<Form> it = fs.getForms(false); while (it.hasNext()) { Form form = it.next(); Solution sol = (Solution)form.getAncestor(IRepository.SOLUTIONS); formScripts.add("solutions/" + sol.getName() + "/forms/" + form.getName() + ".js"); } } return formScripts; } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest request = (HttpServletRequest)servletRequest; String uri = request.getRequestURI(); if (uri != null && (uri.endsWith(".html") || uri.endsWith(".js"))) { String solutionName = getSolutionNameFromURI(uri); if (solutionName != null) { String clientUUID = request.getParameter("sessionId"); INGClientWebsocketSession wsSession = null; if (clientUUID != null) { wsSession = (INGClientWebsocketSession)WebsocketSessionManager.getSession(WebsocketSessionFactory.CLIENT_ENDPOINT, clientUUID); } FlattenedSolution fs = null; boolean closeFS = false; if (wsSession != null) { fs = wsSession.getClient().getFlattenedSolution(); } if (fs == null) { try { closeFS = true; IApplicationServer as = ApplicationServerRegistry.getService(IApplicationServer.class); SolutionMetaData solutionMetaData = (SolutionMetaData)ApplicationServerRegistry.get().getLocalRepository().getRootObjectMetaData( solutionName, IRepository.SOLUTIONS); if (solutionMetaData == null) { Debug.error("Solution '" + solutionName + "' was not found."); } else { fs = new FlattenedSolution(solutionMetaData, new AbstractActiveSolutionHandler(as) { @Override public IRepository getRepository() { return ApplicationServerRegistry.get().getLocalRepository(); } }); } } catch (Exception e) { Debug.error("error loading solution: " + solutionName + " for clientid: " + clientUUID, e); } } if (fs != null) { try { String formName = getFormNameFromURI(uri); if (formName != null) { Form f = fs.getForm(formName); if (f == null && wsSession != null) f = wsSession.getClient().getFormManager().getPossibleForm(formName); Form form = (f != null ? fs.getFlattenedForm(f) : null); if (form != null) { if (HTTPUtils.checkAndSetUnmodified(((HttpServletRequest)servletRequest), ((HttpServletResponse)servletResponse), fs.getLastModifiedTime())) return; HTTPUtils.setNoCacheHeaders((HttpServletResponse)servletResponse); boolean html = uri.endsWith(".html"); PrintWriter w = servletResponse.getWriter(); if (html && form.isResponsiveLayout()) { ((HttpServletResponse)servletResponse).setContentType("text/html"); FormLayoutStructureGenerator.generateLayout(form, formName, wsSession != null ? new ServoyDataConverterContext(wsSession.getClient()) : new ServoyDataConverterContext(fs), w, Utils.getAsBoolean(request.getParameter("design")), Utils.getAsBoolean(request.getParameter("highlight"))); } else if (uri.endsWith(".html")) { ((HttpServletResponse)servletResponse).setContentType("text/html"); FormLayoutGenerator.generateRecordViewForm(w, form, formName, wsSession != null ? new ServoyDataConverterContext(wsSession.getClient()) : new ServoyDataConverterContext(fs), Utils.getAsBoolean(request.getParameter("design")), Utils.getAsBoolean(request.getParameter("highlight"))); } else if (uri.endsWith(".js")) { ((HttpServletResponse)servletResponse).setContentType("text/" + (html ? "html" : "javascript")); new FormTemplateGenerator( wsSession != null ? new ServoyDataConverterContext(wsSession.getClient()) : new ServoyDataConverterContext(fs), false, Utils.getAsBoolean(request.getParameter("design"))).generate(form, formName, "form_recordview_js.ftl", w); } w.flush(); return; } } else { //prepare for possible index.html lookup Map<String, String> variableSubstitution = new HashMap<String, String>(); variableSubstitution.put("orientation", String.valueOf(fs.getSolution().getTextOrientation())); // push some translations to the client, in case the client cannot connect back JSONObject defaultTranslations = new JSONObject(); defaultTranslations.put("servoy.ngclient.reconnecting", getSolutionDefaultMessage(fs.getSolution(), request.getLocale(), "servoy.ngclient.reconnecting")); variableSubstitution.put("defaultTranslations", defaultTranslations.toString()); List<String> css = new ArrayList<String>(); css.add("css/servoy.css"); List<String> formScripts = new ArrayList<String>(getFormScriptReferences(fs)); for (NGPackageSpecification<WebLayoutSpecification> entry : WebComponentSpecProvider.getInstance().getLayoutSpecifications().values()) { if (entry.getCssClientLibrary() != null) { css.addAll(entry.getCssClientLibrary()); } if (entry.getJsClientLibrary() != null) { formScripts.addAll(entry.getJsClientLibrary()); } } super.doFilter(servletRequest, servletResponse, filterChain, css, formScripts, variableSubstitution); return; } } finally { if (closeFS) fs.close(null); } } } } Debug.log("No solution found for this request, calling the default filter: " + uri); super.doFilter(servletRequest, servletResponse, filterChain, null, null, null); } catch (RuntimeException | Error e) { Debug.error(e); throw e; } } private String getSolutionDefaultMessage(Solution solution, Locale locale, String key) { if (ApplicationServerRegistry.get().isDeveloperStartup()) { // do not cache in the solution, it may change in developer return getSolutionDefaultMessageNotCached(solution.getID(), locale, key); } Map<String, String> solutionDefaultMessages = solution.getRuntimeProperty(Solution.DEFAULT_MESSAGES); if (solutionDefaultMessages == null) { solution.setRuntimeProperty(Solution.DEFAULT_MESSAGES, solutionDefaultMessages = new HashMap<>()); } String value = solutionDefaultMessages.get(key); if (value == null) { value = getSolutionDefaultMessageNotCached(solution.getID(), locale, key); solutionDefaultMessages.put(key, value); } return value; } private String getSolutionDefaultMessageNotCached(int solutionId, Locale locale, String key) { MessagesResourceBundle messagesResourceBundle = new MessagesResourceBundle(null /* application */, locale == null ? Locale.ENGLISH : locale, null /* columnNameFilter */, null /* columnValueFilter */, solutionId); return messagesResourceBundle.getString(key); } /** * Get the form name from url * @param uri * @return the name or null */ private String getFormNameFromURI(String uri) { int formIndex = uri.indexOf(FORMS_PATH); if (formIndex > 0) { String formName = uri.substring(formIndex + FORMS_PATH.length()); formName = formName.replace(".html", ""); formName = formName.replace(".js", ""); return formName; } return null; } /** * Get the solution name from an url * @param uri * @return the name or null */ private String getSolutionNameFromURI(String uri) { int solutionIndex = uri.indexOf(SOLUTIONS_PATH); if (solutionIndex > 0) { return uri.substring(solutionIndex + SOLUTIONS_PATH.length(), uri.indexOf("/", solutionIndex + SOLUTIONS_PATH.length() + 1)); } return null; } @Override protected IWebsocketSessionFactory createSessionFactory() { return new WebsocketSessionFactory(); } @Override protected URL getIndexPageResource(HttpServletRequest request) throws IOException { String uri = request.getRequestURI(); if (uri != null && uri.endsWith("index.html")) { return getClass().getResource("index.html"); } return super.getIndexPageResource(request); } @Override public List<String> filterCSSContributions(List<String> cssContributions) { ArrayList<String> allIndexCSS; NGWroFilter wroFilter = (NGWroFilter)filterConfig.getServletContext().getAttribute(NGWroFilter.WROFILTER); if (wroFilter != null) { allIndexCSS = new ArrayList<String>(); allIndexCSS.add(wroFilter.createCSSGroup("wro/servoy_thirdparty.css", Arrays.asList(INDEX_3TH_PARTY_CSS))); allIndexCSS.add(wroFilter.createCSSGroup("wro/servoy_contributions.css", cssContributions)); } else { allIndexCSS = new ArrayList<String>(Arrays.asList(INDEX_3TH_PARTY_CSS)); allIndexCSS.addAll(cssContributions); } return allIndexCSS; } @Override public List<String> filterJSContributions(List<String> jsContributions) { ArrayList<String> allIndexJS; NGWroFilter wroFilter = (NGWroFilter)filterConfig.getServletContext().getAttribute(NGWroFilter.WROFILTER); if (wroFilter != null) { allIndexJS = new ArrayList<String>(); allIndexJS.add(wroFilter.createJSGroup("wro/servoy_thirdparty.js", Arrays.asList(INDEX_3TH_PARTY_JS))); allIndexJS.addAll(Arrays.asList(INDEX_SABLO_JS)); allIndexJS.add(wroFilter.createJSGroup("wro/servoy_app.js", Arrays.asList(INDEX_SERVOY_JS))); allIndexJS.add(wroFilter.createJSGroup("wro/servoy_contributions.js", jsContributions)); } else { allIndexJS = new ArrayList<String>(Arrays.asList(INDEX_3TH_PARTY_JS)); allIndexJS.addAll(Arrays.asList(INDEX_SABLO_JS)); allIndexJS.addAll(Arrays.asList(INDEX_SERVOY_JS)); allIndexJS.addAll(jsContributions); } if (ApplicationServerRegistry.get().isDeveloperStartup()) allIndexJS.add("js/debug.js"); return allIndexJS; } }