org.ecocean.servlet.ServletUtilities.java Source code

Java tutorial

Introduction

Here is the source code for org.ecocean.servlet.ServletUtilities.java

Source

/*
* Wildbook - A Mark-Recapture Framework
* Copyright (C) 2011-2014 Jason Holmberg
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package org.ecocean.servlet;

import com.sun.syndication.feed.synd.*;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.XmlReader;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.json.JSONObject;

import javax.jdo.Query;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletContext;
//import javax.servlet.http.HttpSession;
import org.json.JSONObject;

import java.io.*;
import java.net.URL;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ThreadPoolExecutor;
import java.sql.*;
import java.util.Collection;
import java.util.Map;
import java.util.Enumeration;
import java.util.HashMap;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.text.ParseException;
import java.util.Calendar;

import org.ecocean.*;
import org.ecocean.security.Collaboration;
import org.apache.shiro.crypto.hash.*;
import org.apache.shiro.util.*;
import org.apache.shiro.crypto.*;

import java.util.Properties;

import javax.servlet.http.Cookie;

import org.apache.commons.lang.StringEscapeUtils;

public class ServletUtilities {

    public static String getHeader(HttpServletRequest request) {
        try {
            FileReader fileReader = new FileReader(findResourceOnFileSystem("servletResponseTemplate.htm"));
            BufferedReader buffread = new BufferedReader(fileReader);
            String templateFile = "", line;
            StringBuffer SBreader = new StringBuffer();
            while ((line = buffread.readLine()) != null) {
                SBreader.append(line).append("\n");
            }
            fileReader.close();
            buffread.close();
            templateFile = SBreader.toString();

            String context = getContext(request);

            //process the CSS string
            templateFile = templateFile.replaceAll("CSSURL",
                    CommonConfiguration.getCSSURLLocation(request, context));

            //set the top header graphic
            templateFile = templateFile.replaceAll("TOPGRAPHIC",
                    CommonConfiguration.getURLToMastheadGraphic(context));

            int end_header = templateFile.indexOf("INSERT_HERE");
            return (templateFile.substring(0, end_header));
        } catch (Exception e) {
            //out.println("I couldn't find the template file to read from.");
            e.printStackTrace();
            String error = "<html><body><p>An error occurred while attempting to read from the template file servletResponseTemplate.htm. This probably will not affect the success of the operation you were trying to perform.";
            return error;
        }

    }

    public static String getFooter(String context) {
        try {
            FileReader fileReader = new FileReader(findResourceOnFileSystem("servletResponseTemplate.htm"));
            BufferedReader buffread = new BufferedReader(fileReader);
            String templateFile = "", line;
            StringBuffer SBreader = new StringBuffer();
            while ((line = buffread.readLine()) != null) {
                SBreader.append(line).append("\n");
            }
            fileReader.close();
            buffread.close();
            templateFile = SBreader.toString();
            templateFile = templateFile.replaceAll("BOTTOMGRAPHIC",
                    CommonConfiguration.getURLToFooterGraphic(context));

            int end_header = templateFile.indexOf("INSERT_HERE");
            return (templateFile.substring(end_header + 11));
        } catch (Exception e) {
            //out.println("I couldn't find the template file to read from.");
            e.printStackTrace();
            String error = "An error occurred while attempting to read from an HTML template file. This probably will not affect the success of the operation you were trying to perform.</p></body></html>";
            return error;
        }

    }

    /**
    * Inform (via email) researchers who've logged an interest in encounter.
    * @param request servlet request
    * @param encounterNumber ID of encounter to inform about
    * @param message message to include in email notification
    * @param context webapp context
    */
    public static void informInterestedParties(HttpServletRequest request, String encounterNumber, String message,
            String context) {
        Shepherd shep = new Shepherd(context);
        shep.setAction("ServletUtilities.class.informInterestedParties");
        shep.beginDBTransaction();
        if (shep.isEncounter(encounterNumber)) {
            Encounter enc = shep.getEncounter(encounterNumber);
            if (enc.getInterestedResearchers() != null) {
                Collection<String> notifyMe = enc.getInterestedResearchers();
                if (!notifyMe.isEmpty()) {
                    ThreadPoolExecutor es = MailThreadExecutorService.getExecutorService();
                    for (String mailTo : notifyMe) {
                        Map<String, String> tagMap = NotificationMailer.createBasicTagMap(request, enc);
                        tagMap.put(NotificationMailer.EMAIL_NOTRACK, "number=" + encounterNumber);
                        tagMap.put(NotificationMailer.EMAIL_HASH_TAG, Encounter.getHashOfEmailString(mailTo));
                        tagMap.put(NotificationMailer.STANDARD_CONTENT_TAG, message == null ? "" : message);
                        //            String langCode = ServletUtilities.getLanguageCode(request);
                        NotificationMailer mailer = new NotificationMailer(context, null, mailTo,
                                "encounterDataUpdate", tagMap);
                        es.execute(mailer);
                    }
                    es.shutdown();
                }
            }
        }
        shep.rollbackDBTransaction();
        shep.closeDBTransaction();
    }

    /**
    * Inform (via email) researchers who've logged an interest in individual.
    * @param request servlet request
    * @param individualID ID of individual to inform about
    * @param message message to include in email notification
    * @param context webapp context
    */
    public static void informInterestedIndividualParties(HttpServletRequest request, String individualID,
            String message, String context) {
        Shepherd shep = new Shepherd(context);
        shep.setAction("ServletUtilities.informInterestedIndividualParties.class");
        shep.beginDBTransaction();
        if (shep.isMarkedIndividual(individualID)) {
            MarkedIndividual ind = shep.getMarkedIndividual(individualID);
            if (ind.getInterestedResearchers() != null) {
                Collection<String> notifyMe = ind.getInterestedResearchers();
                if (!notifyMe.isEmpty()) {
                    ThreadPoolExecutor es = MailThreadExecutorService.getExecutorService();
                    for (String mailTo : notifyMe) {
                        Map<String, String> tagMap = NotificationMailer.createBasicTagMap(request, ind);
                        tagMap.put(NotificationMailer.EMAIL_NOTRACK, "individual=" + individualID);
                        tagMap.put(NotificationMailer.EMAIL_HASH_TAG, Encounter.getHashOfEmailString(mailTo));
                        tagMap.put(NotificationMailer.STANDARD_CONTENT_TAG, message == null ? "" : message);
                        //            String langCode = ServletUtilities.getLanguageCode(request);
                        NotificationMailer mailer = new NotificationMailer(context, null, mailTo,
                                "individualDataUpdate", tagMap);
                        es.execute(mailer);
                    }
                    es.shutdown();
                }
            }
        }
        shep.rollbackDBTransaction();
        shep.closeDBTransaction();
    }

    //Loads a String of text from a specified file.
    //This is generally used to load an email template for automated emailing
    public static String getText(String shepherdDataDir, String fileName, String langCode) {
        String overrideText = loadOverrideText(shepherdDataDir, fileName, langCode);
        if (!overrideText.equals("")) {
            return overrideText;
        } else {
            try {
                StringBuffer SBreader = new StringBuffer();
                String line;
                FileReader fileReader = new FileReader(findResourceOnFileSystem(fileName));

                BufferedReader buffread = new BufferedReader(fileReader);
                while ((line = buffread.readLine()) != null) {
                    SBreader.append(line + "\n");
                }
                line = SBreader.toString();
                fileReader.close();
                buffread.close();
                return line;
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
        }
    }

    //Logs a new ATOM entry
    public static synchronized void addATOMEntry(String title, String link, String description, File atomFile,
            String context) {
        try {

            if (atomFile.exists()) {

                //System.out.println("ATOM file found!");
                /** Namespace URI for content:encoded elements */
                String CONTENT_NS = "http://www.w3.org/2005/Atom";

                /** Parses RSS or Atom to instantiate a SyndFeed. */
                SyndFeedInput input = new SyndFeedInput();

                /** Transforms SyndFeed to RSS or Atom XML. */
                SyndFeedOutput output = new SyndFeedOutput();

                // Load the feed, regardless of RSS or Atom type
                SyndFeed feed = input.build(new XmlReader(atomFile));

                // Set the output format of the feed
                feed.setFeedType("atom_1.0");

                List<SyndEntry> items = feed.getEntries();
                int numItems = items.size();
                if (numItems > 9) {
                    items.remove(0);
                    feed.setEntries(items);
                }

                SyndEntry newItem = new SyndEntryImpl();
                newItem.setTitle(title);
                newItem.setLink(link);
                newItem.setUri(link);
                SyndContent desc = new SyndContentImpl();
                desc.setType("text/html");
                desc.setValue(description);
                newItem.setDescription(desc);
                desc.setType("text/html");
                newItem.setPublishedDate(new java.util.Date());

                List<SyndCategory> categories = new ArrayList<SyndCategory>();
                if (CommonConfiguration.getProperty("htmlTitle", context) != null) {
                    SyndCategory category2 = new SyndCategoryImpl();
                    category2.setName(CommonConfiguration.getProperty("htmlTitle", context));
                    categories.add(category2);
                }
                newItem.setCategories(categories);
                if (CommonConfiguration.getProperty("htmlAuthor", context) != null) {
                    newItem.setAuthor(CommonConfiguration.getProperty("htmlAuthor", context));
                }
                items.add(newItem);
                feed.setEntries(items);

                feed.setPublishedDate(new java.util.Date());

                FileWriter writer = new FileWriter(atomFile);
                output.output(feed, writer);
                writer.toString();

            }
        } catch (IOException ioe) {
            System.out.println("ERROR: Could not find the ATOM file.");
            ioe.printStackTrace();
        } catch (Exception e) {
            System.out.println("Unknown exception trying to add an entry to the ATOM file.");
            e.printStackTrace();
        }

    }

    //Logs a new entry in the library RSS file
    public static synchronized void addRSSEntry(String title, String link, String description, File rssFile) {
        //File rssFile=new File("nofile.xml");

        try {
            System.out.println("Looking for RSS file: " + rssFile.getCanonicalPath());
            if (rssFile.exists()) {

                SAXReader reader = new SAXReader();
                Document document = reader.read(rssFile);
                Element root = document.getRootElement();
                Element channel = root.element("channel");
                List items = channel.elements("item");
                int numItems = items.size();
                items = null;
                if (numItems > 9) {
                    Element removeThisItem = channel.element("item");
                    channel.remove(removeThisItem);
                }

                Element newItem = channel.addElement("item");
                Element newTitle = newItem.addElement("title");
                Element newLink = newItem.addElement("link");
                Element newDescription = newItem.addElement("description");
                newTitle.setText(title);
                newDescription.setText(description);
                newLink.setText(link);

                Element pubDate = channel.element("pubDate");
                pubDate.setText((new java.util.Date()).toString());

                //now save changes
                FileWriter mywriter = new FileWriter(rssFile);
                OutputFormat format = OutputFormat.createPrettyPrint();
                format.setLineSeparator(System.getProperty("line.separator"));
                XMLWriter writer = new XMLWriter(mywriter, format);
                writer.write(document);
                writer.close();

            }
        } catch (IOException ioe) {
            System.out.println("ERROR: Could not find the RSS file.");
            ioe.printStackTrace();
        } catch (DocumentException de) {
            System.out.println("ERROR: Could not read the RSS file.");
            de.printStackTrace();
        } catch (Exception e) {
            System.out.println("Unknown exception trying to add an entry to the RSS file.");
            e.printStackTrace();
        }
    }

    public static File findResourceOnFileSystem(String resourceName) {
        File resourceFile = null;

        URL resourceURL = ServletUtilities.class.getClassLoader().getResource(resourceName);
        if (resourceURL != null) {
            String resourcePath = resourceURL.getPath();
            if (resourcePath != null) {
                File tmp = new File(resourcePath);
                if (tmp.exists()) {
                    resourceFile = tmp;
                }
            }
        }
        return resourceFile;
    }

    public static boolean isUserAuthorizedForEncounter(Encounter enc, HttpServletRequest request) {
        boolean isOwner = false;
        //if (request.isUserInRole("admin")) {
        if (request.getUserPrincipal() != null) {
            if (request.isUserInRole("admin")) {
                isOwner = true;
            } else if (enc.getLocationCode() != null && request.isUserInRole(enc.getLocationCode())) {
                isOwner = true;
            } else if ((((enc.getSubmitterID() != null) && (request.getRemoteUser() != null)
                    && (enc.getSubmitterID().equals(request.getRemoteUser()))))) {
                isOwner = true;
            }

            //whaleshark.org custom

            else if (Collaboration.canEditEncounter(enc, request))
                return true;

        }
        return isOwner;
        //}
        // return isOwner;
    }

    public static boolean isUserAuthorizedForIndividual(MarkedIndividual sharky, HttpServletRequest request) {
        //if (request.isUserInRole("admin")) {
        if (request.getUserPrincipal() != null) {
            //return true;

            if (request.isUserInRole("admin")) {
                return true;
            }

            Vector encounters = sharky.getEncounters();
            int numEncs = encounters.size();
            for (int y = 0; y < numEncs; y++) {
                Encounter enc = (Encounter) encounters.get(y);
                if (enc.getLocationCode() != null && request.isUserInRole(enc.getLocationCode())) {
                    return true;
                }
            }
        }
        //}
        return false;
    }

    //occurrence
    public static boolean isUserAuthorizedForOccurrence(Occurrence sharky, HttpServletRequest request) {

        if (request.getUserPrincipal() != null) {

            if (request.isUserInRole("admin")) {
                return true;
            }
            ArrayList<Encounter> encounters = sharky.getEncounters();
            int numEncs = encounters.size();
            for (int y = 0; y < numEncs; y++) {
                Encounter enc = (Encounter) encounters.get(y);
                if (enc.getLocationCode() != null && request.isUserInRole(enc.getLocationCode())) {
                    return true;
                }
            }
        }

        return false;
    }
    //occurrence

    public static Query setRange(Query query, int iterTotal, int highCount, int lowCount) {

        if (iterTotal > 10) {

            //handle the normal situation first
            if ((lowCount > 0) && (lowCount <= highCount)) {
                if (highCount - lowCount > 50) {
                    query.setRange((lowCount - 1), (lowCount + 50));
                } else {
                    query.setRange(lowCount - 1, highCount);
                }
            } else {
                query.setRange(0, 10);
            }

        } else {
            query.setRange(0, iterTotal);
        }
        return query;

    }

    public static String cleanFileName(String myString) {
        return myString.replaceAll("[^a-zA-Z0-9\\.\\-]", "_");
    }

    /*public static String cleanFileName(String aTagFragment) {
    final StringBuffer result = new StringBuffer();
        
    final StringCharacterIterator iterator = new StringCharacterIterator(aTagFragment);
    char character = iterator.current();
    while (character != CharacterIterator.DONE) {
    if (character == '<') {
    result.append("_");
    } else if (character == '>') {
    result.append("_");
    } else if (character == '\"') {
    result.append("_");
    } else if (character == '\'') {
    result.append("_");
    } else if (character == '\\') {
    result.append("_");
    } else if (character == '&') {
    result.append("_");
    } else if (character == ' ') {
    result.append("_");
    } else if (character == '#') {
    result.append("_");
    } else {
    //the char is not a special one
    //add it to the result as is
    result.append(character);
    }
    character = iterator.next();
    }
    return result.toString();
    }
    */

    public static String getEncounterUrl(String encID, HttpServletRequest request) {
        return (CommonConfiguration.getServerURL(request) + "/encounters/encounter.jsp?number=" + encID);
    }

    public static String getIndividualUrl(String indID, HttpServletRequest request) {
        return (CommonConfiguration.getServerURL(request) + "/individuals.jsp?number=" + indID);
    }

    public static String getOccurrenceUrl(String occID, HttpServletRequest request) {
        return (CommonConfiguration.getServerURL(request) + "/occurrence.jsp?number=" + occID);
    }

    public static String preventCrossSiteScriptingAttacks(String description) {
        description = description.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
        description = description.replaceAll("eval\\((.*)\\)", "");
        description = description.replaceAll("[\\\"\\\'][\\s]*((?i)javascript):(.*)[\\\"\\\']", "\"\"");
        description = description.replaceAll("((?i)script)", "");
        description = description.replaceAll("onerror", "");
        //description = description.replaceAll("alert", "");
        description = StringEscapeUtils.escapeHtml(description);
        return description;
    }

    public static String getDate() {
        DateTime dt = new DateTime();
        DateTimeFormatter fmt = ISODateTimeFormat.date();
        return (fmt.print(dt));
    }

    public static Connection getConnection() throws SQLException {

        Connection conn = null;
        Properties connectionProps = new Properties();
        connectionProps.put("user", CommonConfiguration.getProperty("datanucleus.ConnectionUserName", "context0"));
        connectionProps.put("password",
                CommonConfiguration.getProperty("datanucleus.ConnectionPassword", "context0"));

        conn = DriverManager.getConnection(CommonConfiguration.getProperty("datanucleus.ConnectionURL", "context0"),
                connectionProps);

        System.out.println("Connected to database for authentication.");
        return conn;
    }

    public static String hashAndSaltPassword(String clearTextPassword, String salt) {
        return new Sha512Hash(clearTextPassword, salt, 200000).toHex();
    }

    public static String hashString(String hashMe) {
        return new Sha512Hash(hashMe).toHex();
    }

    public static ByteSource getSalt() {
        return new SecureRandomNumberGenerator().nextBytes();
    }

    public static String getContext(HttpServletRequest request) {
        String context = "context0";
        if (ContextConfiguration.getDefaultContext() != null) {
            context = ContextConfiguration.getDefaultContext();
        }
        Properties contexts = ShepherdProperties.getContextsProperties();
        int numContexts = contexts.size();

        //check the URL for the context attribute
        //this can be used for debugging and takes precedence
        if (request.getParameter("context") != null) {
            //get the available contexts
            //System.out.println("Checking for a context: "+request.getParameter("context"));
            if (contexts.containsKey((request.getParameter("context") + "DataDir"))) {
                //System.out.println("Found a request context: "+request.getParameter("context"));
                return request.getParameter("context");
            }
        }

        //the request cookie is the next thing we check. this should be the primary means of figuring context out
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("wildbookContext".equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }

        //finally, we will check the URL vs values defined in context.properties to see if we can set the right context
        String currentURL = request.getServerName();
        for (int q = 0; q < numContexts; q++) {
            String thisContext = "context" + q;
            List<String> domainNames = ContextConfiguration.getContextDomainNames(thisContext);
            int numDomainNames = domainNames.size();
            for (int p = 0; p < numDomainNames; p++) {

                if (currentURL.indexOf(domainNames.get(p)) != -1) {
                    return thisContext;
                }

            }

        }

        return context;
    }

    public static String getLanguageCode(HttpServletRequest request) {
        String context = ServletUtilities.getContext(request);

        //worst case scenario default to English
        String langCode = "en";

        //try to detect a default if defined
        if (CommonConfiguration.getProperty("defaultLanguage", context) != null) {
            langCode = CommonConfiguration.getProperty("defaultLanguage", context);
        }

        List<String> supportedLanguages = new ArrayList<String>();
        if (CommonConfiguration.getIndexedPropertyValues("language", context) != null) {
            supportedLanguages = CommonConfiguration.getIndexedPropertyValues("language", context);
        }

        //if specified directly, always accept the override
        if (request.getParameter("langCode") != null) {
            if (supportedLanguages.contains(request.getParameter("langCode"))) {
                return request.getParameter("langCode");
            }
        }

        //the request cookie is the next thing we check. this should be the primary means of figuring langCode out
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("wildbookLangCode".equals(cookie.getName())) {
                    if (supportedLanguages.contains(cookie.getValue())) {
                        return cookie.getValue();
                    }
                }
            }
        }

        //finally, we will check the URL vs values defined in context.properties to see if we can set the right context
        //TBD - future - detect browser supported language codes and locale from the HTTPServletRequest object

        return langCode;
    }

    public static String dataDir(String context, String rootWebappPath) {
        File webappsDir = new File(rootWebappPath).getParentFile();
        File shepherdDataDir = new File(webappsDir, CommonConfiguration.getDataDirectoryName(context));
        if (!shepherdDataDir.exists()) {
            shepherdDataDir.mkdirs();
        }
        return shepherdDataDir.getAbsolutePath();
    }

    //like above, but can pass a subdir to append
    public static String dataDir(String context, String rootWebappPath, String subdir) {
        return dataDir(context, rootWebappPath) + File.separator + subdir;
    }

    /*
    //like above, but only need request passed
    public static String dataDir(HttpServletRequest request) {
    String context = "context0";
    context = ServletUtilities.getContext(request);
    //String rootWebappPath = request.getServletContext().getRealPath("/");  // only in 3.0??
    //String rootWebappPath = request.getSession(true).getServlet().getServletContext().getRealPath("/");
    ServletContext s = request.getServletContext();
    String rootWebappPath = "xxxxxx";
    return dataDir(context, rootWebappPath);
    }
    */

    private static String loadOverrideText(String shepherdDataDir, String fileName, String langCode) {
        //System.out.println("Starting loadOverrideProps");
        StringBuffer myText = new StringBuffer("");
        //Properties myProps=new Properties();
        File configDir = new File("webapps/" + shepherdDataDir + "/WEB-INF/classes/bundles/" + langCode);
        //System.out.println(configDir.getAbsolutePath());
        //sometimes this ends up being the "bin" directory of the J2EE container
        //we need to fix that
        if ((configDir.getAbsolutePath().contains("/bin/")) || (configDir.getAbsolutePath().contains("\\bin\\"))) {
            String fixedPath = configDir.getAbsolutePath().replaceAll("/bin", "").replaceAll("\\\\bin", "");
            configDir = new File(fixedPath);
            //System.out.println("Fixing the bin issue in Shepherd PMF. ");
            //System.out.println("The fix abs path is: "+configDir.getAbsolutePath());
        }
        //System.out.println("ShepherdProps: "+configDir.getAbsolutePath());
        if (!configDir.exists()) {
            configDir.mkdirs();
        }
        File configFile = new File(configDir, fileName);
        if (configFile.exists()) {
            //System.out.println("ShepherdProps: "+"Overriding default properties with " + configFile.getAbsolutePath());
            FileInputStream fileInputStream = null;
            try {
                fileInputStream = new FileInputStream(configFile);

                BufferedReader reader = new BufferedReader(new InputStreamReader(fileInputStream));
                StringBuilder out = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                    myText.append(line);
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (fileInputStream != null) {
                    try {
                        fileInputStream.close();
                    } catch (Exception e2) {
                        e2.printStackTrace();
                    }
                }
            }
        }
        return myText.toString();
    }

    public static String handleNullString(Object obj) {
        if (obj == null) {
            return "";
        }
        return obj.toString();
    }

    public static JSONObject jsonFromHttpServletRequest(HttpServletRequest request) throws IOException {
        StringBuilder sb = new StringBuilder();
        BufferedReader reader = request.getReader();
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line).append('\n');
            }
        } finally {
            reader.close();
        }
        //ParseException
        return new JSONObject(sb.toString());
    }

    public static void printParams(HttpServletRequest request) {
        Enumeration<String> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            System.out.println("  " + name + ": " + request.getParameter(name));
        }
    }

    public static List<String> getIndexedParameters(String key, HttpServletRequest request) {
        List<String> vals = new ArrayList<String>();
        for (int i = 0; i < 100000; i++) { // hundred thousand seems like a reasonable upper limit right?
            String val = request.getParameter(key + i);
            if (Util.stringExists(val))
                vals.add(val);
            else
                return vals;
        }
        return vals;
    }

    public static String getParameterOrAttribute(String name, HttpServletRequest request) {
        if (name == null)
            return null;
        String result = request.getParameter(name);
        if (result == null) {
            Object attr = request.getAttribute(name);
            if (attr != null)
                result = attr.toString();
        }
        return result;
    }

    public static String getParameterOrAttributeOrSessionAttribute(String name, HttpServletRequest request) {
        String result = request.getParameter(name);
        if (result == null) {
            Object attr = request.getAttribute(name);
            if (attr != null)
                result = attr.toString();
        }
        if (result == null)
            result = getSessionAttribute(name, request);
        return result;
    }

    public static String getSessionAttribute(String name, HttpServletRequest request) {
        String stringAns = null;
        Object attr = request.getSession().getAttribute(name);
        if (attr != null)
            stringAns = attr.toString();
        return stringAns;
    }

    //handy "let anyone do anything (?) cors stuff
    public static void doOptions(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST");
        if (request.getHeader("Access-Control-Request-Headers") != null)
            response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
    }

    /* see webapps/captchaExample.jsp for implementation */

    //note: this only handles single-widget (per page) ... if we need multiple, will have to extend things here
    public static String captchaWidget(HttpServletRequest request) {
        return captchaWidget(request, null);
    }

    public static String captchaWidget(HttpServletRequest request, String params) {
        String context = getContext(request);
        Properties recaptchaProps = ShepherdProperties.getProperties("recaptcha.properties", "", context);
        if (recaptchaProps == null)
            return "<div class=\"error captcha-error captcha-missing-properties\">Unable to get captcha settings.</div>";
        String siteKey = recaptchaProps.getProperty("siteKey");
        String secretKey = recaptchaProps.getProperty("secretKey"); //really dont need this here
        if ((siteKey == null) || (secretKey == null))
            return "<div class=\"error captcha-error captcha-missing-key\">Unable to get captcha key settings.</div>";
        return "<script>function recaptchaCompleted() { return (grecaptcha && grecaptcha.getResponse(0)); }</script>\n"
                + "<script src='https://www.google.com/recaptcha/api.js" + ((params == null) ? "" : "?" + params)
                + "' async defer></script>\n" + "<div class=\"g-recaptcha\" data-sitekey=\"" + siteKey
                + "\"></div>";
    }

    //  https://developers.google.com/recaptcha/docs/verify
    public static boolean captchaIsValid(HttpServletRequest request) {
        return captchaIsValid(getContext(request), request.getParameter("g-recaptcha-response"),
                request.getRemoteAddr());
    }

    public static boolean captchaIsValid(String context, String uresp, String remoteIP) {
        if (context == null)
            context = "context0";
        Properties recaptchaProps = ShepherdProperties.getProperties("recaptcha.properties", "", context);
        if (recaptchaProps == null) {
            System.out.println("WARNING: no recaptcha.properties for captchaIsValid(); failing");
            return false;
        }
        String siteKey = recaptchaProps.getProperty("siteKey"); //really dont need this here
        String secretKey = recaptchaProps.getProperty("secretKey");
        if ((siteKey == null) || (secretKey == null)) {
            System.out.println("WARNING: could not determine keys for captchaIsValid(); failing");
            return false;
        }
        if (uresp == null) {
            System.out.println("WARNING: g-recaptcha-response is null in captchaIsValid(); failing");
            return false;
        }
        JSONObject cdata = new JSONObject();
        cdata.put("secret", secretKey);
        cdata.put("remoteip", remoteIP); //i guess this is technically optional (so we dont care if null?)
        cdata.put("response", uresp);
        JSONObject gresp = null;
        try {
            gresp = RestClient.post(new URL("https://www.google.com/recaptcha/api/siteverify"), cdata);
        } catch (Exception ex) {
            System.out.println(
                    "WARNING: exception calling captcha api in captchaIsValid(); failing: " + ex.toString());
            return false;
        }
        if (gresp == null) { //would this ever happen?
            System.out.println("WARNING: null return from captcha api in captchaIsValid(); failing");
            return false;
        }
        System.out.println("INFO: captchaIsValid() api call returned: " + gresp.toString());
        return gresp.optBoolean("success", false);
    }

    //this takes into account that we might be going thru nginx (etc?) as well
    public static String getRemoteHost(HttpServletRequest request) {
        if (request == null)
            return null;
        //these all seem to be *possible* headers from nginx (or other proxies?) but we standardize on "x-real-ip"
        //   x-real-ip, x-forwarded-for, x-forwarded-host
        if (request.getHeader("x-real-ip") != null)
            return request.getHeader("x-real-ip");
        return request.getRemoteHost();
    }

    public static void importJsp(String filename, HttpServletRequest request, HttpServletResponse response)
            throws javax.servlet.ServletException, java.io.IOException {

        PrintWriter out = response.getWriter();
        request.getRequestDispatcher(filename).include(request, response);

    }

}