/**
* Copyright (c) 2007 Ian Rae
* All Rights Reserved.
* Licensed under the Eclipse Public License - v 1.0
* For more information see http://www.eclipse.org/legal/epl-v10.html
*/
package org.speakright.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.speakright.core.IFlow;
import org.speakright.core.ISRExtensionFactory;
import org.speakright.core.ModelBinder;
import org.speakright.core.SRConfig;
import org.speakright.core.SRFactory;
import org.speakright.core.SRLocations;
import org.speakright.core.SRLogger;
import org.speakright.core.SRResults;
import org.speakright.core.SRRunner;
import org.speakright.core.RawCGIParams;
import org.speakright.core.render.ISpeechPageWriter;
import org.speakright.core.render.html.HTMLSpeechPageWriter;
import org.speakright.core.render.voicexml.VoiceXMLSpeechPageWriter;
/**
* Manages running a SpeakRight application inside a servlet.
* Is a lightweight wrapper around SRInstance to remove dependency
* of application code from it.
*
* Initialization works as follows. On the first HTTP request, the servlet creates an SRServletRunner and
* calls its createNewSRRunner method. This method extracts the servlet's url and physical file path. It
* invokes an SRFactory-derived object. The factory is separate from SRServletRunner in order to allow unit
* testing outside of servlets. The app defines the factory class, usually called AppFactory, that has two
* methods. OnCreateRunner is called once during the entire session and app-specific startup such as creating
* the model object are done here. The second method is OnInitRunner, which is done on *every* HTTP request.
* Here the app should init (or re-init) any transient information, such as registering prompt files.
* After initialization, the SRServletRunner's method startApp is called. It starts the SpeakRight application,
* which executes the first flow object, and renders it into VoiceXML. The startApp method writes the VoiceXML
* to the servlet's printwriter. The SpeakRight application's state is passivated and saved in a session variable.
*
* The servlet's doGet (or doPost) method is now finished and the VoiceXML page is executed on the speech platform.
* It contains 'submit' tags that do a postback to the servlet. The servlet's doGet or doPost creates a new
* SRServletRunner, and detects that the HTTP request is a postback (isNewSession returns false), so it calls
* continueApp. The SpeakRight applicstion's state is activated from its session variable. This will init
* all the non-transient fields. SRServletRunner then re-init the transient fields, calling AppFactory's
* onInitRequest method. It extracts the results (such as user input) from the CGI parameters and now it
* can continue the SpeakRight application. The next flow object runs and its generated VoiceXML is written
* to the servlet's printwriter. The SR app is passivated again and the new VoiceXML page executes on the
* speech platform. Further postbacks are treated in the same way.
*
* @author IanRaeLaptop
*
*/
public class SRServletRunner implements ISRExtensionFactory {
boolean m_doingHTML = false;
HttpServletRequest m_request;
HttpServletResponse m_response;
SRRunner m_run;
SRLogger m_logger;
ISRServlet m_self;
SRFactory m_factory;
public SRServletRunner(SRFactory factory, ISRServlet self, HttpServletRequest request, HttpServletResponse response, String httpActionName)
{
//do this here so we look for local log4j.properties file (in web-inf\classes) first
m_logger = SRLogger.createLogger();
m_logger.log("------ SERVLET: doing " + httpActionName + " ---------");
m_request = request;
m_response = response;
m_self = self;
m_factory = factory;
getMode(); //must do this before do getWriter
}
/**
* Get the print writer that will output the servlet's content (voicexml)
* @return
* @throws IOException
*/
public PrintWriter getWriter() throws IOException
{
PrintWriter out = m_response.getWriter();
return out;
}
/**
* Get the SpeakRight logger (log4j)
* @return
*/
public SRLogger logger()
{
return m_logger;
}
public void log(String msg)
{
m_logger.log(msg);
}
public SRRunner runner()
{
return m_run;
}
/**
* Is this a new session? A session is considered new if session.isNew is true or
* if it can't find the saved SRRunner object saved in the session.
* @return
*/
public boolean isNewSession()
{
HttpSession session = m_request.getSession();
boolean isNew = session.isNew();
String id = session.getId();
m_logger.log("SERVLET: " + ((isNew) ? "*** NEW SESSION ***" : "EXISTING SESSION") + " ID: " + id);
if (!isNew) {
SRRunner run = activateSavedSRRunner(m_request);
if (run == null)
{
m_logger.log("SERVLET: oops, no runner. what happened to our session?!!!!!!!!!!!!!");
return true;
}
}
return isNew;
}
boolean getMode()
{
//do this here so we look for local log4j.properties file (in web-inf\classes) first
SRLogger logger = SRLogger.createLogger();
// logger.log("in doGet!");
boolean b = false;
String str = m_request.getParameter("mode");
// logger.log("mode: " + str);
if (str!= null && str.equals("html"))
{
logger.log("doing html!");
b = true;
m_response.setContentType("text/html");
}
else { //vxml
logger.log("doing vxml!");
}
m_response.setHeader("pragma", "no-cache");
m_doingHTML = b;
return b;
}
String getUrl(HttpServletRequest request)
{
String scheme = request.getScheme(); // http
String serverName = request.getServerName(); // hostname.com
int serverPort = request.getServerPort(); // 80
String contextPath = request.getContextPath(); // /mywebapp
String servletPath = request.getServletPath(); // /servlet/MyServlet
// String pathInfo = request.getPathInfo(); // /a/b;c=123
// String queryString = request.getQueryString(); // d=789
// Reconstruct original requesting URL
String url = scheme+"://"+serverName+":"+serverPort+contextPath+servletPath;
return url;
}
String getServletUrl(HttpServletRequest request)
{
String scheme = request.getScheme(); // http
String serverName = request.getServerName(); // hostname.com
int serverPort = request.getServerPort(); // 80
String contextPath = request.getContextPath(); // /mywebapp
// String servletPath = request.getServletPath(); // /servlet/MyServlet
// String pathInfo = request.getPathInfo(); // /a/b;c=123
// String queryString = request.getQueryString(); // d=789
// Reconstruct original requesting URL
String url = scheme+"://"+serverName+":"+serverPort+contextPath; //+servletPath;
return url;
}
/**
* Create an SRRunner object for this session (phone call)
* @param servlet
* @return
*/
public SRRunner createNewSRRunner(HttpServlet servlet)
{
//init SRConfig, which must be done before SRConfig.getProperty can be called
ServletContext context = servlet.getServletContext();
String path = context.getRealPath("/");
SRConfig.init(path, "srf.properties");
// SRRunner run = new SRRunner();
String returnUrl = getUrl(m_request);
String baseUrl = getServletUrl(m_request);
m_run = m_factory.createRunner(path, returnUrl, baseUrl, m_self);
// m_logger.log("SpeakRight version: " + SRRunner.SPEAKRIGHT_VERSION);
m_run.setExtensionFactory(this);
// setupUrls(run, m_request);
// m_run = run;
// m_run.log("zzzrrrrrrrrrrrrrrrrrrrrr");
// run.locations().setProjectDir(path);
// m_self.initLocations(m_run);
m_run.log("config path: " + SRConfig.configPath());
return m_run;
}
/**
* Start the SpeakRight app. This MUST be called only once for each session (phone call),
* when isNewSession returns true.
* startApp will generate a single VoiceXML page and write it to the print writer.
* @param flow the application flow object
* @throws IOException
*/
public void startApp(IFlow flow) throws IOException
{
m_logger.log("SERVLET: startApp");
m_run.start(flow);
m_logger.log("SERVLET: writing content.");
String content = m_run.getContent();
PrintWriter out = getWriter();
out.print(content); //the html or voicexml
m_logger.log("SERVLET: saving state.");
saveSRRunner(m_request, false); //save new SRRunner
out.close();
m_logger.log("SERVLET: done.");
}
public void continueApp() throws IOException
{
String contentType = m_request.getContentType();
if (contentType != null && contentType.toLowerCase().indexOf("multipart") >= 0)
{
log("multipart (recording)..");
getAudioRecording(m_request);
}
doContinueApp();
}
/**
* Continues the SpeakRight app. This MUST be called once for each postback during a session
* (phone call).
* Will generate a single VoiceXML page and write it to the print writer.
* @throws IOException
*/
void doContinueApp() throws IOException
{
m_logger.log("SERVLET: continueApp");
if (m_run == null)
{
m_logger.log("SERVLET: continueApp: no runner. what happened to our session?!!!!!!!!!!!!!");
return;
}
// m_self.initLocations(m_run);
m_factory.initRunner(m_run, m_self);
PrintWriter out = getWriter();
m_run.restoreModelBinder(new ModelBinder());
boolean stillRunning = generateNextPage(m_request, out);
if (stillRunning) {
m_logger.log("SERVLET: writing content.");
String content = m_run.getContent();
out.print(content); //the html or voicexml
saveSRRunner(m_request, true); //save SRRunner again
out.close();
}
m_logger.log("SERVLET: done.");
}
/**
* Create a page writer that renders SpeakRight output content
* into VoiceXML or whatever markup text you want.
* @return
*/
public ISpeechPageWriter createPageWriter()
{
if (m_doingHTML)
return new HTMLSpeechPageWriter();
else
return new VoiceXMLSpeechPageWriter();
}
void saveSRRunner(HttpServletRequest request, boolean alreadyExists)
{
HttpSession session = request.getSession();
SRRunner savedRun = (SRRunner)session.getAttribute("runner");
if (!alreadyExists && savedRun != null)
{
m_logger.log("WARNING: new session GET and runner already exists!");
}
m_run.prepareToPassivate(); //must call this!
session.setAttribute("runner", m_run);
// System.out.println("saved SRRunner");
}
SRRunner activateSavedSRRunner(HttpServletRequest request)
{
SRLogger logger = SRLogger.createLogger();
HttpSession session = request.getSession();
SRRunner run = (SRRunner)session.getAttribute("runner");
if (run == null)
{
logger.log("POST and no runner!");
return null;
}
else
{
logger.log("POST restored saved session");
run.restoreModelBinder(new ModelBinder());
run.finishActivation(); //must call this!
run.setExtensionFactory(this);
// setupUrls(run, request);
m_factory.initUrls(run, getUrl(m_request), getServletUrl(m_request));
m_run = run;
return run;
}
}
boolean generateNextPage(HttpServletRequest request, java.io.PrintWriter out)
{
SRRunner run = m_run;
HttpSession session = request.getSession();
if (run.isFinished()) {
finish(out, session);
return false;
}
RawCGIParams rawParams = new RawCGIParams();
Enumeration paramNames = m_request.getParameterNames();
while (paramNames.hasMoreElements()) {
String name = (String) paramNames.nextElement();
String[] values = m_request.getParameterValues(name);
for (int i = 0; i < values.length; i++) {
//out.println(" " + values[i]);
//I assume most of the time, each param only has one value!
rawParams.add(name, values[i]);
}
}
SRResults results = new SRResults(rawParams);
run.proceed(results);
if (run.isFinished()) {
finish(out, session);
return false;
}
return true;
}
String getcgiparam(HttpServletRequest request, String param)
{
String result = request.getParameter(param);
if (result == null) {
result = "";
}
return result.trim();
}
void finish(java.io.PrintWriter out, HttpSession session)
{
writeFinPage(out);
session.invalidate(); //done
out.close();
}
void writeFinPage(java.io.PrintWriter out)
{
m_run.generateFinPage();
out.print(m_run.getContent()); //the html or voicexml
// out.println("<html>");
// out.println("<head>");
// out.println("<title>App has finished</title>");
// out.println("</head>");
// out.println("<body>");
// out.println("<h1>App has finished</h1>");
// out.println("</body>");
// out.println("</html>");
}
void getAudioRecording(HttpServletRequest request)
{
boolean SUCCESS = true; //always optimistic...
String res = ""; //default
try {
//set the destination for the recorded audio file
// ServletContext context = this.getServletContext();
// String dir = request.getRealPath("/audio"); //modify as necessary
String path = m_run.locations().projectDir();
String dir = path + "audio"; //"context.getRealPath("/audio"); //modify as necessary
log("recording dir: " + dir);
//process the request object, save the audio part, parse the posted variables
com.oreilly.servlet.MultipartRequest mr = new com.oreilly.servlet.MultipartRequest(request, dir);
log("mulitpart done.");
//assign our posted variables
res = mr.getParameter("sr__res");
log("sr__res: " + res);
} catch (Exception e) {
SUCCESS = false; //something went wrong.
}
}
}