Java tutorial
/** * This class is the servlet that controls the AJAX toaster. * * Copyright (C) 2007 Stephen Harding * * 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 3 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, see <http://www.gnu.org/licenses/>. * * Please send inquiries to; steve@inverse2.com * * $Revision: 1.21 $ * * $Log: AjaxToasterServlet.java,v $ * Revision 1.21 2008/07/24 11:23:36 stevewdh * The servlet will not write a blank string to the client as a response. * This is to enable an asynchronous custom service to write a response and then monitor for an event. * * Revision 1.20 2008/07/16 09:32:11 stevewdh * Made SMD work from top level URL, so you don't have to know a service name before you can ask for SMD. * * Revision 1.19 2008/07/11 14:59:43 stevewdh * The SMD for STX scripts is now automtically generated. * * Revision 1.18 2008/07/10 14:27:23 stevewdh * The list of users authorised to invoke a service is now properly checked. * * Revision 1.17 2008/07/08 16:06:24 stevewdh * The client may now specify that JSON data is returned via a callback function, using the parameter "callback" on the URL. * * Revision 1.16 2008/07/01 17:31:53 stevewdh * Moved generic logger into com.inverse.util package. * * Revision 1.15 2008/06/29 13:36:57 stevewdh * Changed so that we can setup AjaxToaster to log messages using either; Java, Log4J or buffered HTML logging. * * Revision 1.14 2008/06/25 14:21:25 stevewdh * Changed logging to LOG4J * * Revision 1.13 2008/06/25 13:36:57 stevewdh * Changes to the AjaxToaster.properties file are now detected by the servlet and cause it to refresh itself. * * Revision 1.12 2008/06/23 09:57:42 stevewdh * Updated version number. * * Revision 1.11 2008/06/20 15:52:23 stevewdh * Changed so that web context does not form part of the URI. * * Revision 1.10 2008/06/20 15:50:52 stevewdh * Changes for URI mapping. * * Revision 1.9 2008/06/19 14:58:10 stevewdh * Corrected build version number. * * Revision 1.8 2008/06/12 10:06:12 stevewdh * Added output of the version of XMLToaster we are using. * * Revision 1.7 2008/06/12 10:02:05 stevewdh * Try again with the version stuff! * * Revision 1.6 2008/06/12 09:59:39 stevewdh * Added version number information. * * Revision 1.5 2008/06/11 20:09:51 stevewdh * Added CONTEXTDIR substitution variable to database URL. * Had another go at getting the referenced libraries sorted out! * * Revision 1.4 2008/06/11 14:01:24 stevewdh * Implemented a more generic way of getting information about the available service operations. * Implemented automatic discovery of STX and XST parameters for the SMD. * * Revision 1.3 2008/06/04 14:59:08 stevewdh * Rationalised the response format stuff a little bit... and added RAW as a response type (text/plain). * * Revision 1.2 2008/06/03 14:34:59 stevewdh * *** empty log message *** * * Revision 1.1 2008/05/29 09:45:52 stevewdh * *** empty log message *** * * Revision 1.7 2008/04/17 13:48:53 stevewdh * Implemented custom services and fixed a couple of problems with the password implementation. * * Revision 1.6 2008/04/10 15:03:12 stevewdh * Refactored a bit and implemented service pool processing. * * Revision 1.5 2008/03/28 15:31:12 stevewdh * Fixed some inconsistencies in the admin functions. * * Revision 1.4 2008/03/26 18:24:58 stevewdh * Services can now be in a heirarchical directory structure. * * Revision 1.3 2008/03/11 18:03:29 stevewdh * Set ATTRIB_TOASTER_INITIALIZED at the end of the init method... this should allow us to set it to false to force refresh... * * The servlet now searches for the toaster script directory as follows; * * use the value of the servlet initialisation param; toaster.script.path * use the web directory + "/toaster" if that exists * use the web directory * * If the servlet has not been initialised then return error response... * * Revision 1.2 2008/03/10 15:59:47 stevewdh * Standardised headers. * * */ package com.inverse2.ajaxtoaster; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.util.ArrayList; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import javax.naming.InitialContext; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.sql.DataSource; import org.apache.log4j.PropertyConfigurator; import org.json.JSONException; import org.json.JSONObject; import org.json.XML; import com.inverse2.ajaxtoaster.interfaces.PriviledgedServiceHelper; import com.inverse2.ajaxtoaster.interfaces.ServiceOperationInterface; import com.inverse2.ajaxtoaster.interfaces.ServiceScriptHelper; import com.inverse2.util.Logger; import com.inverse2.ajaxtoaster.servicedescription.ServiceMappingDescription; import com.inverse2.ajaxtoaster.urimapping.ServiceMapping; import com.inverse2.ajaxtoaster.urimapping.ServiceURIMapper; /** * * @author Terry B and Steve H * @version */ public class AjaxToasterServlet extends HttpServlet implements ServiceScriptHelper, PriviledgedServiceHelper, Runnable { public static final int VERSION_MAJOR = 2; public static final int VERSION_MINOR = 0; public static final String VERSION_BUILD = "0.beta"; public static final int DEFAULT_POOL_CHECK_INTERVAL = 30000; private static Logger log; static final long serialVersionUID = 1L; // not really required /** Properties file */ /* This is the name of the file that specifies the name of the default database connection file. */ public static String PROPERTIES_FILE = "AjaxToaster.properties"; /* This is the name of the file that maps URI patterns to particulat AjaxToaster services - used for RESTful services. */ public static String URI_MAPPINGS_FILE = "AjaxToaster.urimappings"; public static String SCRIPT_PATH_INIT_PARAM = "toaster.script.path"; public static String PROP_DEFAULT_JNDI_DATABASE = "default_db_jndi_name"; public static String PROP_DEFAULT_JDBC_POOL = "default_db_jdbc_pool"; public static String PROP_RESPONSE_FORMAT = "response_format"; public static String PROP_LOGIN_CLASS = "LoginClass"; public static String PROP_TOASTERPOOL_REFRESH_INTERVAL = "ToasterPoolRefreshInterval"; public static String LOGGING_TYPE = "toaster.logging"; /** Servlet Context attributes */ /* These are the names of the global attributes used by the servlet context... */ public static String ATTRIB_SERVICE_POOL = "ServicePool"; public static String ATTRIB_LOGGED_IN = "LoggedIn"; // is the user logged in? public static String ATTRIB_LOGGED_IN_USER = "LoggedUser"; // user login name public static String ATTRIB_JDBC_CONN_POOL = "DBConnectionPool"; // JDBC Database connection pool - used if JNDI lookup to app-server conn pool isn't used. /** Request Parameters **/ public static String PARAM_SCRIPTNAME1 = "service"; // service=xxxx Specifies which toast script to run public static String PARAM_SCRIPTNAME2 = "toast"; // toast=xxxx Specifies which toast script to run public static String PARAM_INPUTXML = "inputxml"; // inputxml=xxxx XML message to apply against a toaster update script public static String PARAM_INPUTJSON = "inputjson"; // inputjson=xxxx Input JSON message to apply to a toaster update script public static String PARAM_RETURNXML = "returnxml"; // returnxml=true Returns output in XML format instead of the default JSON public static String PARAM_RETURNJSON = "returnjson"; // returnjson=true Returns output in JSON format public static String PARAM_RETURNRAW = "returnraw"; // returnraw=true Returns output in plain text format public static String PARAM_CALLBACK = "callback"; // callback=function Specifies that a JSON callback should be returned public static String PARAM_SMD = "smd"; // ?smd Return Service Mapping Description /** Private Globals **/ private String default_db_jndi_name = null; // holds the name of the DB connection to use by default private String default_db_jdbc_name = null; // holds the name of the DB connection to use by default private String response_format_prop = null; private ServicePool servicePool; private ServiceURIMapper serviceMapper; private Thread monitorPropertiesThread; private boolean monitorProperties; private String scriptPath; private String propertiesPath; private long sleepInterval; private long propertiesFileLastChanged; private String loggingType; public static String getVersion() { return (VERSION_MAJOR + "." + VERSION_MINOR + "." + VERSION_BUILD); } public String getWorkingDirectory() { return (scriptPath); } /** * Processes requests from the client for both HTTP <code>GET</code> * and <code>POST</code> methods. * * @param request servlet request * @param response servlet response */ protected void processRequest(String requestType, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String responseFormat = response_format_prop; // flags that the user has not set the response format boolean defaultResponseFormat = response_format_prop.equals("XML") ? true : false; ServiceOperationInterface service = null; String callbackFunction = null; log.info(">> Start processRequest(" + requestType + ") at " + new Date()); try { ServletContext context = getServletContext(); String scriptName = request.getParameter(PARAM_SCRIPTNAME1); // look for "service=xxxx" String contextPath = ""; /* If the service parameter is not specified then use the URL to get the service name... */ if (scriptName == null) { scriptName = request.getPathInfo(); contextPath = request.getContextPath(); /* //Put this in for debugging... System.out.println("****** -> pathInfo [" + request.getPathInfo() + "]"); System.out.println("****** -> pathTranslated [" + request.getPathTranslated() + "]"); System.out.println("****** -> contextPath [" + request.getContextPath() + "]"); System.out.println("****** -> localAddr [" + request.getLocalAddr() + "]"); System.out.println("****** -> localName [" + request.getLocalName() + "]"); System.out.println("****** -> requestURI [" + request.getRequestURI() + "]");//***** System.out.println("****** -> servletPath [" + request.getServletPath() + "]"); */ if (scriptName == null) { scriptName = "UNSPECIFIED_SERVICE"; } } /* See if the URI is mapped to another service... */ ServiceMapping serviceMapping; serviceMapping = serviceMapper.getURIMapping(""/*contextPath*/, scriptName, requestType); if (serviceMapping != null) { log.info("Redirect URI to [" + serviceMapping.getServiceName() + "]"); scriptName = serviceMapping.getServiceName(); /* If the URI has been mapped then see if the "Accept" header specifies the return type required... */ String accept = request.getHeader("Accept"); if (accept.indexOf("text/xml") != -1) { responseFormat = "XML"; defaultResponseFormat = false; } if (accept.indexOf("text/json") != -1) { responseFormat = "JSON"; defaultResponseFormat = false; } } if (scriptName.startsWith("/")) { scriptName = scriptName.substring(1, scriptName.length()); } /** * If "log" service invoked then process it... */ if (scriptName.equals("log")) { returnHTMLLog(response); return; } /** * If "health" service invoked then process it... */ if (scriptName.equals("health")) { returnHealth(response); return; } /* Check for the flag to return XML or JSON objects... */ if (request.getParameter(PARAM_RETURNXML) != null) { println(">> Servlet will return XML object."); responseFormat = "XML"; defaultResponseFormat = false; } else if (request.getParameter(PARAM_RETURNJSON) != null) { println(">> Servlet will return XML object."); responseFormat = "JSON"; defaultResponseFormat = false; } else if (request.getParameter(PARAM_RETURNRAW) != null) { println(">> Servlet will return raw text object."); responseFormat = "RAW"; defaultResponseFormat = false; } /* Check for the callback function parameter... */ callbackFunction = request.getParameter(PARAM_CALLBACK); /** * Check to see if the client wants a "Service Mapping Description" (SMD) for the 'service'... */ if (request.getParameter(PARAM_SMD) != null) { log.info("Client wants SMD for [" + scriptName + "]"); try { ServicePool pool = null; Map availableServices = null; ServiceMappingDescription smd = null; ServiceScriptPool serviceScriptPool = null; String serviceScriptName = null; String returnString = null; pool = (ServicePool) context.getAttribute(ATTRIB_SERVICE_POOL); availableServices = pool.getAvailableServices(); smd = new ServiceMappingDescription(request.getRequestURL().toString(), request.getRequestURL().toString() + "?smd", null); for (Iterator it = availableServices.values().iterator(); it.hasNext();) { serviceScriptPool = (ServiceScriptPool) it.next(); serviceScriptName = serviceScriptPool.getPoolName(); /** * If the service script name begins with the passed in script name then add it to the * service mapping description... */ log.debug("scriptName = [" + scriptName + "], serviceScriptName = [" + serviceScriptName + "]"); if (scriptName.equals("") || serviceScriptName.startsWith(scriptName + "/") || serviceScriptName.equals(scriptName)) { smd.addOperation(serviceScriptName); service = serviceScriptPool.getService(); smd.setOperationDescription(service.getScriptDescription()); smd.setOperationTransport(service.getHTTPMethods()); smd.setOperationEnvelope("URL"); smd.setOperationContentType(service.getResponseFormat()); smd.setOperationParameters(serviceScriptPool.getServiceParameters()); smd.setOperationReturns(serviceScriptPool.getServiceReturns()); } } returnString = smd.getSMDJSONString(); writeResponse(returnString, "JSONRAW", callbackFunction, response); } catch (Exception ex) { log.error("Exception getting SMD: " + ex.toString()); ex.printStackTrace(); } return; } /** * Get the service and run it... */ println(">> Client wants to invoke the service [" + scriptName + "]"); try { service = getServiceScript(scriptName); } catch (Exception ex) { errorResponse(response, "Could not get an instance of the service [" + scriptName + "]: " + ex.toString(), responseFormat, callbackFunction); return; } if (service == null) { errorResponse(response, "Service [" + scriptName + "] not found.", responseFormat, callbackFunction); return; } /** * If the script exists in the toaster pool then invoke it */ println(">> Checking login required"); try { if (service.getLoginRequired().equals("true")) { HttpSession session = request.getSession(false); Object loggedIn = null; if (session != null) { loggedIn = session.getAttribute(ATTRIB_LOGGED_IN); } log.trace("**** SESSION = " + session); log.trace("**** Logged In = " + loggedIn); if (session == null || loggedIn == null || loggedIn.equals("true") == false) { errorResponse(response, "The service " + scriptName + " requires you to be logged in to run it.", responseFormat, callbackFunction); freeServiceScript(service); return; } /* Check that the logged in user is authorised to run the service... */ String validUsers; String[] validUsersArray; String user; String loggedInUser; boolean validUser; validUsers = service.getValidUsers(); validUsersArray = validUsers.split("[,]"); loggedInUser = (String) session.getAttribute(ATTRIB_LOGGED_IN_USER); validUser = false; for (int idx = 0; idx < validUsersArray.length; idx++) { user = validUsersArray[idx].trim(); if (user.equals("*")) { validUser = true; break; } if (user.equals(loggedInUser)) { validUser = true; break; } } if (validUser == false) { log.error("The user [" + loggedInUser + "] is not authorised to invoke the service [" + scriptName + "]"); errorResponse(response, "You are not authorised to invoke the service [" + scriptName + "]", responseFormat, callbackFunction); freeServiceScript(service); return; } } } catch (Exception ex) { errorResponse(response, "Could not check if login required for this service. " + ex.toString(), responseFormat, callbackFunction); return; } boolean scriptInputSet = false; /* * Go through the set of parameters passed to us and set them up in the service instance... */ for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) { String parameterName = (String) e.nextElement(); if (parameterName.equals(PARAM_SCRIPTNAME1) == true || parameterName.equals(PARAM_SCRIPTNAME2) == true || parameterName.equals(PARAM_RETURNXML) == true || parameterName.equals(PARAM_RETURNJSON) == true || parameterName.equals(PARAM_CALLBACK) == true) { continue; } String parameterValue = (String) request.getParameter(parameterName); if (parameterName.equals(PARAM_INPUTXML) == true) { service.setInputXML(parameterValue); scriptInputSet = true; continue; } if (parameterName.equals(PARAM_INPUTJSON) == true) { try { // The input object is a JSON object... so convert it into XML... JSONObject json = new JSONObject(parameterValue); service.setInputXML(XML.toString(json)); scriptInputSet = true; println("JSON converted to \n" + XML.toString(json)); } catch (JSONException ex) { errorResponse(response, "Could not create JSON object." + ex.toString() + ". " + ex.getStackTrace(), responseFormat, callbackFunction); freeServiceScript(service); return; } continue; } /* Any leftover parameters are query parameters. */ println("Query Parameter found... Setting " + parameterName + " to " + parameterValue); service.setParameter(parameterName, parameterValue); } // End of parameters for loop /* If there is content in the request then, unless we have already set it, this is the input to the script... */ if (requestType.equals("POST") && scriptInputSet == false) { try { BufferedReader reader = request.getReader(); StringBuffer buf = new StringBuffer(); String line; String postData; while ((line = reader.readLine()) != null) { buf.append(line); } postData = buf.toString(); log.debug("POST DATA: " + postData); if (postData.startsWith("<")) { service.setInputXML(postData); scriptInputSet = true; } else { try { // The input object is a JSON object... so convert it into XML... JSONObject json = new JSONObject(postData); service.setInputXML(XML.toString(json)); scriptInputSet = true; log.debug("POST JSON converted to \n" + XML.toString(json)); } catch (JSONException ex) { errorResponse(response, "Could not convert POSTed JSON object." + ex.toString() + ". " + ex.getStackTrace(), responseFormat, callbackFunction); freeServiceScript(service); return; } } } catch (Exception ex) { log.warn("Exception getting posted data: " + ex.toString()); errorResponse(response, "Could not convert posted data.", responseFormat, callbackFunction); freeServiceScript(service); return; } } /* If the service name has been redirected then set any parameters that where embedded in the URI... */ if (serviceMapping != null) { Properties serviceParameters = serviceMapping.getParameters(); String paramName; String paramValue; for (Enumeration<Object> en = serviceParameters.keys(); en.hasMoreElements();) { paramName = (String) en.nextElement(); paramValue = (String) serviceParameters.get(paramName); service.setParameter(paramName, paramValue); } } String serviceResultString = null; /** * Run the service script... */ service.setSessionRequest(request); service.setSessionResponse(response); service.setCallbackFunction(callbackFunction); /* Check if the service has a predefined output format... */ /* If the user has specified a format then that is used.. */ String operationResponseFormat; operationResponseFormat = service.getResponseFormat(); if (defaultResponseFormat == true && operationResponseFormat != null && operationResponseFormat.equals("") == false) { responseFormat = operationResponseFormat; } service.setInvokeResponseFormat(responseFormat); /* If this is a priviledged operation then pass in a reference to the servlet... */ String priviledgedOperation = service.getPriviledged(); if (priviledgedOperation.compareToIgnoreCase("true") == 0 || priviledgedOperation.compareToIgnoreCase("yes") == 0 || priviledgedOperation.compareToIgnoreCase("y") == 0) { service.setPriviledgedHelper(this); } serviceResultString = service.invokeOperation(); if (serviceResultString == null) { errorResponse(response, "Error invoking the operation.<br><b>" + service.getScriptMessage() + "</b>", responseFormat, callbackFunction); freeServiceScript(service); return; } /* Return the results... */ if (serviceResultString != null && serviceResultString.equals("") == false) { writeResponse(serviceResultString, responseFormat, callbackFunction, response); } println(">> Service script executed successfully."); /* Free the service instance... */ freeServiceScript(service); } catch (Exception ex) { errorResponse(response, "Exception processing request: " + ex.toString(), responseFormat, callbackFunction); ex.printStackTrace(); try { freeServiceScript(service); } catch (Exception x) { log.warn("Exception freeing a service instance: " + x.toString()); } return; } println(">> Finished processRequest() at " + new Date()); } // processRequest() private void returnHealth(HttpServletResponse response) throws Exception { StringBuffer healthInfo = new StringBuffer(); healthInfo.append("AjaxToaster version " + getVersion() + "<br>"); healthInfo.append("Using XMLToaster version " + com.inverse2.xmltoaster.Version.getVersion() + "<br>"); healthInfo.append("Properties file name [" + propertiesPath + "]<br>"); healthInfo.append("Script directory [" + scriptPath + "]<br>"); healthInfo.append("Logging method [" + loggingType + "]<br>"); healthInfo.append("Sleep interval [" + sleepInterval + "]<br>"); healthInfo.append("<br>"); healthInfo.append(serviceMapper.getURIMappings().replaceAll("\\n", "<br>")); healthInfo.append("<br>"); healthInfo.append("List of operations available;<br><br>"); List<ServiceOperationInfo> serviceList = servicePool.getOperationInfo(); ServiceOperationInfo operation; for (Iterator<ServiceOperationInfo> it = serviceList.iterator(); it.hasNext();) { operation = it.next(); healthInfo.append("-- " + operation.getServiceName() + "/" + operation.getOperationName() + "<br>"); } Runtime rt = Runtime.getRuntime(); healthInfo.append("<br>JVM: Total Mem [" + rt.totalMemory() + "], Free Mem [" + rt.freeMemory() + "], Max Mem [" + rt.maxMemory() + "]<br>"); writeResponse(healthInfo.toString(), "HTML", null, response); } private void returnHTMLLog(HttpServletResponse response) throws Exception { String logdata = Logger.getHTMLLog(); writeResponse(logdata, "HTML", null, response); } /** Handles the HTTP <code>GET</code> method. * @param request servlet request * @param response servlet response */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest("GET", request, response); } /** Handles the HTTP <code>POST</code> method. * @param request servlet request * @param response servlet response */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest("POST", request, response); } /** * Returns a short description of the servlet. */ public String getServletInfo() { return ("AjaxToaster Servlet"); } /** * Tora! Tora! Tora! */ public void destroy() { log.info("Destroy the servlet..."); /* Stop the script pool... */ servicePool.stopMonitoring(); /* Stop the database connection pools... */ List poolList = getJDCBConnectionPoolList(); DBConnectionPool pool; for (Iterator it = poolList.iterator(); it.hasNext();) { pool = (DBConnectionPool) it.next(); pool.closePool(); } /* Stop monitoring for changes in the properties file... */ monitorProperties = false; try { monitorPropertiesThread.interrupt(); } catch (Exception ex) { } super.destroy(); } /** * Initialise the ToasterServlet - build the required service and database connection pools. */ public void init(ServletConfig config) throws ServletException { /* Perform servlet initialisation... */ super.init(config); ServletContext context = getServletContext(); Properties servletProperties = null; try { servletProperties = getAjaxToasterProperties(); loggingType = servletProperties.getProperty(LOGGING_TYPE); Logger.setLoggerType(loggingType); log = Logger.getLogger(AjaxToasterServlet.class.getName()); if (loggingType.equals(Logger.LOG4J_LOGGING_STR)) { PropertyConfigurator.configureAndWatch(propertiesPath); } } catch (Exception ex) { log = Logger.getLogger(AjaxToasterServlet.class.getName()); log.error("Exception initialising", ex); } log.info("AjaxToaster version " + getVersion()); log.info("Using XMLToaster version " + com.inverse2.xmltoaster.Version.getVersion()); if (servletProperties == null) { println("**** ERROR: Could not cache the AjaxToaster properties file."); return; } // Read response format of output returned to client - XML or JSON. response_format_prop = servletProperties.getProperty(PROP_RESPONSE_FORMAT); if (response_format_prop != null && response_format_prop.equalsIgnoreCase("JSON")) { response_format_prop = "JSON"; } else { response_format_prop = "XML"; } log.info(" Response format >> [" + response_format_prop + "]"); sleepInterval = getSleepInterval(servletProperties); // Create a new service pool servicePool = new ServicePool(sleepInterval, this); context.setAttribute(ATTRIB_SERVICE_POOL, servicePool); try { println("**** INFO: CREATING NEW SERVICE POOL - sleepInterval = " + sleepInterval + ", scriptPath=" + scriptPath); servicePool.initialise(scriptPath); servicePool.start(); } catch (Exception ex) { println("**** ERROR: Exception creating AjaxToaster service pool: " + ex.toString()); } /* Initialise the service mapping object... */ try { serviceMapper = new ServiceURIMapper(scriptPath, URI_MAPPINGS_FILE); serviceMapper.start(); } catch (Exception ex) { log.error("Exception setting up the Service URI Mapper: " + ex.toString()); ex.printStackTrace(); } initDBPools(servletProperties, context); /* Start a thread that will watch for changes in the servlet's properties file */ try { monitorProperties = true; monitorPropertiesThread = new Thread(this); monitorPropertiesThread.start(); } catch (Exception ex) { log.warn("Could not start a thread to watch for changes in the properties file: " + ex.toString()); } } // end init() private Properties getAjaxToasterProperties() throws Exception { return (getAjaxToasterProperties(false)); } private Properties getAjaxToasterProperties(boolean withMessages) throws Exception { /* Get a File that represents the directory where the toaster scripts are held... */ File scriptDirectory = getScriptDirectory(); if (scriptDirectory == null) { throw new Exception("**** ERROR: Could not locate the toaster script directory."); } if (withMessages) { println(">> TOASTER SCRIPT DIRECTORY: [" + scriptDirectory + "]"); } scriptPath = scriptDirectory.toString(); propertiesPath = scriptDirectory + File.separator + PROPERTIES_FILE; // Open the AjaxToaster Servlet properties file Properties servletProperties = new Properties(); File servletPropertiesFile; try { servletPropertiesFile = new File(propertiesPath); propertiesFileLastChanged = servletPropertiesFile.lastModified(); servletProperties.load(new FileInputStream(servletPropertiesFile)); } catch (Exception ex) { throw new Exception( "ERROR: Exception reading properties file (" + propertiesPath + ") : " + ex.toString()); } return (servletProperties); } private long getSleepInterval(Properties servletProperties) { String sleepIntervalRaw = ""; sleepInterval = DEFAULT_POOL_CHECK_INTERVAL; try { sleepIntervalRaw = "0" + servletProperties.getProperty(PROP_TOASTERPOOL_REFRESH_INTERVAL).trim(); println("sleepInterval==" + sleepIntervalRaw); sleepInterval = Long.parseLong(sleepIntervalRaw); } catch (Exception ex) { println("Exception reading the toaster pool refresh interval: " + ex.toString() + " - defaulting."); sleepInterval = DEFAULT_POOL_CHECK_INTERVAL; } return (sleepInterval); } /** * Perform initialisation necessary to setup AjaxToaster database pools... */ private void initDBPools(Properties servletProperties, ServletContext context) { // Read the default JNDI name or connection pool name to use when creating database connections. // held in AjaxToaster.properties as the "default_db_jndi_name" property. // This is used if there is no properties file with an overriding "db_jndi_name" property supplied for a toaster script. default_db_jndi_name = servletProperties.getProperty(PROP_DEFAULT_JNDI_DATABASE); default_db_jdbc_name = servletProperties.getProperty(PROP_DEFAULT_JDBC_POOL); println("**** Default JDBC pool = " + default_db_jdbc_name); println("**** Default JNDI name = " + default_db_jndi_name); /** * Create any jdbc connection pools * * Properties file format for connection pools is : * jdbc.class.[poolId] = [driver classname] * jdbc.database.[poolId] = [database to connect to] * jdbc.username.[poolId] = [user to connect as] * jdbc.password.[poolId] = [password] * * ...Where [id] is a unique identifier for the pool. It can be anything as long as it's unique per pool. * * There is no restriction on the number of connection pools that can be defined. */ for (Enumeration e = servletProperties.propertyNames(); e.hasMoreElements();) { String propertyName = (String) e.nextElement(); println("**** checking property.... " + propertyName); if (propertyName.startsWith("jdbc.database.")) { String poolId = propertyName.substring(propertyName.lastIndexOf(".")); poolId = poolId.replaceFirst("\\.", ""); String driver = servletProperties.getProperty("jdbc.driver." + poolId); String url = servletProperties.getProperty("jdbc.database." + poolId); String username = servletProperties.getProperty("jdbc.username." + poolId); String password = servletProperties.getProperty("jdbc.password." + poolId); // Not using a container managed database connection pool.... lets make our own! // Create a database connection pool... println("**** INFO: JDBC CONNECTION PROPERTIES FOR " + poolId + " - (" + servletProperties.getProperty(propertyName) + ")"); println("**** driver=" + driver); println("**** url=" + url); println("**** username=" + username); println("**** password=" + password); try { String contextDirectory = context.getRealPath("/"); DBConnectionPool dbpool = new DBConnectionPool(poolId, driver, url, username, password, contextDirectory); String attr = ATTRIB_JDBC_CONN_POOL + "." + poolId; println("Storing JDBC pool in " + attr); context.setAttribute(attr, dbpool); } catch (Exception ex) { println("**** ERROR: Exception creating a database connection pool: " + ex.toString()); // continue... some pools might be ok... } } } } // end initDBPools() /** * This method returns a File that points to the location of the toaster scripts... */ private File getScriptDirectory() throws Exception { ServletContext context = getServletContext(); String tmpScriptPath; File scriptDirectory; /* Check if the servlet initialisation parameter defines the script path... */ tmpScriptPath = context.getInitParameter(SCRIPT_PATH_INIT_PARAM); if (tmpScriptPath != null) { scriptDirectory = checkScriptPath(tmpScriptPath); if (scriptDirectory != null) { return (scriptDirectory); } } /* Get the system path to the web directory... check for the toaster scripts */ /* under this. */ tmpScriptPath = context.getRealPath("/"); if (tmpScriptPath == null) { /* null means that we cannot get the system directory... which probably */ /* means that the servlet is running from a WAR file. */ throw new Exception("Could not get the directory that the XMLToaster scripts are in."); } /* If a directory called "services" exists under the web directory.. then use that... */ String tmpScriptPathWithToaster = tmpScriptPath + File.separator + "services"; scriptDirectory = checkScriptPath(tmpScriptPathWithToaster); if (scriptDirectory != null) { return (scriptDirectory); } /* If the "services" directory does not exist then use the web directory... */ scriptDirectory = checkScriptPath(tmpScriptPath); if (scriptDirectory != null) { return (scriptDirectory); } /* We have run out of options... */ return (null); } private File checkScriptPath(String path) throws Exception { /* Expand the script path... */ /* Check that the script directory exists and is accessible... */ File scriptDirectory = new File(path); if (scriptDirectory.exists() == false) { /* Directory does not exist... */ throw new Exception("The AjaxToaster script directory [" + path + "] does not exist!"); } if (scriptDirectory.isDirectory() == false) { /* The path does not point to a directory... */ throw new Exception("The AjaxToaster script directory [" + path + "] is not a directory!"); } if (scriptDirectory.canRead() == false) { /* Directory is not readable... */ throw new Exception("The AjaxToaster script directory [" + path + "] is not readable!"); } return (scriptDirectory); } private void println(String msg) { log.info("{" + this.hashCode() + "} " + msg); } private void respondWithMessage(HttpServletResponse response, String messageType, String description, Exception exception, String responseFormat, String callbackFunction) throws ServletException, IOException { try { PrintWriter out = response.getWriter(); String format = null; String callbackFunctionTail = ");"; if (responseFormat == null) { responseFormat = "JSON"; } if (callbackFunction == null) { callbackFunction = ""; callbackFunctionTail = ""; } else { callbackFunction = callbackFunction + "("; } format = convertResponseFormat(responseFormat); response.setContentType(format); response.setHeader("Cache-Control", "no-cache, must-revalidate"); response.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT"); if (responseFormat.equals("XML")) { // print the error message in XML format out.println( callbackFunction + "<" + messageType + "><description>" + description + "</description>"); if (exception != null) { out.println("<detail>" + exception.toString() + "</detail>"); } else { out.println("<detail/>"); } out.println("</" + messageType + ">" + callbackFunctionTail); } else if (responseFormat.equals("JSON")) { // print the error message in JSON format out.println(callbackFunction + "{\"" + messageType + "\":{\"detail\":\"" + description + "\",\"description\":\"" + (exception == null ? "" : exception.toString()) + "\"}}" + callbackFunctionTail); } else { // output raw out.println(messageType + ":" + description); out.println("Exception: " + exception.toString()); } out.close(); } catch (Exception ex) { log.error("ERROR printing error - " + ex.toString() + ". " + ex.getStackTrace()); } } public void errorResponse(HttpServletResponse response, String errString, String responseFormat, String callbackFunction) { println(errString); try { respondWithMessage(response, "error", errString, null, responseFormat, callbackFunction); } catch (ServletException ex) { log.error("ERROR Servlet exception printing error - " + ex.toString() + ". " + ex.getStackTrace()); } catch (IOException ex) { log.error("ERROR IO Exception printing error - " + ex.toString() + ". " + ex.getStackTrace()); } return; } public void successResponse(HttpServletResponse response, String message, String responseFormat, String callbackFunction) { println(message); try { respondWithMessage(response, "success", message, null, responseFormat, callbackFunction); } catch (ServletException ex) { log.error("ERROR Servlet exception printing error - " + ex.toString() + ". " + ex.getStackTrace()); } catch (IOException ex) { log.error("ERROR IO Exception printing error - " + ex.toString() + ". " + ex.getStackTrace()); } return; } public void writeResponse(String responseMessage, String responseFormat, String callbackFunction, HttpServletResponse response) throws Exception { String format; String callbackFunctionTail = ");"; if (responseFormat == null) { responseFormat = "JSON"; } if (callbackFunction == null) { callbackFunction = ""; callbackFunctionTail = ""; } else { callbackFunction = callbackFunction + "("; } format = convertResponseFormat(responseFormat); response.setContentType(format); response.setHeader("Cache-Control", "no-cache, must-revalidate"); response.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT"); PrintWriter out = response.getWriter(); if (responseFormat.equals("JSON")) { try { /* TODO The "force singleton objects to arrays flag should be specified by the script properties... */ JSONObject json = XML.toJSONObject(responseMessage, // result JSON true, // force singleton objects to be arrays "rows" // array exception list ); println(">> Return JSON: " + json.toString()); out.println(callbackFunction + json.toString() + callbackFunctionTail); } catch (JSONException ex) { errorResponse(response, "ERROR - JSON Exception converting XML to JSON object. " + ex.toString() + ". " + ex.getStackTrace(), responseFormat, callbackFunction); return; } } else { out.println(callbackFunction + responseMessage + callbackFunctionTail); log.debug(">> Return Data: " + responseMessage); } out.close(); } private String convertResponseFormat(String responseFormat) { String convertedFormat; if (responseFormat.equals("XML")) { convertedFormat = "text/xml;charset=UTF-8"; } else if (responseFormat.equals("JSON") || responseFormat.equals("JSONRAW")) { convertedFormat = "text/json;charset=UTF-8"; } else if (responseFormat.equals("RAW")) { convertedFormat = "text/plain"; } else if (responseFormat.equals("HTML")) { convertedFormat = "text/html"; } else { convertedFormat = "text/plain"; } return (convertedFormat); } public ServiceOperationInterface getServiceScript(String scriptName) throws Exception { ServletContext context = getServletContext(); ServicePool pool = null; ServiceOperationInterface toaster = null; Connection connection = null; DBConnectionPool dbpool = null; String db_jndi_name = null; String db_jdbc_poolname = null; try { pool = (ServicePool) context.getAttribute(ATTRIB_SERVICE_POOL); } catch (Exception ex) { log.error("Error getting the toaster pool from the servlet context [" + ATTRIB_SERVICE_POOL + "]"); throw new Exception("ERROR - could not get toast pool from server context [" + ATTRIB_SERVICE_POOL + "]: " + ex.toString(), ex); } if (pool == null) { /* Toaster pool is not set-up - this is very bad...! */ log.error("The toaster pool does not exists in the servlet context."); throw new Exception("The toaster pool does not exist in the servlet context."); } try { toaster = pool.getService(scriptName); } catch (Exception ex) { log.error("Exception getting a toaster instance: " + ex.toString()); throw new Exception( "ERROR - could not get toaster instance from pool for " + scriptName + ": " + ex.toString(), ex); } /** * DATABASE CONNECTION... * if the DbJDNI property is set to non null value, or the default connection is a JNDI one, * then try to use JNDI. */ println(">> Starting database connection"); db_jndi_name = toaster.getDbJNDI(); // get jndi name from the scripts' properties file (if any) db_jdbc_poolname = toaster.getJDBCpoolname(); // get jdbc name from the scripts' properties file (if any) if (db_jndi_name != null || default_db_jndi_name != null) { // JNDI - Container managed connection pool. // Lookup the JNDI name for the connection pool and create the database connection. if (db_jndi_name == null) { db_jndi_name = default_db_jndi_name; } println("Looking up database connection for JNDI name " + db_jndi_name + "... " + "An exception here probably indicates that the JNDI name or corresponing connection pool isn't setup on your application server."); try { // The following code for Websphere 6... InitialContext ic = new InitialContext(); DataSource dataSource = null; dataSource = (DataSource) ic.lookup(db_jndi_name); connection = dataSource.getConnection(); } catch (Exception ex) { log.error("Exception getting JNDI database connection: " + ex.toString()); throw new Exception("ERROR - getting a connection to the database (using JNDI name " + db_jndi_name + "): " + ex.toString(), ex); } } else if (db_jdbc_poolname != null || default_db_jdbc_name != null) { if (db_jdbc_poolname == null) { db_jdbc_poolname = default_db_jdbc_name; // it isn't, so use the default one. } // find the connection pool in the servlet context try { String attr = ATTRIB_JDBC_CONN_POOL + "." + db_jdbc_poolname; println(">> Trying to get DB connection pool (" + attr + ")"); dbpool = (DBConnectionPool) context.getAttribute(attr); } catch (Exception ex) { log.error("Could not get a database connection from the pool: " + ex.toString()); throw new Exception("ERROR - could not get DB connection pool from server context (" + ATTRIB_JDBC_CONN_POOL + "." + db_jdbc_poolname + ") " + ex.toString(), ex); } if (dbpool == null) { if (toaster != null) { pool.freeService(scriptName, toaster); } throw new Exception("ERROR - The database connection pool has not been created (dbpool for " + ATTRIB_JDBC_CONN_POOL + "." + db_jdbc_poolname + " is null)."); } try { connection = dbpool.getConnection(); } catch (Exception ex) { if (toaster != null) { pool.freeService(scriptName, toaster); } throw new Exception("ERROR - could not get DB connection from DB connection pool. " + ex.toString(), ex); } } /* * Give the Toaster the database connection we created earlier... */ toaster.setConnection(connection); return (toaster); } public void freeServiceScript(ServiceOperationInterface toaster) throws Exception { ServletContext context = getServletContext(); ServicePool pool = null; DBConnectionPool dbpool = null; if (toaster != null) { Connection connection = toaster.getConnection(); if (connection != null) { String attr = ATTRIB_JDBC_CONN_POOL + "." + toaster.getJDBCpoolname(); dbpool = (DBConnectionPool) context.getAttribute(attr); toaster.setConnection(null); if (dbpool != null) { dbpool.freeConnection(connection); } } pool = (ServicePool) context.getAttribute(ATTRIB_SERVICE_POOL); pool.freeService(toaster.getScriptName(), toaster); } return; } public void errorResponse(String errString, String responseFormat, ServiceOperationInterface service) { errorResponse(service.getSessionResponse(), errString, responseFormat, service.getCallbackFunction()); } public void successResponse(String message, String responseFormat, ServiceOperationInterface service) { successResponse(service.getSessionResponse(), message, responseFormat, service.getCallbackFunction()); } public void writeResponse(String responseMessage, String responseFormat, ServiceOperationInterface service) throws Exception { writeResponse(responseMessage, responseFormat, service.getCallbackFunction(), service.getSessionResponse()); } public String getLoggedInUser(ServiceOperationInterface service) { String loggedInUser; HttpServletRequest request; HttpSession session; request = service.getSessionRequest(); session = request.getSession(); if (session == null) { return (null); } loggedInUser = (String) session.getAttribute(ATTRIB_LOGGED_IN_USER); return (loggedInUser); } public void setLoggedInUser(ServiceOperationInterface service, String username) { HttpServletRequest request; HttpSession session; request = service.getSessionRequest(); session = request.getSession(true); session.setAttribute(ATTRIB_LOGGED_IN, "true"); session.setAttribute(ATTRIB_LOGGED_IN_USER, username); } public void logout(ServiceOperationInterface service) { HttpServletRequest request; HttpSession session; request = service.getSessionRequest(); session = request.getSession(true); session.setAttribute(ATTRIB_LOGGED_IN, "false"); session.removeAttribute(ATTRIB_LOGGED_IN_USER); } public void resetServices(ServiceOperationInterface service) throws Exception { // TODO Auto-generated method stub throw new Exception("This method is not currently supported."); } public ServicePool getServicePool() { ServletContext context = getServletContext(); ServicePool pool = null; pool = (ServicePool) context.getAttribute(ATTRIB_SERVICE_POOL); return (pool); } public List getJDCBConnectionPoolList() { ServletContext context = getServletContext(); DBConnectionPool pool = null; ArrayList poolList = new ArrayList(); for (Enumeration e = context.getAttributeNames(); e.hasMoreElements();) { String attribName = (String) e.nextElement(); if (attribName.startsWith(ATTRIB_JDBC_CONN_POOL)) { pool = (DBConnectionPool) context.getAttribute(attribName); if (pool != null) { poolList.add(pool); } } } return (poolList); } /** * Monitor the servlet's properties file... and refresh if the file changes... */ public void run() { long propertiesFileLastRefreshed; Properties servletProperties; propertiesFileLastRefreshed = propertiesFileLastChanged; while (monitorProperties) { try { Thread.sleep(sleepInterval); } catch (Exception ex) { log.warn("Properties monitor thread had exception: " + ex.toString()); continue; } try { /* Calling this method caches the properties file and gets when it last changed... */ servletProperties = getAjaxToasterProperties(); if (propertiesFileLastChanged > propertiesFileLastRefreshed) { log.info("Servlet properties file has changed... will refresh..."); /* Refresh the sleep interval... */ long sleep; sleep = getSleepInterval(servletProperties); servicePool.setSleepInterval(sleep); /* Refresh the JDBC database connection pools... */ initDBPools(servletProperties, getServletContext()); /* Remember when we last refreshed... */ propertiesFileLastRefreshed = propertiesFileLastChanged; } } catch (Exception ex) { log.error("Exception monitoring the properties file.", ex); } } // end while monitoring } // end run() }