Java tutorial
/* Copyright (C) 2015 - Gareth Edwards / Rapid Information Systems gareth.edwards@rapid-is.co.uk This file is part of the Rapid Application Platform Rapid is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The terms require you to include the original copyright, and the license notice in all redistributions. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License in a file named "COPYING". If not, see <http://www.gnu.org/licenses/>. */ package com.rapid.server; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.rapid.core.Application; import com.rapid.core.Application.RapidLoadingException; import com.rapid.core.Page; import com.rapid.core.Pages.PageHeader; import com.rapid.core.Pages.PageHeaders; import com.rapid.forms.FormAdapter; import com.rapid.forms.FormAdapter.FormControlValue; import com.rapid.forms.FormAdapter.FormPageControlValues; import com.rapid.forms.FormAdapter.ServerSideValidationException; import com.rapid.forms.FormAdapter.UserFormDetails; import com.rapid.security.SecurityAdapter; import com.rapid.security.SecurityAdapter.User; import com.rapid.server.filter.RapidFilter; import com.rapid.utils.Files; public class Rapid extends RapidHttpServlet { private static final long serialVersionUID = 1L; // these are held here and referred to globally public static final String VERSION = "2.3.4"; // the master version of this Rapid server instance public static final String MOBILE_VERSION = "1"; // the mobile version. update it if you want all mobile devices to run app updates on their next version check public static final String DESIGN_ROLE = "RapidDesign"; public static final String ADMIN_ROLE = "RapidAdmin"; public static final String SUPER_ROLE = "RapidSuper"; // helper methods for forms private String getFirstPageForFormType(Application app, int formPageType) throws RapidLoadingException { // loop the sorted page headers for (PageHeader pageHeader : app.getPages().getSortedPages()) { // get the page Page page = app.getPages().getPage(getServletContext(), pageHeader.getId()); // if this is s submitted page if (page.getFormPageType() == formPageType) { // return the page id return page.getId(); } } return null; } public static void gotoStartPage(HttpServletRequest request, HttpServletResponse response, Application app, boolean invalidate) throws IOException { // clear the session if requested to if (invalidate) request.getSession().invalidate(); // go to the start page response.sendRedirect("~?a=" + app.getId() + "&v=" + app.getVersion()); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // fake a delay for testing slow servers //try { Thread.sleep(3000); } catch (InterruptedException e) {} // get a logger Logger logger = getLogger(); // log! logger.debug("Rapid GET request : " + request.getQueryString()); // get a new rapid request passing in this servlet and the http request RapidRequest rapidRequest = new RapidRequest(this, request); try { // get the application object Application app = rapidRequest.getApplication(); // check app exists if (app == null) { // send message sendMessage(response, 404, "Application not found", "The application you requested can't be found"); //log logger.debug("Rapid GET response (404) : Application not found on this server"); } else { // get the application security SecurityAdapter security = app.getSecurityAdapter(); // check the password if (security.checkUserPassword(rapidRequest, rapidRequest.getUserName(), rapidRequest.getUserPassword())) { // get the user User user = security.getUser(rapidRequest); // get the action String action = rapidRequest.getActionName(); // check if there is a Rapid action if ("summary".equals(action) || "pdf".equals(action)) { // get the form adapter for both of the above FormAdapter formAdapter = app.getFormAdapter(); // check there is one if (formAdapter == null) { // send message sendMessage(response, 500, "Not a form", "This Rapid app is not a form"); // log logger.error("Rapid GET response (500) : Summary requested for " + app.getId() + "/" + app.getDescription() + " " + action + " but it does not have a form adapter"); } else { if ("pdf".equals(action)) { // write the form pdf formAdapter.doWriteFormPDF(rapidRequest, response, request.getParameter("f")); } else { // summary is never cached RapidFilter.noCache(response); // write the form summary page formAdapter.writeFormSummary(rapidRequest, response); } } } else if ("download".equals(action)) { // set the file name String fileName = app.getId() + "_" + rapidRequest.getUserName() + ".zip"; // create the zip file app.zip(this, rapidRequest, user, fileName, true); // set the type as a .zip response.setContentType("application/x-zip-compressed"); // Shows the download dialog response.setHeader("Content-disposition", "attachment; filename=" + app.getId() + ".zip"); // get the file for the zip we're about to create File zipFile = new File(getServletContext().getRealPath("/WEB-INF/temp/" + fileName)); // get it's size long fileSize = Files.getSize(zipFile); // add size to response headers if small enough if (fileSize < Integer.MAX_VALUE) response.setContentLength((int) fileSize); // send the file to browser OutputStream os = response.getOutputStream(); FileInputStream in = new FileInputStream(zipFile); byte[] buffer = new byte[1024]; int length; while ((length = in.read(buffer)) > 0) { os.write(buffer, 0, length); } in.close(); os.flush(); // delete the file zipFile.delete(); } else { // assume it's ok to write the page boolean pageCheck = true; // assume we won't be redirecting to the summary boolean showSummary = false; // get the requested page object Page page = rapidRequest.getPage(); // get the form adapter (if there is one) FormAdapter formAdapter = app.getFormAdapter(); // assume the form details are null UserFormDetails formDetails = null; try { // if there is a formAdapter, make sure there's a form id, unless it's for a simple page if (formAdapter != null) { // if there is a start parameter if (request.getParameter("start") != null) { // invalidate the session request.getSession().invalidate(); // create a new session request.getSession(); // recreate the rapidRequest rapidRequest = new RapidRequest(this, request); } // if this is a form resume if ("resume".equals(action)) { // get the form id and password from the url String resumeFormId = request.getParameter("f"); String resumePassword = request.getParameter("pwd"); // try and get the resume form details formDetails = formAdapter.doResumeForm(rapidRequest, resumeFormId, resumePassword); // check whether we can resume this form if (formDetails != null) { // go for the summary if no page specified if (request.getParameter("p") == null) showSummary = true; } } else { // get form id from the adapter formDetails = formAdapter.getUserFormDetails(rapidRequest); } // if there isn't a form id, or we want to show the summary don't check the pages if (formDetails == null || showSummary) { // set the page check to false pageCheck = false; } else if (page != null) { // check that we have progressed far enough in the form to view this page, or we are a designer if (formAdapter.checkMaxPage(rapidRequest, formDetails, page.getId()) || security.checkUserRole(rapidRequest, DESIGN_ROLE)) { // only if this is not a dialogue if (!"dialogue".equals(action)) { // get all of the pages PageHeaders pageHeaders = app.getPages().getSortedPages(); // get this page position int pageIndex = pageHeaders.indexOf(page.getId()); // check the page visibility while (!page.isVisible(rapidRequest, app, formDetails)) { // if we're here the visibility check on the current page failed so increment the index pageIndex++; // if there are no more pages go to the summary if (pageIndex > pageHeaders.size() - 1) { // fail the check to print a page pageCheck = false; // but set the the show summary to true showSummary = true; // we're done break; } else { // select the next page to check the visibility of page = app.getPages().getPage(getServletContext(), pageHeaders.get(pageIndex).getId()); // if not submitted set that we're allowed to this page if (!formDetails.getSubmitted()) formAdapter.setMaxPage(rapidRequest, formDetails, page.getId()); // if this page has session values if (page.getSessionVariables() != null) { // loop them for (String variable : page.getSessionVariables()) { // look for session values String value = (String) rapidRequest .getSessionAttribute(variable); // if we got one update it's value if (value != null) formAdapter.setFormPageVariableValue(rapidRequest, formDetails.getId(), variable, value); } } } // pages remaining check } // page visible loop } // dialogue check } else { // go back to the start pageCheck = false; //log logger.debug("Not allowed on page " + page.getId() + " yet!"); } // page max check } // form id check } // form adapter check } catch (Exception ex) { // set the page to null so we show the user a not found page = null; // log logger.debug("Error with page visibility rules : " + ex.getMessage(), ex); } // if the pageCheck was ok (or not invalidated by lack of a form id or summary page) if (pageCheck) { // check we got one if (page == null) { // send message sendMessage(response, 404, "Page not found", "The page you requested can't be found"); // log logger.debug("Rapid GET response (404) : Page not found"); } else { // get the pageId String pageId = page.getId(); // if the page we're about to write is the page we asked for (visibility rules might move us on a bit) if (pageId.equals(rapidRequest.getPage().getId())) { // create a writer PrintWriter out = response.getWriter(); // assume we require the designer link boolean designerLink = true; // set designer link to false if action is dialogue if ("dialogue".equals(rapidRequest.getActionName())) designerLink = false; // set the response type response.setContentType("text/html"); // write the page html page.writeHtml(this, response, rapidRequest, app, user, out, designerLink); // close the writer out.close(); // flush the writer out.flush(); // if we have a form adapter and form details if (formAdapter != null && formDetails != null) { // if this is an error page we have just shown the error, remove it if (page.getFormPageType() == Page.FORM_PAGE_TYPE_ERROR) formDetails.setErrorMessage(null); // if this is a save page we have just shown it, set to not saved if (page.getFormPageType() == Page.FORM_PAGE_TYPE_SAVED) formDetails.setSaved(false); } } else { // redirect user to correct page response.sendRedirect( "~?a=" + app.getId() + "&v=" + app.getVersion() + "&p=" + page.getId()); } } // page check } else { if (showSummary) { // go to the summary response.sendRedirect( "~?a=" + app.getId() + "&v=" + app.getVersion() + "&action=summary"); } else { logger.debug("Returning to start - failed page check and no showSummary"); // go to the start page (invalidate unless user has design role) gotoStartPage(request, response, app, !security.checkUserRole(rapidRequest, DESIGN_ROLE)); } } // form id check } // action name check } else { // send message sendMessage(response, 403, "No permission", "You do not have permission to use this application"); //log logger.debug("Rapid GET response (403) : User " + rapidRequest.getUserName() + " not authorised for application"); } // password check } // app exists check } catch (Exception ex) { logger.error("Rapid GET error : ", ex); sendException(rapidRequest, response, ex); } } private JSONObject getJSONObject(byte[] bodyBytes) throws UnsupportedEncodingException, JSONException { // assume we weren't passed any json JSONObject jsonData = null; // read the body into a string String bodyString = new String(bodyBytes, "UTF-8"); // if there is something in the body string it must be json so parse it if (!"".equals(bodyString)) { // get a logger Logger logger = getLogger(); // log the body string logger.debug(bodyString); // get the data jsonData = new JSONObject(bodyString); } return jsonData; } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // fake a delay for testing slow servers //try { Thread.sleep(3000); } catch (InterruptedException e) {} // this byte buffer is used for reading the post data byte[] byteBuffer = new byte[1024]; // read bytes from request body into our own byte array (this means we can deal with images) InputStream input = request.getInputStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); for (int length = 0; (length = input.read(byteBuffer)) > -1;) outputStream.write(byteBuffer, 0, length); byte[] bodyBytes = outputStream.toByteArray(); // get a logger Logger logger = getLogger(); // log logger.debug("Rapid POST request : " + request.getQueryString() + " bytes=" + bodyBytes.length); // create a Rapid request RapidRequest rapidRequest = new RapidRequest(this, request); try { // this is the only variant where an application isn't specified and secured first if ("getApps".equals(rapidRequest.getActionName())) { // create an empty array which we will populate JSONArray jsonApps = new JSONArray(); // get all available applications List<Application> apps = getApplications().sort(); // if there were some if (apps != null) { // fail silently if there was an issue try { // assume we weren't passed any json JSONObject jsonData = getJSONObject(bodyBytes); // assume the request wasn't for testing on Rapid Mobile boolean forTesting = false; // if we got some data, look for a test = true entry - this is sent from Rapid Mobile if (jsonData != null) forTesting = jsonData.optBoolean("test"); // loop the apps for (Application app : apps) { // if Rapid app must not be for testing / from Rapid Mobile if (!"rapid".equals(app.getId()) || !forTesting) { // get the relevant security adapter SecurityAdapter security = app.getSecurityAdapter(); // make a rapidRequest for this application RapidRequest getAppsRequest = new RapidRequest(this, request, app); // check the user password if (security.checkUserPassword(getAppsRequest, rapidRequest.getUserName(), rapidRequest.getUserPassword())) { // assume can add boolean canAdd = true; if ("rapid".equals(app.getId())) { // must have RapidAdmin or RapidSuper to see Rapid app canAdd = security.checkUserRole(rapidRequest, Rapid.ADMIN_ROLE) || security.checkUserRole(rapidRequest, Rapid.SUPER_ROLE); } if (canAdd) { // create a json object for the details of this application JSONObject jsonApp = new JSONObject(); // add details jsonApp.put("id", app.getId()); jsonApp.put("version", app.getVersion()); jsonApp.put("title", app.getTitle()); jsonApp.put("noRetainPassword", app.getNoRetainPassword()); // add app to our main array jsonApps.put(jsonApp); // check if we are testing if (forTesting) { // if the user has Rapid Design for this application, (or Rpaid Super if this is the rapid app) if (security.checkUserRole(rapidRequest, Rapid.DESIGN_ROLE) && (!app.getId().equals("rapid") || security .checkUserRole(rapidRequest, Rapid.SUPER_ROLE))) { // loop the versions for (Application version : getApplications() .getVersions(app.getId()).sort()) { // create a json object for the details of this version jsonApp = new JSONObject(); // add details jsonApp.put("id", version.getId()); jsonApp.put("version", version.getVersion()); jsonApp.put("status", version.getStatus()); jsonApp.put("title", version.getTitle()); jsonApp.put("noRetainPassword", version.getNoRetainPassword()); jsonApp.put("test", true); // add app to our main array jsonApps.put(jsonApp); } } // got design role } // forTesting check } // rapid app extra check } // user check } // rapid app and not for testing check } // apps loop } catch (Exception ex) { // only log logger.error("Error geting apps : ", ex); } } // apps check // set response to json response.setContentType("application/json"); // create a writer PrintWriter out = response.getWriter(); // print the results out.print(jsonApps.toString()); // close the writer out.close(); // log response logger.debug("Rapid POST response : " + jsonApps.toString()); } else { // get the application Application app = rapidRequest.getApplication(); // check we got one if (app == null) { // send forbidden response sendMessage(response, 400, "Application not found", "The application you requested can't be found"); // log logger.debug("Rapid POST response (403) : Application not found"); } else { // get the security SecurityAdapter security = app.getSecurityAdapter(); // check the user password if (security.checkUserPassword(rapidRequest, rapidRequest.getUserName(), rapidRequest.getUserPassword())) { // if an application action was found in the request if (rapidRequest.getAction() != null) { // assume we weren't passed any json JSONObject jsonData = getJSONObject(bodyBytes); // if we got some data if (jsonData != null) { // fetch the action result JSONObject jsonResult = rapidRequest.getAction().doAction(rapidRequest, jsonData); // set response to json response.setContentType("application/json"); // create a writer PrintWriter out = response.getWriter(); // print the results out.print(jsonResult.toString()); // close the writer out.close(); // log response logger.debug("Rapid POST response : " + jsonResult); } // jsonData } else if ("application/x-www-form-urlencoded".equals(request.getContentType())) { // log logger.debug("Form data received"); // get the form adapter FormAdapter formAdapter = app.getFormAdapter(); // form adapter check if (formAdapter == null) { // send message sendMessage(response, 500, "Not a form", "This Rapid app is not a form"); // log logger.debug("Rapid GET response (500) : Not a form"); } else { // get the form details to test all is ok UserFormDetails formDetails = formAdapter.getUserFormDetails(rapidRequest); // check we got one if (formDetails == null) { logger.debug("Returning to start - could not retrieve form details"); // we've lost the form id so start the form again gotoStartPage(request, response, app, true); } else { // this is a form page's data being submitted String formData = new String(bodyBytes, "UTF-8"); // log it! logger.trace("Form data : " + formData); // if there's a submit action if ("submit".equals(request.getParameter("action"))) { // if submitted already go to start (should never happen) if (formDetails.getSubmitted()) { logger.debug( "Returning to start - submit action but form not submitted"); // go to the start page gotoStartPage(request, response, app, true); } else { try { // do the submit (this will call the non-abstract submit, manage the form state, and retain the submit message) formAdapter.doSubmitForm(rapidRequest); // place holder for first submitted page String submittedPageId = getFirstPageForFormType(app, Page.FORM_PAGE_TYPE_SUBMITTED); // check we got a submitted page if (submittedPageId == null) { // invalidate the form formAdapter.setUserFormDetails(rapidRequest, null); logger.debug( "Returning to start - form has been submitted, no submission page"); // go to the start page gotoStartPage(request, response, app, true); } else { // request the first submitted page response.sendRedirect("~?a=" + app.getId() + "&v=" + app.getVersion() + "&p=" + submittedPageId); } } catch (Exception ex) { // place holder for first submitted page String errrorPageId = getFirstPageForFormType(app, Page.FORM_PAGE_TYPE_ERROR); // check we got one if (errrorPageId == null) { // just re throw the error throw ex; } else { // request the first error page response.sendRedirect("~?a=" + app.getId() + "&v=" + app.getVersion() + "&p=" + errrorPageId); } } } // submit check } else { // try try { // get the page Page page = rapidRequest.getPage(); // if we got one if (page == null) { // send error sendMessage(response, 403, "Page does not exist", "The page you requested does not exist"); } else { // get the page id String requestPageId = rapidRequest.getPage().getId(); // if form not submitted if (!formDetails.getSubmitted()) { // get the page control values FormPageControlValues pageControlValues = FormAdapter .getPostPageControlValues(rapidRequest, formData, formDetails.getId()); // check we got some if (pageControlValues != null) { // loop and print them if trace on if (logger.isTraceEnabled()) { for (FormControlValue controlValue : pageControlValues) { logger.debug(controlValue.getId() + " = " + controlValue.getValue()); } } // store the form page control values formAdapter.setFormPageControlValues(rapidRequest, formDetails.getId(), requestPageId, pageControlValues); } } // assume we're not going to go to the summary boolean requestSummary = false; // get all of the app pages PageHeaders pageHeaders = app.getPages().getSortedPages(); // get the position of the next page in sequence int pageIndex = pageHeaders.indexOf(requestPageId) + 1; // get the next page page = app.getPages().getPage(getServletContext(), pageHeaders.get(pageIndex).getId()); // check the page visibility while (!page.isVisible(rapidRequest, app, formDetails)) { // if we're here the visibility check on the current page failed so increment the index pageIndex++; // if there are no more pages go to the summary if (pageIndex > pageHeaders.size() - 1) { // but set the the show summary to true requestSummary = true; // we're done break; } else { // select the next page to check the visibility of page = app.getPages().getPage(getServletContext(), pageHeaders.get(pageIndex).getId()); // if not submitted set that we're allowed to this page if (!formDetails.getSubmitted()) formAdapter.setMaxPage(rapidRequest, formDetails, page.getId()); } // pages remaining check } // page visible loop // if this is the last page if (requestSummary) { // mark that this form is complete (if not submitted) if (!formDetails.getSubmitted()) formAdapter.setFormComplete(rapidRequest, formDetails); // send a redirect for the summary (this also avoids ERR_CACH_MISS issues on the back button ) response.sendRedirect("~?a=" + app.getId() + "&v=" + app.getVersion() + "&action=summary"); } else { // set that we're allowed to this page if (!formDetails.getSubmitted()) formAdapter.setMaxPage(rapidRequest, formDetails, page.getId()); // send a redirect for the page (this avoids ERR_CACH_MISS issues on the back button ) response.sendRedirect("~?a=" + app.getId() + "&v=" + app.getVersion() + "&p=" + page.getId()); } // last page check } // page check } catch (ServerSideValidationException ex) { // log it! logger.error( "Form data failed server side validation : " + ex.getMessage(), ex); // send a redirect back to the beginning - there's no reason except for tampering that this would happen gotoStartPage(request, response, app, true); } } // form id check } // submit action check } // form adapter check } else if ("checkVersion".equals(rapidRequest.getActionName())) { // create a json version object JSONObject jsonVersion = new JSONObject(); // add the mobile version, followed by the app version jsonVersion.put("version", MOBILE_VERSION + " - " + app.getVersion()); // create a writer PrintWriter out = response.getWriter(); // print the results out.print(jsonVersion.toString()); // close the writer out.close(); // log response logger.debug("Rapid POST response : " + jsonVersion.toString()); } else if ("uploadImage".equals(rapidRequest.getActionName())) { // get the name String imageName = request.getParameter("name"); // if we got one if (imageName == null) { // send forbidden response sendMessage(response, 400, "Name required", "Image name must be provided"); // log logger.debug("Rapid POST response (403) : Name must be provided"); } else { // check the jpg file signature (from http://en.wikipedia.org/wiki/List_of_file_signatures) if (bodyBytes[0] == (byte) 0xFF && bodyBytes[1] == (byte) 0xD8 && bodyBytes[2] == (byte) 0xFF) { // create the path String imagePath = "uploads/" + app.getId() + "/" + imageName; // create a file File imageFile = new File(getServletContext().getRealPath(imagePath)); // create app folder if need be imageFile.getParentFile().mkdir(); // create a file output stream to save the data to FileOutputStream fos = new FileOutputStream(imageFile); // write the body bytes to the stream fos.write(bodyBytes); // close the stream fos.close(); // log the file creation logger.debug("Saved image file " + imageFile); // create a writer PrintWriter out = response.getWriter(); // print the results out.print(imagePath); // close the writer out.close(); } else { // send forbidden response sendMessage(response, 400, "Unrecognised", "Unrecognised file type"); // log logger.debug("Rapid POST response (403) : Unrecognised file type"); } // signature check } // upload file name check } // action check } else { // send forbidden response sendMessage(response, 403, "No permisssion", "You do not have permssion to use this application"); // log logger.debug("Rapid POST response (403) : User not authorised for application"); } // user check } // app check } // pre app action check } catch (Exception ex) { logger.error("Rapid POST error : ", ex); sendException(rapidRequest, response, ex); } } }