001/* 002 * Copyright (c) 2013 Nu Echo Inc. All rights reserved. 003 */ 004 005package com.nuecho.rivr.voicexml.servlet; 006 007import java.io.*; 008import java.util.*; 009import java.util.regex.*; 010 011import javax.json.*; 012import javax.servlet.*; 013import javax.servlet.http.*; 014 015import org.apache.commons.fileupload.*; 016import org.apache.commons.fileupload.servlet.*; 017 018import com.nuecho.rivr.core.servlet.*; 019import com.nuecho.rivr.core.util.*; 020import com.nuecho.rivr.voicexml.turn.first.*; 021import com.nuecho.rivr.voicexml.turn.input.*; 022import com.nuecho.rivr.voicexml.util.*; 023import com.nuecho.rivr.voicexml.util.json.*; 024 025/** 026 * VoiceXML specialization of {@link InputTurnFactory}. 027 * 028 * @author Nu Echo Inc. 029 */ 030public final class VoiceXmlInputTurnFactory implements InputTurnFactory<VoiceXmlInputTurn, VoiceXmlFirstTurn> { 031 public static final String INPUT_TURN_PARAMETER = "inputTurn"; 032 public static final String ROOT_PATH = "/root/"; 033 034 //general 035 036 //recording-related 037 public static final String RECORDING_PARAMETER = "recording"; 038 public static final String RECORDING_META_DATA_PROPERTY = "recordingMetaData"; 039 public static final String TERM_CHAR_PROPERTY = "termChar"; 040 public static final String MAX_TIME_PROPERTY = "maxTime"; 041 public static final String DURATION_PROPERTY = "duration"; 042 043 //event-related 044 public static final String EVENTS_PROPERTY = "events"; 045 public static final String EVENT_NAME_PROPERTY = "name"; 046 public static final String EVENT_MESSAGE_PROPERTY = "message"; 047 048 //recognition-related 049 public static final String DTMF_INPUTMODE_VALUE = "dtmf"; 050 public static final String VOICE_INPUTMODE_VALUE = "voice"; 051 public static final String INPUT_MODE_PROPERTY = "inputMode"; 052 public static final String MARK_PROPERTY = "mark"; 053 public static final String MARK_NAME_PROPERTY = "name"; 054 public static final String MARK_TIME_PROPERTY = "time"; 055 public static final String RECOGNITION_PROPERTY = "recognition"; 056 public static final String RESULT_PROPERTY = "result"; 057 058 //transfer-related 059 public static final String TRANSFER_PROPERTY = "transfer"; 060 public static final String TRANSFER_DURATION_PROPERTY = "duration"; 061 public static final String TRANSFER_STATUS_PROPERTY = "status"; 062 063 //for subdialogue, script and object 064 public static final String VALUE_PROPERTY = "value"; 065 066 private static final Pattern CHAR_SET_PATTERN = Pattern.compile("charset\\s*=\\s*([^ ;]+)"); 067 068 @Override 069 public VoiceXmlFirstTurn createFirstTurn(HttpServletRequest request, HttpServletResponse response) 070 throws InputTurnFactoryException { 071 072 Map<String, String> parameters = new HashMap<String, String>(); 073 074 @SuppressWarnings("unchecked") 075 Enumeration<String> parameterNames = request.getParameterNames(); 076 while (parameterNames.hasMoreElements()) { 077 String parameterName = parameterNames.nextElement(); 078 parameters.put(parameterName, request.getParameter(parameterName)); 079 } 080 081 return new VoiceXmlFirstTurn(parameters); 082 } 083 084 @Override 085 public VoiceXmlInputTurn createInputTurn(HttpServletRequest request, HttpServletResponse response) 086 throws InputTurnFactoryException { 087 088 Map<String, String> parameters = new HashMap<String, String>(); 089 Map<String, FileUpload> files = new HashMap<String, FileUpload>(); 090 processRequestParametersAndFiles(request, parameters, files); 091 092 String result = parameters.get(INPUT_TURN_PARAMETER); 093 094 if (result == null) 095 throw new InputTurnFactoryException("Unable to process request. Missing '" 096 + INPUT_TURN_PARAMETER 097 + "' parameter."); 098 099 JsonReader jsonReader = JsonUtils.createReader(result); 100 JsonObject resultObject = jsonReader.readObject(); 101 VoiceXmlInputTurn voiceXmlInputTurn = new VoiceXmlInputTurn(); 102 voiceXmlInputTurn.setFiles(files); 103 104 addEvents(resultObject, voiceXmlInputTurn); 105 addObject(resultObject, voiceXmlInputTurn); 106 addTransferStatusInfo(resultObject, voiceXmlInputTurn); 107 addRecognitionInfo(resultObject, voiceXmlInputTurn); 108 addRecordingInfo(resultObject, voiceXmlInputTurn, files); 109 return voiceXmlInputTurn; 110 } 111 112 private static void addEvents(JsonObject resultObject, VoiceXmlInputTurn voiceXmlInputTurn) { 113 if (!resultObject.containsKey(EVENTS_PROPERTY)) return; 114 115 List<JsonValue> eventObjects = resultObject.getJsonArray(EVENTS_PROPERTY); 116 List<VoiceXmlEvent> events = new ArrayList<VoiceXmlEvent>(); 117 for (JsonValue jsonValue : eventObjects) { 118 JsonObject eventObject = (JsonObject) jsonValue; 119 120 String name = eventObject.getString(EVENT_NAME_PROPERTY); 121 String message = eventObject.getString(EVENT_MESSAGE_PROPERTY, null); 122 123 events.add(new VoiceXmlEvent(name, message)); 124 } 125 126 voiceXmlInputTurn.setEvents(events); 127 } 128 129 private static void addObject(JsonObject resultObject, VoiceXmlInputTurn voiceXmlInputTurn) { 130 if (!resultObject.containsKey(VALUE_PROPERTY)) return; 131 132 JsonValue subdialogueResultJsonValue = resultObject.get(VALUE_PROPERTY); 133 voiceXmlInputTurn.setJsonValue(subdialogueResultJsonValue); 134 } 135 136 private static void addTransferStatusInfo(JsonObject resultObject, VoiceXmlInputTurn voiceXmlInputTurn) { 137 if (!resultObject.containsKey(TRANSFER_PROPERTY)) return; 138 139 JsonObject transferResultObject = resultObject.getJsonObject(TRANSFER_PROPERTY); 140 141 TransferStatus transferStatus = new TransferStatus(transferResultObject.getString(TRANSFER_STATUS_PROPERTY)); 142 143 long durationValue; 144 145 if (transferResultObject.containsKey(TRANSFER_DURATION_PROPERTY)) { 146 durationValue = JsonUtils.getLongProperty(transferResultObject, TRANSFER_DURATION_PROPERTY); 147 } else { 148 durationValue = 0; 149 } 150 151 Duration duration = Duration.milliseconds(durationValue); 152 153 voiceXmlInputTurn.setTransferResult(new TransferStatusInfo(transferStatus, duration)); 154 } 155 156 private static void addRecordingInfo(JsonObject resultObject, 157 VoiceXmlInputTurn voiceXmlInputTurn, 158 Map<String, FileUpload> files) { 159 if (!resultObject.containsKey(RECORDING_META_DATA_PROPERTY)) return; 160 161 JsonObject recordingMetaData = resultObject.getJsonObject(RECORDING_META_DATA_PROPERTY); 162 163 Duration duration; 164 if (recordingMetaData.containsKey(DURATION_PROPERTY)) { 165 long durationInMilliseconds = JsonUtils.getLongProperty(recordingMetaData, DURATION_PROPERTY); 166 duration = Duration.milliseconds(durationInMilliseconds); 167 } else { 168 duration = null; 169 } 170 171 boolean maxTime = recordingMetaData.getBoolean(MAX_TIME_PROPERTY, false); 172 173 String dtmfTermChar = recordingMetaData.getString(TERM_CHAR_PROPERTY, null); 174 175 FileUpload file; 176 if (!files.containsKey(RECORDING_PARAMETER)) { 177 file = null; 178 } else { 179 file = files.get(RECORDING_PARAMETER); 180 } 181 182 voiceXmlInputTurn.setRecordingInfo(new RecordingInfo(file, duration, maxTime, dtmfTermChar)); 183 } 184 185 private void addRecognitionInfo(JsonObject jsonObject, VoiceXmlInputTurn voiceXmlInputTurn) { 186 187 if (!jsonObject.containsKey(RECOGNITION_PROPERTY)) return; 188 189 JsonObject recognitionObject = jsonObject.getJsonObject(RECOGNITION_PROPERTY); 190 191 JsonArray recognitionResultArray = recognitionObject.getJsonArray(RESULT_PROPERTY); 192 193 MarkInfo markInfo = null; 194 if (recognitionObject.containsKey(MARK_PROPERTY)) { 195 JsonObject markObject = recognitionObject.getJsonObject(MARK_PROPERTY); 196 long timeInMilliseconds = JsonUtils.getLongProperty(markObject, MARK_TIME_PROPERTY); 197 markInfo = new MarkInfo(markObject.getString(MARK_NAME_PROPERTY), Duration.milliseconds(timeInMilliseconds)); 198 } 199 200 voiceXmlInputTurn.setRecognitionInfo(new RecognitionInfo(recognitionResultArray, markInfo)); 201 } 202 203 private void processRequestParametersAndFiles(HttpServletRequest request, 204 Map<String, String> parameters, 205 Map<String, FileUpload> files) throws InputTurnFactoryException { 206 if (ServletFileUpload.isMultipartContent(request)) { 207 ServletFileUpload servletFileUpload = new ServletFileUpload(); 208 try { 209 FileItemIterator itemIterator = servletFileUpload.getItemIterator(request); 210 while (itemIterator.hasNext()) { 211 FileItemStream fileItemStream = itemIterator.next(); 212 213 byte[] bytes; 214 String parameterName = fileItemStream.getFieldName(); 215 try { 216 InputStream stream = fileItemStream.openStream(); 217 bytes = IOUtils.toByteArray(stream); 218 } catch (IOException exception) { 219 throw new ServletException("Unable to read stream from " + parameterName, exception); 220 } 221 222 if (!fileItemStream.isFormField()) { 223 FileUpload fileUpload = new FileUpload(fileItemStream.getName(), 224 fileItemStream.getContentType(), 225 bytes, 226 getHeaders(fileItemStream)); 227 228 files.put(parameterName, fileUpload); 229 } else { 230 String encoding = findEncoding(request, fileItemStream); 231 try { 232 parameters.put(parameterName, new String(bytes, encoding)); 233 } catch (UnsupportedEncodingException exception) { 234 throw new Exception("Unable to find encoding '" 235 + encoding 236 + "'. Cannot decode text form field '" 237 + parameterName 238 + "' of multipart request.", exception); 239 } 240 } 241 242 } 243 } catch (Exception exception) { 244 throw new InputTurnFactoryException("Unable to get recording.", exception); 245 } 246 } else { 247 @SuppressWarnings("unchecked") 248 Enumeration<String> parameterNames = request.getParameterNames(); 249 while (parameterNames.hasMoreElements()) { 250 String parameterName = parameterNames.nextElement(); 251 parameters.put(parameterName, request.getParameter(parameterName)); 252 } 253 } 254 255 } 256 257 private String findEncoding(HttpServletRequest request, FileItemStream fileItemStream) { 258 259 String encoding = null; 260 261 String contentType = fileItemStream.getContentType(); 262 263 if (contentType != null && contentType.startsWith("text/plain")) { 264 Matcher matcher = CHAR_SET_PATTERN.matcher(contentType); 265 if (matcher.find()) { 266 encoding = matcher.group(1); 267 } else { 268 encoding = Encoding.US_ASCII.getId(); 269 } 270 } 271 272 if (encoding == null) { 273 encoding = request.getCharacterEncoding(); 274 } 275 276 if (encoding == null) { 277 encoding = Encoding.US_ASCII.getId(); 278 } 279 280 return encoding; 281 } 282 283 private Map<String, String> getHeaders(FileItemStream fileItemStream) { 284 Map<String, String> headers = new HashMap<String, String>(); 285 FileItemHeaders sourceHeaders = fileItemStream.getHeaders(); 286 287 if (sourceHeaders == null) return headers; 288 289 @SuppressWarnings("unchecked") 290 Iterator<String> headerNames = sourceHeaders.getHeaderNames(); 291 while (headerNames.hasNext()) { 292 String headerName = headerNames.next(); 293 headers.put(headerName, sourceHeaders.getHeader(headerName)); 294 } 295 return headers; 296 } 297}