Java tutorial
/* * JSmart Framework - Java Web Development Framework * Copyright (c) 2015, Jeferson Albino da Silva, All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3.0 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. */ package com.jsmartframework.web.manager; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.jsmartframework.web.util.WebAlert; import com.jsmartframework.web.util.WebUtils; import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringReader; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; import javax.el.ExpressionFactory; import javax.servlet.AsyncContext; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.servlet.jsp.JspApplicationContext; import javax.servlet.jsp.JspFactory; import javax.servlet.jsp.PageContext; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; /** * This class represents the context of the request being currently processed and it allows beans * to get an instance of {@link ServletContext}, {@link HttpSession}, {@link HttpServletRequest} or * {@link HttpServletResponse}. * <br> * This class also include methods to add message to client side, check if request is Ajax request or * retrieve attributes from the request, session or application among other utilities. */ public final class WebContext implements Serializable { private static final long serialVersionUID = -3910553204750683737L; private static final String ERROR_CODE = "error"; private static final JspFactory JSP_FACTORY = JspFactory.getDefaultFactory(); private static final Map<Thread, WebContext> THREADS = new ConcurrentHashMap<>(); private static final Gson GSON = new GsonBuilder() .registerTypeAdapter(LocalDateTime.class, new JsonConverter.LocalDateTimeTypeConverter()) .registerTypeAdapter(DateTime.class, new JsonConverter.DateTimeTypeConverter()) .registerTypeAdapter(Date.class, new JsonConverter.DateTypeConverter()).create(); private static Servlet smartServlet; private static JspApplicationContext jspContext; private HttpServletRequest request; private String bodyContent; private HttpServletResponse response; private boolean responseWritten; private String redirectTo; private boolean redirectToWindow; private boolean invalidate; private PageContext pageContext; private Map<String, List<WebAlert>> alerts = new LinkedHashMap<>(); private Map<String, Object> mappedValues = new ConcurrentHashMap<>(); private Map<String, String> queryParams; private WebContext(HttpServletRequest request, HttpServletResponse response) { this.request = request; this.response = response; } static final void setServlet(Servlet servlet) { smartServlet = servlet; jspContext = JSP_FACTORY.getJspApplicationContext(servlet.getServletConfig().getServletContext()); } private static final WebContext getCurrentInstance() { return THREADS.get(Thread.currentThread()); } static final void initCurrentInstance(HttpServletRequest request, HttpServletResponse response) { THREADS.put(Thread.currentThread(), new WebContext(request, response)); } static final void closeCurrentInstance() { THREADS.remove(Thread.currentThread()).close(); } private void close() { if (invalidate) { request.getSession().invalidate(); } invalidate = false; request = null; bodyContent = null; response = null; responseWritten = false; redirectTo = null; alerts.clear(); alerts = null; mappedValues.clear(); mappedValues = null; JSP_FACTORY.releasePageContext(pageContext); pageContext = null; } static PageContext getPageContext() { WebContext context = getCurrentInstance(); return context != null ? context.getPage() : null; } private PageContext getPage() { if (pageContext == null) { pageContext = JSP_FACTORY.getPageContext(smartServlet, request, response, null, true, 8192, true); } return pageContext; } static ExpressionFactory getExpressionFactory() { return jspContext.getExpressionFactory(); } private static void setResponseAsError(String errorCode) { // Case response error is called we need to call onError on client side HttpServletResponse response = getResponse(); if (response != null) { response.setHeader("Error-Ajax", StringUtils.isNotBlank(errorCode) ? errorCode : ERROR_CODE); } } /** * Returns the current {@link ServletContext} instance associated to the request * being processed. * * @return a instance of {@link ServletContext}. */ public static ServletContext getApplication() { return smartServlet.getServletConfig().getServletContext(); } /** * Returns the current {@link HttpSession} instance associated to the request being * processed. * * @return a instance of {@link HttpSession}. */ public static HttpSession getSession() { WebContext context = getCurrentInstance(); return context != null ? context.request.getSession() : null; } /** * Returns the current {@link HttpServletRequest} instance associated to the request being * processed. * * @return a instance of {@link HttpServletRequest}. */ public static HttpServletRequest getRequest() { WebContext context = getCurrentInstance(); return context != null ? context.request : null; } /** * Returns the current query parameters associated to the request being processed. * * @return Map containing name and values of URL query parameters */ public static Map<String, String> getQueryParams() { WebContext context = getCurrentInstance(); if (context == null) { return null; } if (context.queryParams == null) { context.queryParams = new ConcurrentHashMap<String, String>(); String queryParam = context.request.getQueryString(); if (StringUtils.isBlank(queryParam)) { return context.queryParams; } for (String param : context.request.getParameterMap().keySet()) { if (queryParam.contains(param + "=")) { context.queryParams.put(param, context.request.getParameter(param)); } } } return context.queryParams; } /** * Returns the current {@link HttpServletResponse} instance associated to the request * being processed. * * @return a instance of {@link HttpServletResponse} */ public static HttpServletResponse getResponse() { WebContext context = getCurrentInstance(); return context != null ? context.response : null; } static String getRedirectTo() { WebContext context = getCurrentInstance(); return context != null ? context.redirectTo : null; } static boolean isRedirectToWindow() { WebContext context = getCurrentInstance(); return context != null ? context.redirectToWindow : false; } /** * Redirect the request to the specified link path after the current request is processed. * <br> * Case this method is called on {@link PostConstruct} annotated method, the redirect is done * after method execution. * * @param path mapped on configuration file {@code webConfig.xml} or general valid URL link. */ public static void redirectTo(String path) { WebContext context = getCurrentInstance(); if (context != null) { context.redirectTo = WebUtils.decodePath(path); } } /** * Redirect the request to the specified link path after the current request is processed on a * new window on client browser. * <br> * Case this method is called on {@link PostConstruct} annotated method, the redirect is done * after method execution. * * @param path mapped on configuration file {@code webConfig.xml} or general valid URL link. */ public static void redirectToWindow(String path) { WebContext context = getCurrentInstance(); if (context != null) { context.redirectTo = WebUtils.decodePath(path); context.redirectToWindow = true; } } /** * Calling this method will cause the current {@link HttpSession} to be invalidated after the request * processing is done. It means that the session will be invalidated after request is completed. * <br> * Case there is a need to invalidate the session at the moment of the execution, use {@link HttpSession} * invalidate method instead. */ public static void invalidate() { WebContext context = getCurrentInstance(); if (context != null) { context.invalidate = true; } } /** * Returns the {@link Locale} of the client associated to the request being processed. * * @return {@link Locale} instance. */ public static Locale getLocale() { HttpServletRequest request = getRequest(); return request != null ? request.getLocale() : null; } /** * Returns true if the request being process was triggered by Ajax on client side, * false otherwise. * * @return boolean value indicating if request was done using Ajax. */ public static boolean isAjaxRequest() { HttpServletRequest request = getRequest(); return request != null ? "XMLHttpRequest".equals(request.getHeader("X-Requested-With")) : false; } static List<WebAlert> getAlerts(String id) { WebContext context = getCurrentInstance(); return context != null ? context.alerts.get(id) : null; } /** * Add info alert to be presented on client side after the response is returned. * <br> * This method only take effect if the alert tag is mapped with specified id on JSP page. * <br> * The message is placed on the same position where the {@code alert} is mapped. * * @param id of the alert to receive the message. * @param alert object containing alert details such as title, message, header and icon. */ public static void addAlert(String id, WebAlert alert) { WebContext context = getCurrentInstance(); if (context != null && id != null && alert != null) { List<WebAlert> alerts = context.alerts.get(id); if (alerts == null) { context.alerts.put(id, alerts = new ArrayList<>()); } alerts.add(alert); } } /** * Add info alert to be presented on client side after the response is returned. * <br> * This method only take effect if the alert tag is mapped with specified id on JSP page. * <br> * The message is placed on the same position where the {@code alert} tag is mapped. * * @param id of the alert to receive the message. * @param message to be presented on the client side. */ public static void addInfo(String id, String message) { WebAlert alert = new WebAlert(WebAlert.AlertType.INFO); alert.setMessage(message); addAlert(id, alert); } /** * Add warning alert to be presented on client side after the response is returned. * <br> * This method only take effect if the alert tag is mapped with specified id on JSP page. * <br> * The message is placed on the same position where the {@code alert} tag is mapped. * * @param id of the alert to receive the message. * @param message to be presented on the client side. */ public static void addWarning(String id, String message) { WebAlert alert = new WebAlert(WebAlert.AlertType.WARNING); alert.setMessage(message); addAlert(id, alert); } /** * Add success alert to be presented on client side after the response is returned. * <br> * This method only take effect if the alert tag is mapped with specified id on JSP page. * <br> * The message is placed on the same position where the {@code alert} tag is mapped. * * @param id of the alert to receive the message. * @param message to be presented on the client side. */ public static void addSuccess(String id, String message) { WebAlert alert = new WebAlert(WebAlert.AlertType.SUCCESS); alert.setMessage(message); addAlert(id, alert); } /** * * @param errorCode */ public static void addError(String errorCode) { // Case addError is called we need to call onError on client side setResponseAsError(errorCode); } /** * Add error alert to be presented on client side after the response is returned. * <br> * This method only take effect if the alert tag is mapped with specified id on JSP page. * <br> * The message is placed on the same position where the {@code alert} message tag is mapped. * * @param id of the alert to receive the message. * @param message to be presented on the client side. */ public static void addError(String id, String message) { addError(id, message, ERROR_CODE); } /** * * @param id * @param message * @param errorCode */ public static void addError(String id, String message, String errorCode) { WebAlert alert = new WebAlert(WebAlert.AlertType.DANGER); alert.setMessage(message); addAlert(id, alert); // Case addError is called we need to call onError on client side setResponseAsError(errorCode); } static Object getMappedValue(String name) { WebContext context = getCurrentInstance(); if (context != null) { return context.mappedValues.get(name); } return null; } static Object removeMappedValue(String name) { WebContext context = getCurrentInstance(); if (context != null) { return context.mappedValues.remove(name); } return null; } static void addMappedValue(String name, Object value) { WebContext context = getCurrentInstance(); if (context != null) { context.mappedValues.put(name, value); } } /** * Returns the attribute carried on {@link HttpServletRequest}, {@link HttpSession} or {@link ServletContext} * instances associated with current request being processed. * * @param name name of the attribute. * @return the {@link Object} mapped by attribute name on the current request. */ public static Object getAttribute(String name) { if (name != null) { HttpServletRequest request = getRequest(); if (request != null && request.getAttribute(name) != null) { return request.getAttribute(name); } HttpSession session = getSession(); if (session != null) { synchronized (session) { if (session.getAttribute(name) != null) { return session.getAttribute(name); } } } ServletContext application = getApplication(); if (application.getAttribute(name) != null) { return application.getAttribute(name); } } return null; } /** * Check if attribute is carried on {@link HttpServletRequest}, {@link HttpSession} or {@link ServletContext} * instances associated with current request being processed. * * @param name name of the attribute. * @return true if the attribute is contained in one of the instances {@link HttpServletRequest}, * {@link HttpSession} or {@link ServletContext}, false otherwise. */ public static boolean containsAttribute(String name) { if (name != null) { HttpServletRequest request = getRequest(); if (request != null && request.getAttribute(name) != null) { return true; } HttpSession session = getSession(); if (session != null) { synchronized (session) { if (session.getAttribute(name) != null) { return true; } } } return getApplication().getAttribute(name) != null; } return false; } /** * Given that the current request has ReCaptcha content to be verified use this method * to check if the ReCaptcha input is valid on server side. * * @param secretKey for ReCaptcha based on your domain registered * * @return true if the ReCaptcha input is valid, false otherwise */ public static boolean checkReCaptcha(String secretKey) { String responseField = (String) getMappedValue(ReCaptchaHandler.RESPONSE_V1_FIELD_NAME); if (responseField != null) { return ReCaptchaHandler.checkReCaptchaV1(secretKey, responseField); } responseField = (String) getMappedValue(ReCaptchaHandler.RESPONSE_V2_FIELD_NAME); if (responseField != null) { return ReCaptchaHandler.checkReCaptchaV2(secretKey, responseField); } throw new RuntimeException( "ReCaptcha not found on this submit. Plase make sure the recaptcha tag is included on submitted form"); } /** * Returns the request content as String * * @return request content as String * @throws IOException */ public static String getContentAsString() throws IOException { WebContext context = getCurrentInstance(); if (context == null) { return null; } if (context.bodyContent == null) { String line = null; StringBuffer buffer = new StringBuffer(); BufferedReader reader = context.request.getReader(); while ((line = reader.readLine()) != null) { buffer.append(line); } context.bodyContent = buffer.toString(); } return context.bodyContent; } /** * Get request content from JSON and convert it to class mapping the content. * * @param clazz - Class mapping the request content. * @param <T> - type of class to convert JSON into class. * * @return content from JSON to object * @throws IOException */ public static <T> T getContentFromJson(Class<T> clazz) throws IOException { return getContentFromJson(clazz, GSON); } /** * Get request content from JSON and convert it to class mapping the content. * * @param clazz - Class mapping the request content. * @param gson - Gson converter to convert JSON request into object. * @param <T> - type of class to convert JSON into class. * * @return content from JSON to object * @throws IOException */ public static <T> T getContentFromJson(Class<T> clazz, Gson gson) throws IOException { return gson.fromJson(getContentAsString(), clazz); } /** * Get request content from XML and convert it to class mapping the content. * * @param clazz - Class mapping the request content. * @param <T> - type of class to convert XML into class. * * @return content from XML to object * @throws IOException */ public static <T> T getContentFromXml(Class<T> clazz) throws IOException, JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(clazz); return getContentFromXml(jaxbContext.createUnmarshaller()); } /** * Get request content from XML and convert it to class mapping the content. * * @param unmarshaller - JAXBContext unmarshaller to convert the request content into object. * @param <T> - type of class to convert XML into class. * * @return content from XML to object * @throws IOException */ public static <T> T getContentFromXml(Unmarshaller unmarshaller) throws IOException, JAXBException { StringReader reader = new StringReader(getContentAsString()); return (T) unmarshaller.unmarshal(reader); } static boolean isResponseWritten() { WebContext context = getCurrentInstance(); return context != null ? context.responseWritten : false; } /** * * @throws IOException */ public static void writeResponseError() throws IOException { writeResponseError(ERROR_CODE); } /** * * @param errorCode * @throws IOException */ public static void writeResponseError(int errorCode) throws IOException { writeResponseError(String.valueOf(errorCode)); } /** * * @param errorCode * @throws IOException */ public static void writeResponseError(String errorCode) throws IOException { setResponseAsError(errorCode); WebContext context = getCurrentInstance(); if (context != null) { context.responseWritten = true; } } /** * * @param response * @throws IOException */ public static void writeResponseErrorAsString(String response) throws IOException { writeResponseErrorAsString(ERROR_CODE, response); } /** * * @param errorCode * @param response * @throws IOException */ public static void writeResponseErrorAsString(String errorCode, String response) throws IOException { setResponseAsError(errorCode); writeResponseAsString(response); } /** * Write response directly as String. Note that by using this method the response * as HTML will not be generated and the response will be what you have defined. * * @param response String to write in the response. * @throws IOException */ public static void writeResponseAsString(String response) throws IOException { WebContext context = getCurrentInstance(); if (context != null) { context.responseWritten = true; PrintWriter writer = context.response.getWriter(); writer.write(response); writer.flush(); } } /** * * @param object * @throws IOException */ public static void writeResponseErrorAsJson(Object object) throws IOException { writeResponseErrorAsJson(ERROR_CODE, object); } /** * * @param errorCode * @param object * @throws IOException */ public static void writeResponseErrorAsJson(String errorCode, Object object) throws IOException { writeResponseErrorAsJson(errorCode, object, GSON); } /** * * @param errorCode * @param object * @param gson * @throws IOException */ public static void writeResponseErrorAsJson(String errorCode, Object object, Gson gson) throws IOException { setResponseAsError(errorCode); writeResponseAsJson(object, gson); } /** * Write response directly as JSON from Object. Note that by using this method the response * as HTML will not be generated and the response will be what you have defined. * * @param object Object to convert into JSON to write in the response. * @throws IOException */ public static void writeResponseAsJson(Object object) throws IOException { writeResponseAsJson(object, GSON); } /** * Write response directly as JSON from Object. Note that by using this method the response * as HTML will not be generated and the response will be what you have defined. * * @param object Object to convert into JSON to write in the response. * @param gson Gson instance to be used to convert object into JSON. * @throws IOException */ public static void writeResponseAsJson(Object object, Gson gson) throws IOException { WebContext context = getCurrentInstance(); if (context != null) { context.responseWritten = true; context.response.setContentType("application/json"); PrintWriter writer = context.response.getWriter(); writer.write(gson.toJson(object)); writer.flush(); } } /** * * @param object * @throws IOException * @throws JAXBException */ public static void writeResponseErrorAsXml(Object object) throws IOException, JAXBException { writeResponseErrorAsXml(ERROR_CODE, object); } /** * * @param errorCode * @param object * @throws IOException * @throws JAXBException */ public static void writeResponseErrorAsXml(String errorCode, Object object) throws IOException, JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass()); writeResponseErrorAsXml(errorCode, object, jaxbContext.createMarshaller()); } /** * * @param errorCode * @param object * @param marshaller * @throws IOException * @throws JAXBException */ public static void writeResponseErrorAsXml(String errorCode, Object object, Marshaller marshaller) throws IOException, JAXBException { setResponseAsError(errorCode); writeResponseAsXml(object, marshaller); } /** * Write response directly as XML from Object. Note that by using this method the response * as HTML will not be generated and the response will be what you have defined. * * @param object Object to convert into XML to write in the response. * @throws IOException */ public static void writeResponseAsXml(Object object) throws IOException, JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass()); writeResponseAsXml(object, jaxbContext.createMarshaller()); } /** * Write response directly as XML from Object. Note that by using this method the response * as HTML will not be generated and the response will be what you have defined. * * @param object Object to convert into XML to write in the response. */ public static void writeResponseAsXmlQuietly(Object object) { try { writeResponseAsXml(object); } catch (Exception e) { } } /** * Write response directly as XML from Object. Note that by using this method the response * as HTML will not be generated and the response will be what you have defined. * * @param object Object to convert into XML to write in the response. * @param marshaller JAXBContext marshaller to write object as XML. * @throws IOException */ public static void writeResponseAsXml(Object object, Marshaller marshaller) throws IOException, JAXBException { WebContext context = getCurrentInstance(); if (context != null) { context.responseWritten = true; context.response.setContentType("application/xml"); PrintWriter writer = context.response.getWriter(); marshaller.marshal(object, writer); writer.flush(); } } /** * Write response directly as Event-Stream for Server Sent Events. * * @param asyncContext - Asynchronous Context. * @param event - Name of event to be written on response. * @param data - Content of event ot be written on response. * @throws IOException */ public static void writeResponseAsEventStream(AsyncContext asyncContext, String event, Object data) throws IOException { writeResponseAsEventStream(asyncContext, event, data, null); } /** * Write response directly as Event-Stream for Server Sent Events. * * @param asyncContext - Asynchronous Context. * @param event - Name of event to be written on response. * @param data - Content of event ot be written on response. * @param retry - Time in (milliseconds) for client retry opening connection. * after asynchronous context is closed. * @throws IOException */ public static void writeResponseAsEventStream(AsyncContext asyncContext, String event, Object data, Long retry) throws IOException { if (asyncContext != null && event != null && data != null) { HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse(); response.setContentType("text/event-stream"); PrintWriter printWriter = response.getWriter(); if (retry != null) { printWriter.write("retry:" + retry + "\n"); } printWriter.write("event:" + event + "\n"); printWriter.write("data:" + data + "\n\n"); printWriter.flush(); } } /** * Write response as file stream when you want to provide download functionality. * Note that by using this method the response as HTML will not be generated and * the response will be what you have defined. * * @param file - File to be written on response. * @param bufferSize - Buffer size to write the response. Example 2048 bytes. * @throws IOException */ public static void writeResponseAsFileStream(File file, int bufferSize) throws IOException { WebContext context = getCurrentInstance(); if (context != null && file != null && bufferSize > 0) { context.responseWritten = true; context.response.setHeader("Content-Disposition", "attachment;filename=\"" + file.getName() + "\""); context.response.addHeader("Content-Length", Long.toString(file.length())); context.response.setContentLength((int) file.length()); String mimetype = getApplication().getMimeType(file.getName()); context.response.setContentType((mimetype != null) ? mimetype : "application/octet-stream"); FileInputStream fileInputStream = new FileInputStream(file); ServletOutputStream outputStream = context.response.getOutputStream(); try { int i; byte[] buffer = new byte[bufferSize]; while ((i = fileInputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, i); } } finally { outputStream.flush(); fileInputStream.close(); } } } }