001/* 002MIT License 003 004Copyright (c) 2020 FBSQL Team 005 006Permission is hereby granted, free of charge, to any person obtaining a copy 007of this software and associated documentation files (the "Software"), to deal 008in the Software without restriction, including without limitation the rights 009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010copies of the Software, and to permit persons to whom the Software is 011furnished to do so, subject to the following conditions: 012 013The above copyright notice and this permission notice shall be included in all 014copies or substantial portions of the Software. 015 016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022SOFTWARE. 023 024Home: https://fbsql.github.io 025E-Mail: fbsql.team@gmail.com 026*/ 027 028package org.fbsql.servlet; 029 030import static org.fbsql.servlet.StringUtils.q; 031 032import java.io.BufferedReader; 033import java.io.IOException; 034import java.io.OutputStream; 035import java.io.StringReader; 036import java.io.Writer; 037import java.lang.reflect.InvocationTargetException; 038import java.lang.reflect.Method; 039import java.nio.charset.StandardCharsets; 040import java.sql.Blob; 041import java.sql.CallableStatement; 042import java.sql.Clob; 043import java.sql.Date; 044import java.sql.ParameterMetaData; 045import java.sql.PreparedStatement; 046import java.sql.ResultSet; 047import java.sql.SQLException; 048import java.sql.Time; 049import java.sql.Timestamp; 050import java.sql.Types; 051import java.text.MessageFormat; 052import java.text.ParseException; 053import java.time.Instant; 054import java.util.ArrayList; 055import java.util.Arrays; 056import java.util.Base64.Decoder; 057import java.util.Calendar; 058import java.util.Collection; 059import java.util.Collections; 060import java.util.LinkedHashMap; 061import java.util.List; 062import java.util.Locale; 063import java.util.Map; 064import java.util.Queue; 065import java.util.TimeZone; 066 067import javax.servlet.AsyncContext; 068import javax.servlet.http.Cookie; 069import javax.servlet.http.HttpServletRequest; 070import javax.servlet.http.HttpServletResponse; 071import javax.servlet.http.HttpSession; 072 073import org.fbsql.antlr4.parser.ParseNativeStmt; 074import org.fbsql.antlr4.parser.ParseStmtConnectTo; 075import org.fbsql.antlr4.parser.ParseStmtConnectTo.StmtConnectTo; 076import org.fbsql.antlr4.parser.ParseStmtDeclareStatement.StmtDeclareStatement; 077import org.fbsql.connection_pool.ConnectionPoolManager; 078import org.fbsql.connection_pool.DbConnection; 079import org.fbsql.json.parser.JsonUtils; 080import org.mozilla.javascript.Context; 081import org.mozilla.javascript.Function; 082import org.mozilla.javascript.NativeArray; 083import org.mozilla.javascript.NativeJSON; 084import org.mozilla.javascript.NativeObject; 085import org.mozilla.javascript.Scriptable; 086 087/** 088 * <p><strong>DbRequestProcessor</strong> contains the processing logic that 089 * the @link(DbServlet) performs as it receives each servlet request 090 * from the container.</p> 091 */ 092 093public class DbRequestProcessor implements Runnable { 094 095 /** 096 * SQL NULL constant 097 */ 098 private static final String SQL_NULL = "NULL"; // SQL NULL constant 099 100 /** 101 * SQL FALSE constant 102 */ 103 private static final String SQL_FALSE = "0"; // SQL FALSE constant 104 105 /** 106 * SQL TRUE constant 107 */ 108 private static final String SQL_TRUE = "1"; // SQL TRUE constant 109 110 /** 111 * SQL quote character 112 */ 113 private static final String SQL_QUOTE_CHAR = "'"; // SQL quote character 114 115 /** 116 * Execute type: query 117 * WARNING! This constant used also in JavaScript part 118 */ 119 private static final String EXEC_TYPE_QUERY = "Q"; 120 121 /** 122 * Execute type: update 123 * WARNING! This constant used also in JavaScript part 124 */ 125 private static final String EXEC_TYPE_UPDATE = "U"; 126 127 /* 128 * Built-in functions 129 */ 130 131 /* functions with parameter */ 132 private static final String FUT_IN_ROLE = "IN_ROLE("; 133 private static final String FUT_GET_COOKIE = "COOKIE("; 134 private static final String FUT_GET_HTTP_SESSION_ATTRIBUTE_AS_CHAR = "GET_HTTP_SESSION_ATTRIBUTE_AS_CHAR("; 135 private static final String FUT_GET_HTTP_SESSION_ATTRIBUTE_AS_INTEGER = "GET_HTTP_SESSION_ATTRIBUTE_AS_INTEGER("; 136 private static final String FUT_GET_HTTP_HEADER_AS_CHAR = "GET_HTTP_HEADER_AS_CHAR("; 137 private static final String FUT_GET_HTTP_HEADER_AS_DATE = "GET_HTTP_HEADER_AS_DATE("; 138 private static final String FUT_GET_HTTP_HEADER_AS_INTEGER = "GET_HTTP_HEADER_AS_INTEGER("; 139 140 /* functions without parameters */ 141 private static final String FUN_REMOTE_USER = "REMOTE_USER()"; 142 private static final String FUN_REMOTE_ROLE = "REMOTE_ROLE()"; 143 private static final String FUN_REMOTE_SESSION_ID = "REMOTE_SESSION_ID()"; 144 private static final String FUN_REMOTE_SESSION_CREATION_TIME = "REMOTE_SESSION_CREATION_TIME()"; 145 private static final String FUN_REMOTE_SESSION_LAST_ACCESSED_TIME = "REMOTE_SESSION_LAST_ACCESSED_TIME()"; 146 private static final String FUN_REMOTE_USER_INFO = "REMOTE_USER_INFO()"; 147 148 /* system constants (See JavaSctript class Constants in fbsql.js */ 149 private static final String FBSQL_REMOTE_USER = "FBSQL_REMOTE_USER"; 150 private static final String FBSQL_REMOTE_ROLE = "FBSQL_REMOTE_ROLE"; 151 private static final String FBSQL_REMOTE_SESSION_ID = "FBSQL_REMOTE_SESSION_ID"; 152 private static final String FBSQL_REMOTE_SESSION_CREATION_TIME = "FBSQL_REMOTE_SESSION_CREATION_TIME"; 153 private static final String FBSQL_REMOTE_SESSION_LAST_ACCESSED_TIME = "FBSQL_REMOTE_SESSION_LAST_ACCESSED_TIME"; 154 private static final String FBSQL_REMOTE_USER_INFO = "FBSQL_REMOTE_USER_INFO"; 155 156 private String instanceName; 157 private String instanceDirectory; 158 private StmtConnectTo stmtConnectTo; 159 private AsyncContext asyncContext; 160 161 private boolean debug; 162 private ConnectionPoolManager connectionPoolManager; 163 164 private Map<String /* SQL statement name */, StmtDeclareStatement> declaredStatementsMap; // list of SQL statements 165 private Map<String /* stored procedure name */, NonNativeProcedure> proceduresMap; 166 private Map<String /* js file name */, Scriptable> mapScopes; 167 private Map<String /* js file name */, Map<String /* function name */, Function>> mapFunctions; 168 169 private Map<StaticStatement, ReadyResult> mapJson; 170 private Queue<AsyncContext> ongoingRequests; 171 private DbServlet.SharedCoder sharedCoder; 172 private ParseNativeStmt parseNativeStmt; 173 174 /** 175 * Constructs DbRequestProcessor object 176 * 177 * @param instanceName 178 * @param stmtConnectTo 179 * @param asyncCtx 180 * @param debug 181 * @param connectionPoolManager 182 * @param declaredStatementsMap 183 * @param proceduresMap 184 * @param mapScopes 185 * @param mapFunctions 186 * @param mapJson 187 * @param ongoingRequests 188 * @param sharedCoder 189 */ 190 public DbRequestProcessor( // 191 String instanceName, // 192 String instanceDirectory, // 193 StmtConnectTo stmtConnectTo, // 194 AsyncContext asyncCtx, // 195 boolean debug, // 196 ConnectionPoolManager connectionPoolManager, // 197 Map<String /* SQL statement name */, StmtDeclareStatement> declaredStatementsMap, // list of SQL statements 198 Map<String /* stored procedure name */, NonNativeProcedure> proceduresMap, // 199 Map<String /* js file name */, Scriptable> mapScopes, // 200 Map<String /* js file name */, Map<String /* function name */, Function>> mapFunctions, // 201 Map<StaticStatement, ReadyResult> mapJson, // 202 Queue<AsyncContext> ongoingRequests, // 203 ParseNativeStmt parseNativeStmt, // 204 DbServlet.SharedCoder sharedCoder // 205 ) { 206 this.instanceName = instanceName; 207 this.instanceDirectory = instanceDirectory; 208 this.stmtConnectTo = stmtConnectTo; 209 this.asyncContext = asyncCtx; 210 this.debug = debug; 211 this.connectionPoolManager = connectionPoolManager; 212 this.declaredStatementsMap = declaredStatementsMap; // list of SQL statements 213 this.proceduresMap = proceduresMap; 214 this.mapScopes = mapScopes; 215 this.mapFunctions = mapFunctions; 216 this.mapJson = mapJson; 217 this.ongoingRequests = ongoingRequests; 218 this.parseNativeStmt = parseNativeStmt; 219 this.sharedCoder = sharedCoder; 220 } 221 222 /** 223 * Implementation of Runnable.run() method 224 * 225 * Overview of request processor logic 226 * - Parse request body 227 * - Check SQL statement against white list (list of allowed SQL statements) 228 * - Execute SQL statement if allowed 229 */ 230 @Override 231 public void run() { 232 HttpServletRequest request = (HttpServletRequest) asyncContext.getRequest(); 233 HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse(); 234 235 try { 236 String etagFromClient = request.getHeader("If-None-Match"); 237 238 String remoteUser = (String) request.getAttribute(DbServlet.REQUEST_ATTRIBUTE_USER); 239 String remoteRole = request.getHeader(DbServlet.CUSTOM_HTTP_HEADER_ROLE); 240 Cookie[] cookies = request.getCookies(); 241 // 242 HttpSession session = request.getSession(); 243 244 boolean reject = false; 245 String rejectMessage = null; 246 Throwable exception = null; 247 // 248 String clientInfoJson = getClientInfo(request, sharedCoder.decoder); 249 BufferedReader br = getCustomDataReader(request, sharedCoder.decoder); 250 String jsonLine = br.readLine(); 251 Map<String, String> bodyMap = JsonUtils.parseJsonObject(jsonLine); 252 String statementId = (String) JsonUtils.parseJson(bodyMap.get("statementId")); 253 String execType = (String) JsonUtils.parseJson(bodyMap.get("execType")); 254 String jsonStrFormat = bodyMap.get("format"); 255 Integer resultSetFormat = JsonUtils.parseJsonInt(jsonStrFormat); 256 String parameters = bodyMap.get("parameters"); 257 CharSequence sessionInfoJson = HttpRequestUtils.generateSessionInfoJson(request, sharedCoder.encoder); 258 String userInfoJson = generateUserInfoJson( // 259 request, // 260 clientInfoJson, // 261 sessionInfoJson // 262 ); 263 264 String updateResultJson = null; 265 266 String paramJsonArray; 267 boolean batch; 268 if (parameters.startsWith("[")) { 269 batch = true; 270 paramJsonArray = parameters; 271 } else { 272 batch = false; 273 paramJsonArray = '[' + parameters + ']'; 274 } 275 276 List<String> paramJsons = JsonUtils.parseJsonArray(paramJsonArray); 277 278 boolean executeTypeQuery = execType.equals(EXEC_TYPE_QUERY); 279 boolean executeTypeUpdate = execType.equals(EXEC_TYPE_UPDATE); 280 281 String unprocessedNamedPreparedStatement = null; 282 String namedPreparedStatement = null; 283 List<Map<String, Object>> parametersListOfMaps = null; 284 StmtDeclareStatement stmtDeclareStatement = null; 285 286 if (statementId == null) { // SQL statement provided 287 StringBuilder unprocessedNamedPreparedStatementSb = new StringBuilder(); 288 while (true) { 289 String line = br.readLine(); 290 if (line == null) 291 break; 292 unprocessedNamedPreparedStatementSb.append(' ' + line.trim() + '\n'); 293 } 294 unprocessedNamedPreparedStatement = unprocessedNamedPreparedStatementSb.toString().trim(); 295 namedPreparedStatement = SqlParseUtils.processStatement(unprocessedNamedPreparedStatement); 296 for (StmtDeclareStatement curDeclareStatement : declaredStatementsMap.values()) { 297 if (curDeclareStatement.statement.equals(namedPreparedStatement)) { 298 statementId = curDeclareStatement.alias; 299 stmtDeclareStatement = declaredStatementsMap.get(statementId); 300 break; 301 } 302 } 303 reject = stmtDeclareStatement == null && !stmtConnectTo.exposeUndeclaredStatements; 304 if (reject) 305 rejectMessage = StringUtils.escapeJson(MessageFormat.format("Rejected. SQL statement \"{0}\" not exposed to frontend", namedPreparedStatement)); 306 } else { // SQL statement name provided 307 reject = statementId.startsWith(ParseStmtConnectTo.NONEXPOSABLE_NAME_PREFIX); 308 final String NAME_NOT_FOUND_MSG = StringUtils.escapeJson(MessageFormat.format("Rejected. SQL statement name: ''{0}'' not found", statementId)); 309 if (reject) // wrong name format 310 rejectMessage = NAME_NOT_FOUND_MSG; 311 else { 312 stmtDeclareStatement = declaredStatementsMap.get(statementId); 313 if (stmtDeclareStatement == null) { 314 reject = true; 315 rejectMessage = NAME_NOT_FOUND_MSG; 316 } else { 317 namedPreparedStatement = stmtDeclareStatement.statement; 318 unprocessedNamedPreparedStatement = namedPreparedStatement; 319 } 320 } 321 } 322 323 if (stmtDeclareStatement != null) { 324 Collection<String> allowedRoles = stmtDeclareStatement.roles; 325 if (!allowedRoles.isEmpty()) 326 if (!allowedRoles.contains(remoteRole)) { 327 reject = true; 328 rejectMessage = StringUtils.escapeJson(MessageFormat.format("Rejected. SQL statement \"{0}\" not allowed for role \"{1}\"", namedPreparedStatement, remoteRole)); 329 } 330 } 331 332 // 333 // validate 334 // 335 parametersListOfMaps = new ArrayList<>(paramJsons.size()); 336 for (String paramJson : paramJsons) { 337 if (stmtDeclareStatement != null) { 338 String validatorStoredProcedureName = stmtDeclareStatement.trigger_before_procedure_name; 339 if (validatorStoredProcedureName != null) { 340 String statementInfoJson = generateStatementInfoJson( // 341 instanceName, // 342 statementId, // 343 unprocessedNamedPreparedStatement, // 344 paramJson // 345 ); 346 347 DbConnection dbConnection0 = null; 348 String modifiedParamsJson = null; 349 try { 350 dbConnection0 = connectionPoolManager.getConnection(); 351 352 NonNativeProcedure nonNativeProcedure = proceduresMap.get(validatorStoredProcedureName); 353 if (nonNativeProcedure != null) { // Java or JavaScript 354 if (nonNativeProcedure.procedureType == ProcedureType.JAVA || nonNativeProcedure.procedureType == ProcedureType.JS) { // Java or JavaScript 355 356 List<Object> parameterValues = new ArrayList<>(); 357 parameterValues.add(request); 358 parameterValues.add(response); 359 parameterValues.add(dbConnection0.getConnection()); 360 parameterValues.add(instanceName); 361 parameterValues.add(userInfoJson); 362 parameterValues.add(statementInfoJson); 363 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 364 365 Object obj = null; 366 if (nonNativeProcedure.procedureType == ProcedureType.JAVA) { // Java 367 Method method = CallUtils.getMethod(nonNativeProcedure.optionsJson); 368 try { 369 obj = (String) method.invoke(null, parametersArray); 370 } catch (InvocationTargetException e) { 371 exception = e.getTargetException(); 372 } 373 } else if (nonNativeProcedure.procedureType == ProcedureType.JS) { // JavaScript 374 // 375 // initize Rhino 376 // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino 377 // 378 Context ctx = Context.enter(); 379 try { 380 ctx.setLanguageVersion(Context.VERSION_1_7); 381 ctx.setOptimizationLevel(9); // Rhino optimization: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Optimization 382 JsFunction jsFunction = CallUtils.getFunction(instanceDirectory, nonNativeProcedure.optionsJson, mapScopes, mapFunctions); 383 try { 384 obj = jsFunction.function.call(ctx, jsFunction.scope, null, parametersArray); 385 } catch (Exception e) { 386 exception = e.getCause(); 387 } 388 if (obj instanceof NativeObject) 389 obj = (String) NativeJSON.stringify(ctx, jsFunction.scope, obj, null, null); // to string 390 } finally { 391 ctx.exit(); 392 } 393 } 394 if (obj instanceof ResultSet) 395 try (ResultSet rs = (ResultSet) obj) { 396 if (rs.next()) 397 modifiedParamsJson = rs.getString(1); 398 } catch (SQLException e) { 399 exception = e.getCause(); 400 } 401 else if (obj instanceof CharSequence) 402 modifiedParamsJson = obj.toString(); 403 } else if (nonNativeProcedure.procedureType == ProcedureType.EXEC) { // OS 404 List<Object> parameterValues = new ArrayList<>(); 405 parameterValues.add(instanceName); 406 parameterValues.add(userInfoJson); 407 parameterValues.add(statementInfoJson); 408 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 409 410 modifiedParamsJson = CallUtils.executeOsProgramm(instanceDirectory, nonNativeProcedure.optionsJson, parametersArray); 411 412 } else if (nonNativeProcedure.procedureType == ProcedureType.URL) { // URL 413 Map<String, Object> parametersMap = new LinkedHashMap<>(); 414 parametersMap.put("instanceName", instanceName); 415 parametersMap.put("userInfoJson", userInfoJson); 416 parametersMap.put("statementInfoJson", statementInfoJson); 417 418 modifiedParamsJson = CallUtils.executeUrl(instanceDirectory, nonNativeProcedure.optionsJson, parametersMap); 419 } 420 } else { // Native 421 CallableStatement cs = dbConnection0.getCallableStatement("{call " + validatorStoredProcedureName + "(?,?)}"); 422 cs.setString(1, instanceName); 423 cs.setString(2, userInfoJson); 424 cs.setString(3, statementInfoJson); 425 426 try (ResultSet rs = cs.executeQuery()) { 427 if (rs.next()) 428 modifiedParamsJson = rs.getString(1); 429 } 430 } 431 } finally { 432 if (dbConnection0 != null) 433 connectionPoolManager.releaseConnection(dbConnection0); 434 if (modifiedParamsJson == null) { // reject if stored procedure return null 435 reject = true; 436 if (exception == null || exception.getMessage() == null) 437 rejectMessage = StringUtils.escapeJson(MessageFormat.format("Rejected. Message: \"Rejected by trigger\". Procedure: {0}. SQL statement: \"{1}\". Parameters: {2}.", validatorStoredProcedureName, namedPreparedStatement, paramJson)); 438 else 439 rejectMessage = exception.getMessage(); 440 } else 441 paramJson = modifiedParamsJson.trim(); 442 } 443 } 444 } 445 446 Map<String, String> parametersJsonStrs = JsonUtils.parseJsonObject(paramJson); 447 Map<String, Object> parametersMap = new LinkedHashMap<>(parametersJsonStrs.size()); 448 for (Map.Entry<String, String> parameterNameValueEntry : parametersJsonStrs.entrySet()) { 449 String pname = parameterNameValueEntry.getKey(); 450 String pvalueJson = parameterNameValueEntry.getValue(); 451 Object pvalueObj = JsonUtils.parseJson(pvalueJson); 452 parametersMap.put(pname, pvalueObj); 453 } 454 parametersListOfMaps.add(parametersMap); 455 } 456 if (reject) { 457 response.setStatus(HttpServletResponse.SC_BAD_REQUEST); 458 try (OutputStream os = response.getOutputStream()) { 459 os.write(('{' + q("message") + ':' + q(rejectMessage) + '}').getBytes(StandardCharsets.UTF_8)); 460 os.flush(); 461 } 462 asyncContext.complete(); 463 return; 464 } 465 466 StringBuilder preparedStatementSb = new StringBuilder(); 467 Map<String /* name */, List<Integer /* index */>> mapParams = SqlParseUtils.parseNamedPreparedStatement(namedPreparedStatement, preparedStatementSb); 468 String preparedStatement = preparedStatementSb.toString(); 469 470 // 471 // Replace built-in functions with values 472 // 473 preparedStatement = preparedStatement.replace(FUN_REMOTE_USER, remoteUser == null ? SQL_NULL : SQL_QUOTE_CHAR + remoteUser + SQL_QUOTE_CHAR); 474 preparedStatement = preparedStatement.replace(FUN_REMOTE_ROLE, remoteRole == null ? SQL_NULL : SQL_QUOTE_CHAR + remoteRole + SQL_QUOTE_CHAR); 475 preparedStatement = preparedStatement.replace(FUN_REMOTE_SESSION_ID, SQL_QUOTE_CHAR + session.getId() + SQL_QUOTE_CHAR); 476 preparedStatement = preparedStatement.replace(FUN_REMOTE_SESSION_CREATION_TIME, Long.toString(session.getCreationTime())); 477 preparedStatement = preparedStatement.replace(FUN_REMOTE_SESSION_LAST_ACCESSED_TIME, Long.toString(session.getLastAccessedTime())); 478 preparedStatement = preparedStatement.replace(FUN_REMOTE_USER_INFO, userInfoJson == null ? SQL_NULL : SQL_QUOTE_CHAR + userInfoJson + SQL_QUOTE_CHAR); 479 480 // 481 // Replace IN_ROLE() built-in function with value 482 // 483 while (true) { 484 int offset = SqlParseUtils.indexOf(preparedStatement, FUT_IN_ROLE); 485 if (offset == -1) 486 break; 487 int pos1 = offset + FUT_IN_ROLE.length(); 488 char quote = preparedStatement.charAt(pos1); // get single «'» or double «"» quote 489 String s = preparedStatement.substring(pos1 + 1); 490 int pos2 = s.indexOf(quote); 491 String role = s.substring(0, pos2); 492 boolean inRole = request.isUserInRole(role); 493 if (!inRole) 494 inRole = role.equals(remoteRole); 495 preparedStatement = preparedStatement.substring(0, offset) + (inRole ? SQL_TRUE : SQL_FALSE) + preparedStatement.substring(pos1 + role.length() + 3); 496 } 497 498 // 499 // Replace COOKIE() built-in function with value 500 // 501 while (true) { 502 int offset = SqlParseUtils.indexOf(preparedStatement, FUT_GET_COOKIE); 503 if (offset == -1) 504 break; 505 int pos1 = offset + FUT_GET_COOKIE.length(); 506 char quote = preparedStatement.charAt(pos1); // get single «'» or double «"» quote 507 String s = preparedStatement.substring(pos1 + 1); 508 int pos2 = s.indexOf(quote); 509 String cookieName = s.substring(0, pos2); 510 String value = null; 511 for (Cookie cookie : cookies) { 512 if (cookieName.equals(cookie.getName())) { 513 value = cookie.getValue(); 514 break; 515 } 516 } 517 preparedStatement = preparedStatement.substring(0, offset) + (value == null ? SQL_NULL : SQL_QUOTE_CHAR + value + SQL_QUOTE_CHAR) + preparedStatement.substring(pos1 + cookieName.length() + 3); 518 } 519 520 // 521 // Replace GET_HTTP_SESSION_ATTRIBUTE_AS_CHAR() built-in function with value 522 // 523 while (true) { 524 int offset = SqlParseUtils.indexOf(preparedStatement, FUT_GET_HTTP_SESSION_ATTRIBUTE_AS_CHAR); 525 if (offset == -1) 526 break; 527 int pos1 = offset + FUT_GET_HTTP_SESSION_ATTRIBUTE_AS_CHAR.length(); 528 char quote = preparedStatement.charAt(pos1); // get single «'» or double «"» quote 529 String s = preparedStatement.substring(pos1 + 1); 530 int pos2 = s.indexOf(quote); 531 String attributeName = s.substring(0, pos2); 532 Object attributeValue = session.getAttribute(attributeName); 533 preparedStatement = preparedStatement.substring(0, offset) + (attributeValue == null ? SQL_NULL : SQL_QUOTE_CHAR + attributeValue.toString() + SQL_QUOTE_CHAR) + preparedStatement.substring(pos1 + attributeName.length() + 3); 534 } 535 536 // 537 // Replace FUT_GET_HTTP_SESSION_ATTRIBUTE_AS_INTEGER() built-in function with value 538 // 539 while (true) { 540 int offset = SqlParseUtils.indexOf(preparedStatement, FUT_GET_HTTP_SESSION_ATTRIBUTE_AS_INTEGER); 541 if (offset == -1) 542 break; 543 int pos1 = offset + FUT_GET_HTTP_SESSION_ATTRIBUTE_AS_INTEGER.length(); 544 char quote = preparedStatement.charAt(pos1); // get single «'» or double «"» quote 545 String s = preparedStatement.substring(pos1 + 1); 546 int pos2 = s.indexOf(quote); 547 String attributeName = s.substring(0, pos2); 548 Object attributeValue = session.getAttribute(attributeName); 549 preparedStatement = preparedStatement.substring(0, offset) + (attributeValue == null ? SQL_NULL : attributeValue.toString()) + preparedStatement.substring(pos1 + attributeName.length() + 3); 550 } 551 552 // 553 // Replace GET_HTTP_HEADER_AS_CHAR() built-in function with value 554 // 555 while (true) { 556 int offset = SqlParseUtils.indexOf(preparedStatement, FUT_GET_HTTP_HEADER_AS_CHAR); 557 if (offset == -1) 558 break; 559 int pos1 = offset + FUT_GET_HTTP_HEADER_AS_CHAR.length(); 560 char quote = preparedStatement.charAt(pos1); // get single «'» or double «"» quote 561 String s = preparedStatement.substring(pos1 + 1); 562 int pos2 = s.indexOf(quote); 563 String headerName = s.substring(0, pos2); 564 String header = request.getHeader(headerName); 565 preparedStatement = preparedStatement.substring(0, offset) + (header == null ? SQL_NULL : SQL_QUOTE_CHAR + header + SQL_QUOTE_CHAR) + preparedStatement.substring(pos1 + headerName.length() + 3); 566 } 567 568 // 569 // Replace GET_HTTP_HEADER_AS_INTEGER() built-in function with value 570 // 571 while (true) { 572 int offset = SqlParseUtils.indexOf(preparedStatement, FUT_GET_HTTP_HEADER_AS_INTEGER); 573 if (offset == -1) 574 break; 575 int pos1 = offset + FUT_GET_HTTP_HEADER_AS_INTEGER.length(); 576 char quote = preparedStatement.charAt(pos1); // get single «'» or double «"» quote 577 String s = preparedStatement.substring(pos1 + 1); 578 int pos2 = s.indexOf(quote); 579 String headerName = s.substring(0, pos2); 580 int header = request.getIntHeader(headerName); 581 preparedStatement = preparedStatement.substring(0, offset) + (header == -1 ? SQL_NULL : header) + preparedStatement.substring(pos1 + headerName.length() + 3); 582 } 583 584 // 585 // Replace GET_HTTP_HEADER_AS_DATE() built-in function with value 586 // 587 while (true) { 588 int offset = SqlParseUtils.indexOf(preparedStatement, FUT_GET_HTTP_HEADER_AS_DATE); 589 if (offset == -1) 590 break; 591 int pos1 = offset + FUT_GET_HTTP_HEADER_AS_DATE.length(); 592 char quote = preparedStatement.charAt(pos1); // get single «'» or double «"» quote 593 String s = preparedStatement.substring(pos1 + 1); 594 int pos2 = s.indexOf(quote); 595 String headerName = s.substring(0, pos2); 596 long header = request.getDateHeader(headerName); 597 preparedStatement = preparedStatement.substring(0, offset) + (header == -1 ? SQL_NULL : header) + preparedStatement.substring(pos1 + headerName.length() + 3); 598 } 599 600 ReadyResult readyResult = mapJson.get(new StaticStatement(preparedStatement, resultSetFormat)); 601 String etag = null; 602 boolean compressed = false; 603 byte[] bs = null; 604 605 if (readyResult == null) { 606 if (debug) { 607 System.out.println("Statement was delegated to underlying database:"); 608 System.out.println(unprocessedNamedPreparedStatement); 609 } 610 DbConnection dbConnection = connectionPoolManager.getConnection(); 611 try { 612 NonNativeProcedure nonNativeProcedure = CallUtils.getCallStatementNonNativeProcedure(unprocessedNamedPreparedStatement, proceduresMap); 613 614 if (nonNativeProcedure != null) { 615 if (nonNativeProcedure.procedureType == ProcedureType.JAVA) { // Java 616 Method method = CallUtils.getMethod(nonNativeProcedure.optionsJson); 617 if (executeTypeUpdate) { 618 int rowCount = 0; 619 for (Map<String, Object> parametersMap : parametersListOfMaps) { 620 List<Object> parameterValues = new ArrayList<>(); 621 parameterValues.add(request); 622 parameterValues.add(response); 623 parameterValues.add(dbConnection.getConnection()); 624 parameterValues.add(instanceName); 625 CallUtils.getCallStatementParameterValues(parseNativeStmt, unprocessedNamedPreparedStatement, parametersMap, parameterValues); 626 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 627 628 method.invoke(null, parametersArray); 629 rowCount++; 630 } 631 bs = simpleExecuteUpdateResultJson(rowCount).getBytes(StandardCharsets.UTF_8); 632 } else if (executeTypeQuery) { 633 Map<String, Object> parametersMap = parametersListOfMaps.get(0); 634 List<Object> parameterValues = new ArrayList<>(); 635 parameterValues.add(request); 636 parameterValues.add(response); 637 parameterValues.add(dbConnection.getConnection()); 638 parameterValues.add(instanceName); 639 CallUtils.getCallStatementParameterValues(parseNativeStmt, unprocessedNamedPreparedStatement, parametersMap, parameterValues); 640 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 641 642 Object obj = method.invoke(null, parametersArray); 643 Integer compression = stmtDeclareStatement == null ? CompressionLevel.BEST_COMPRESSION : stmtDeclareStatement.compressionLevel; 644 List<Map<String /* column name */, String /* JSON value */>> list = null; 645 if (obj instanceof ResultSet) 646 list = QueryUtils.resutlSetToListOfMapsJsonValues((ResultSet) obj, sharedCoder.encoder); 647 else if (obj instanceof CharSequence) 648 list = QueryUtils.convertJsonArrayOfObjectsToListOfMaps(obj.toString()); 649 ReadyResult rr = QueryUtils.createReadyResult(list, resultSetFormat, compression, sharedCoder.encoder); 650 bs = rr.bs; 651 etag = rr.etag; 652 compressed = rr.compressed; 653 } 654 } else if (nonNativeProcedure.procedureType == ProcedureType.JS) { // JavaScript 655 // 656 // initize Rhino 657 // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino 658 // 659 Context ctx = Context.enter(); 660 661 try { 662 ctx.setLanguageVersion(Context.VERSION_1_7); 663 ctx.setOptimizationLevel(9); // Rhino optimization: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Optimization 664 JsFunction jsFunction = CallUtils.getFunction(instanceDirectory, nonNativeProcedure.optionsJson, mapScopes, mapFunctions); 665 666 if (executeTypeUpdate) { 667 int rowCount = 0; 668 for (Map<String, Object> parametersMap : parametersListOfMaps) { 669 List<Object> parameterValues = new ArrayList<>(); 670 parameterValues.add(request); 671 parameterValues.add(response); 672 parameterValues.add(dbConnection.getConnection()); 673 parameterValues.add(instanceName); 674 CallUtils.getCallStatementParameterValues(parseNativeStmt, unprocessedNamedPreparedStatement, parametersMap, parameterValues); 675 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 676 677 jsFunction.function.call(ctx, jsFunction.scope, null, parametersArray); 678 rowCount++; 679 } 680 bs = simpleExecuteUpdateResultJson(rowCount).getBytes(StandardCharsets.UTF_8); 681 } else if (executeTypeQuery) { 682 Map<String, Object> parametersMap = parametersListOfMaps.get(0); 683 List<Object> parameterValues = new ArrayList<>(); 684 parameterValues.add(request); 685 parameterValues.add(response); 686 parameterValues.add(dbConnection.getConnection()); 687 parameterValues.add(instanceName); 688 CallUtils.getCallStatementParameterValues(parseNativeStmt, unprocessedNamedPreparedStatement, parametersMap, parameterValues); 689 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 690 691 Object obj = jsFunction.function.call(ctx, jsFunction.scope, null, parametersArray); 692 Integer compression = stmtDeclareStatement == null ? CompressionLevel.BEST_COMPRESSION : stmtDeclareStatement.compressionLevel; 693 List<Map<String /* column name */, String /* JSON value */>> list = null; 694 if (obj instanceof NativeObject || obj instanceof NativeArray) { // return JSON object that will sent to client 695 String json = (String) NativeJSON.stringify(ctx, jsFunction.scope, obj, null, null); 696 if (json.startsWith("{") && json.endsWith("}")) 697 json = '[' + json + ']'; 698 list = QueryUtils.convertJsonArrayOfObjectsToListOfMaps(json); 699 } else if (obj instanceof ResultSet) 700 list = QueryUtils.resutlSetToListOfMapsJsonValues((ResultSet) obj, sharedCoder.encoder); 701 else if (obj instanceof CharSequence) 702 list = QueryUtils.convertJsonArrayOfObjectsToListOfMaps(obj.toString()); 703 ReadyResult rr = QueryUtils.createReadyResult(list, resultSetFormat, compression, sharedCoder.encoder); 704 bs = rr.bs; 705 etag = rr.etag; 706 compressed = rr.compressed; 707 } 708 } catch (Exception e) { 709 e.printStackTrace(); 710 } finally { 711 ctx.exit(); 712 } 713 } else if (nonNativeProcedure.procedureType == ProcedureType.EXEC) { // OS 714 if (executeTypeUpdate) { 715 int rowCount = 0; 716 for (Map<String, Object> parametersMap : parametersListOfMaps) { 717 List<Object> parameterValues = new ArrayList<>(); 718 parameterValues.add(instanceName); 719 CallUtils.getCallStatementParameterValues(parseNativeStmt, unprocessedNamedPreparedStatement, parametersMap, parameterValues); 720 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 721 722 CallUtils.executeOsProgramm(instanceDirectory, nonNativeProcedure.optionsJson, parametersArray); 723 rowCount++; 724 } 725 bs = simpleExecuteUpdateResultJson(rowCount).getBytes(StandardCharsets.UTF_8); 726 } else if (executeTypeQuery) { 727 Map<String, Object> parametersMap = parametersListOfMaps.get(0); 728 List<Object> parameterValues = new ArrayList<>(); 729 parameterValues.add(instanceName); 730 CallUtils.getCallStatementParameterValues(parseNativeStmt, unprocessedNamedPreparedStatement, parametersMap, parameterValues); 731 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 732 733 String jsonArray = CallUtils.executeOsProgramm(instanceDirectory, nonNativeProcedure.optionsJson, parametersArray); 734 735 Integer compression = stmtDeclareStatement == null ? CompressionLevel.BEST_COMPRESSION : stmtDeclareStatement.compressionLevel; 736 List<Map<String /* column name */, String /* JSON value */>> list = QueryUtils.convertJsonArrayOfObjectsToListOfMaps(jsonArray); 737 ReadyResult rr = QueryUtils.createReadyResult(list, resultSetFormat, compression, sharedCoder.encoder); 738 bs = rr.bs; 739 etag = rr.etag; 740 compressed = rr.compressed; 741 } 742 } else if (nonNativeProcedure.procedureType == ProcedureType.URL) { // URL 743 if (executeTypeUpdate) { 744 int rowCount = 0; 745 for (Map<String, Object> parametersMap : parametersListOfMaps) { 746 Map<String, Object> parametersMap0 = new LinkedHashMap<>(); 747 parametersMap0.put("instanceName", instanceName); 748 parametersMap0.putAll(parametersMap); 749 750 CallUtils.executeUrl(instanceDirectory, nonNativeProcedure.optionsJson, parametersMap0); 751 rowCount++; 752 } 753 bs = simpleExecuteUpdateResultJson(rowCount).getBytes(StandardCharsets.UTF_8); 754 } else if (executeTypeQuery) { 755 Map<String, Object> parametersMap = parametersListOfMaps.get(0); 756 757 Map<String, Object> parametersMap0 = new LinkedHashMap<>(); 758 parametersMap0.put("instanceName", instanceName); 759 parametersMap0.putAll(parametersMap); 760 761 String jsonArray = CallUtils.executeUrl(instanceDirectory, nonNativeProcedure.optionsJson, parametersMap0); 762 763 Integer compression = stmtDeclareStatement == null ? CompressionLevel.BEST_COMPRESSION : stmtDeclareStatement.compressionLevel; 764 List<Map<String /* column name */, String /* JSON value */>> list = QueryUtils.convertJsonArrayOfObjectsToListOfMaps(jsonArray); 765 ReadyResult rr = QueryUtils.createReadyResult(list, resultSetFormat, compression, sharedCoder.encoder); 766 bs = rr.bs; 767 etag = rr.etag; 768 compressed = rr.compressed; 769 } 770 } 771 } else { // Native 772 PreparedStatement ps = dbConnection.getPreparedStatement(preparedStatement); 773 ParameterMetaData pmd = ps.getParameterMetaData(); 774 for (Map<String, Object> parametersMap : parametersListOfMaps) { 775 for (Map.Entry<String, Object> parameterMapEntry : parametersMap.entrySet()) { 776 String pname = parameterMapEntry.getKey(); 777 Object pvalueObj = parameterMapEntry.getValue(); 778 String pvalue = pvalueObj == null ? null : pvalueObj.toString(); 779 780 List<Integer /* index */> indexes = mapParams.get(pname); 781 if (indexes != null) 782 for (int n : indexes) { 783 int parameterType = pmd.getParameterType(n); 784 if (pvalue == null) // JavaScript null 785 ps.setNull(n, parameterType); 786 else { 787 if (parameterType == Types.SMALLINT) // JavaScript Number 788 ps.setShort(n, Short.parseShort(pvalue)); 789 else if (parameterType == Types.INTEGER) // JavaScript Number 790 ps.setInt(n, Integer.parseInt(pvalue)); 791 else if (parameterType == Types.BIGINT) // JavaScript Number 792 ps.setLong(n, Long.parseLong(pvalue)); 793 else if (parameterType == Types.FLOAT) // JavaScript Number 794 ps.setFloat(n, Float.parseFloat(pvalue)); 795 else if (parameterType == Types.DOUBLE) // JavaScript Number 796 ps.setDouble(n, Double.parseDouble(pvalue)); 797 else if (parameterType == Types.BOOLEAN || parameterType == Types.BIT) // JavaScript Boolean 798 ps.setBoolean(n, Boolean.parseBoolean(pvalue)); 799 else if (parameterType == Types.DATE) { 800 if (pvalue.contains("T")) { // JavaScript Date 801 Instant instant = Instant.parse(pvalue); 802 Timestamp ts = Timestamp.from(instant); 803 long time = ts.getTime(); 804 ps.setDate(n, new Date(time)); 805 } else if (pvalue.contains("-")) // JavaScript String 806 ps.setString(n, pvalue); // JavaScript Number 807 else if (pvalue.charAt(0) > '0' && pvalue.charAt(0) <= '9') // JavaScript Number 808 ps.setDate(n, new Date(Long.parseLong(pvalue))); 809 else 810 ps.setString(n, pvalue); 811 } else if (parameterType == Types.TIME) { 812 if (pvalue.contains("T")) { // JavaScript Date 813 Instant instant = Instant.parse(pvalue); 814 Timestamp ts = Timestamp.from(instant); 815 ps.setTime(n, new Time(ts.getTime())); 816 } else if (pvalue.contains(":")) // JavaScript String 817 ps.setString(n, pvalue); 818 else if (pvalue.charAt(0) > '0' && pvalue.charAt(0) <= '9') // JavaScript Number 819 ps.setTime(n, new Time(Long.parseLong(pvalue))); 820 else 821 ps.setString(n, pvalue); 822 } else if (parameterType == Types.TIMESTAMP) { 823 if (pvalue.contains("T")) { // JavaScript Date 824 Instant instant = Instant.parse(pvalue); 825 Timestamp ts = Timestamp.from(instant); 826 ps.setTimestamp(n, ts); 827 } else if (pvalue.charAt(0) > '0' && pvalue.charAt(0) <= '9') // JavaScript Number 828 ps.setTime(n, new Time(Long.parseLong(pvalue))); 829 else 830 ps.setString(n, pvalue); 831 } else if (parameterType == Types.BINARY || parameterType == Types.VARBINARY || parameterType == Types.LONGVARBINARY) // JavaScript (ArrayBuffer, Blob, URL, String) converted to BASE64 on client side 832 ps.setBytes(n, sharedCoder.decoder.decode(pvalue)); 833 else if (parameterType == Types.BLOB) { // JavaScript (ArrayBuffer, Blob, URL, String) converted to BASE64 on client side 834 Blob blob = ps.getConnection().createBlob(); 835 blob.setBytes(1L, sharedCoder.decoder.decode(pvalue)); 836 ps.setBlob(n, blob); 837 } else if (parameterType == Types.CLOB) { // JavaScript string stored in CLOB "AS IS" (as String) 838 Clob clob = ps.getConnection().createClob(); 839 clob.setString(1L, pvalue); 840 ps.setClob(n, clob); 841 } else { // unknown 842 if (FBSQL_REMOTE_USER.equals(pvalue)) 843 ps.setString(n, remoteUser); 844 else if (FBSQL_REMOTE_ROLE.equals(pvalue)) 845 ps.setString(n, remoteRole); 846 else if (FBSQL_REMOTE_SESSION_ID.equals(pvalue)) 847 ps.setString(n, session.getId()); 848 else if (FBSQL_REMOTE_SESSION_CREATION_TIME.equals(pvalue)) 849 ps.setLong(n, session.getCreationTime()); 850 else if (FBSQL_REMOTE_SESSION_LAST_ACCESSED_TIME.equals(pvalue)) 851 ps.setLong(n, session.getLastAccessedTime()); 852 else if (FBSQL_REMOTE_USER_INFO.equals(pvalue)) 853 ps.setString(n, userInfoJson); 854 else 855 ps.setString(n, pvalue); 856 } 857 } 858 } 859 } 860 if (executeTypeUpdate) 861 ps.addBatch(); 862 } 863 864 if (executeTypeQuery) { // Execute Query 865 Integer compression = stmtDeclareStatement == null ? CompressionLevel.BEST_COMPRESSION : stmtDeclareStatement.compressionLevel; 866 ResultSet rs = ps.executeQuery(); 867 List<Map<String /* column name */, String /* JSON value */>> list = QueryUtils.resutlSetToListOfMapsJsonValues(rs, sharedCoder.encoder); 868 // try (ResultSet rs = ps.executeQuery()) { 869 // List<Map<String /* column name */, Object /* column value */>> resultsListOfMaps = QueryUtils.resutlSetToListOfMaps(rs); 870 // List<Map<String /* column name */, String /* JSON value */>> list = QueryUtils.listOfMapsToListOfMapsJsonValues(resultsListOfMaps, sharedCoder.encoder); 871 872 ReadyResult rr = QueryUtils.createReadyResult(list, resultSetFormat, compression, sharedCoder.encoder); 873 bs = rr.bs; 874 etag = rr.etag; 875 compressed = rr.compressed; 876 // } finally { 877 // if (dbConnection != null) 878 // connectionPoolManager.releaseConnection(dbConnection); 879 // } 880 881 } else if (executeTypeUpdate) { // Execute Update 882 List<Map<String /* column name */, Object /* JSON value */>> gkList; 883 int[] rowCounts = ps.executeBatch(); 884 try (ResultSet rsgk = ps.getGeneratedKeys()) { 885 if (rsgk == null) 886 gkList = Collections.emptyList(); 887 else 888 gkList = QueryUtils.resutlSetToListOfMaps(rsgk); 889 } catch (Throwable e) { 890 e.printStackTrace(); 891 gkList = Collections.emptyList(); 892 // } finally { 893 // if (dbConnection != null) 894 // connectionPoolManager.releaseConnection(dbConnection); 895 } 896 897 List<Map<String /* column name */, String /* JSON value */>> generatedKeys = QueryUtils.listOfMapsToListOfMapsJsonValues(gkList, sharedCoder.encoder); 898 899 // 900 // Build executeUpdate result JSON 901 // 902 903 StringBuilder resultSb = new StringBuilder(); 904 resultSb.append('{'); 905 resultSb.append(q("rowCount")); 906 resultSb.append(':'); 907 resultSb.append(batch ? Arrays.toString(rowCounts).replace(" ", "") : Integer.toString(rowCounts[0])); // JavaScript array of numbers (batch) or number 908 resultSb.append(','); 909 resultSb.append(q("generatedKeys")); 910 resultSb.append(':'); 911 resultSb.append(QueryUtils.convertToJsonArray(generatedKeys, resultSetFormat)); 912 resultSb.append('}'); 913 914 updateResultJson = resultSb.toString(); 915 bs = updateResultJson.getBytes(StandardCharsets.UTF_8); 916 } else 917 throw new IllegalArgumentException(execType); 918 } 919 } catch (SQLException e) { 920 e.printStackTrace(); 921 try (OutputStream os = response.getOutputStream()) { 922 String message = e.getMessage(); 923 String sqlState = e.getSQLState(); 924 String msg = "{" + q("message") + ":" + (message == null ? "null" : q(message)) + "," + q("errorCode") + ":" + e.getErrorCode() + "," + q("SQLState") + ":" + (sqlState == null ? "null" : q(sqlState)) + "}"; 925 os.write(msg.getBytes(StandardCharsets.UTF_8)); 926 os.flush(); 927 } catch (Exception e2) { 928 e2.printStackTrace(); 929 } 930 asyncContext.complete(); 931 return; 932 } finally { 933 if (dbConnection != null) 934 connectionPoolManager.releaseConnection(dbConnection); 935 } 936 937 if (!ongoingRequests.isEmpty()) { 938 long timestamp = Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTimeInMillis(); 939 if (stmtDeclareStatement != null) { 940 String notifierStoredProcedureName = stmtDeclareStatement.trigger_after_procedure_name; 941 for (AsyncContext ac : ongoingRequests) { 942 try { 943 HttpServletRequest selfRequest = (HttpServletRequest) ac.getRequest(); 944 HttpServletResponse selfResponce = (HttpServletResponse) ac.getResponse(); 945 946 if (notifierStoredProcedureName != null) { 947 String selfClientInfoJson = getClientInfo(selfRequest, sharedCoder.decoder); 948 CharSequence selfSessionInfoJson = HttpRequestUtils.generateSessionInfoJson(request, sharedCoder.encoder); 949 950 String selfUserInfoJson = generateUserInfoJson( // 951 selfRequest, // 952 selfClientInfoJson, // 953 selfSessionInfoJson // 954 ); 955 956 String statementInfoJson = generateStatementInfoJson( // 957 instanceName, // 958 statementId, // 959 unprocessedNamedPreparedStatement, // 960 paramJsonArray // 961 ); 962 963 String executionResultJson = generateExecutionResultJson( // 964 timestamp, // 965 updateResultJson); 966 967 String outEvent = null; // JSON object that will sent to client 968 DbConnection dbConnection0 = null; 969 try { 970 dbConnection0 = connectionPoolManager.getConnection(); 971 NonNativeProcedure nonNativeProcedure = proceduresMap.get(notifierStoredProcedureName); 972 if (nonNativeProcedure != null) { 973 if (nonNativeProcedure.procedureType == ProcedureType.JAVA || nonNativeProcedure.procedureType == ProcedureType.JS) { // Java or JavaScript 974 Object obj = null; 975 976 List<Object> parameterValues = new ArrayList<>(); 977 parameterValues.add(selfRequest); 978 parameterValues.add(selfResponce); 979 parameterValues.add(dbConnection0.getConnection()); 980 parameterValues.add(instanceName); 981 parameterValues.add(userInfoJson); 982 parameterValues.add(selfUserInfoJson); 983 parameterValues.add(statementInfoJson); 984 parameterValues.add(executionResultJson); 985 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 986 987 if (nonNativeProcedure.procedureType == ProcedureType.JAVA) { // Java 988 Method method = CallUtils.getMethod(nonNativeProcedure.optionsJson); 989 obj = method.invoke(null, parametersArray); 990 } else if (nonNativeProcedure.procedureType == ProcedureType.JS) { // JavaScript 991 // 992 // initize Rhino 993 // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino 994 // 995 Context ctx = Context.enter(); 996 try { 997 ctx.setLanguageVersion(Context.VERSION_1_7); 998 ctx.setOptimizationLevel(9); // Rhino optimization: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Optimization 999 JsFunction jsFunction = CallUtils.getFunction(instanceDirectory, nonNativeProcedure.optionsJson, mapScopes, mapFunctions); 1000 obj = jsFunction.function.call(ctx, jsFunction.scope, null, parametersArray); 1001 if (obj instanceof NativeObject) 1002 obj = (String) NativeJSON.stringify(ctx, jsFunction.scope, obj, null, null); 1003 } catch (Exception e) { 1004 obj = null; 1005 e.printStackTrace(); 1006 } finally { 1007 ctx.exit(); 1008 } 1009 } 1010 if (obj instanceof ResultSet) 1011 try (ResultSet rs = (ResultSet) obj) { 1012 if (rs.next()) 1013 outEvent = rs.getString(1); 1014 } 1015 else if (obj instanceof CharSequence) 1016 outEvent = obj.toString(); 1017 } else if (nonNativeProcedure.procedureType == ProcedureType.EXEC) { // OS 1018 List<Object> parameterValues = new ArrayList<>(); 1019 parameterValues.add(instanceName); 1020 parameterValues.add(userInfoJson); 1021 parameterValues.add(selfUserInfoJson); 1022 parameterValues.add(statementInfoJson); 1023 parameterValues.add(executionResultJson); 1024 Object[] parametersArray = parameterValues.toArray(new Object[parameterValues.size()]); 1025 outEvent = CallUtils.executeOsProgramm(instanceDirectory, nonNativeProcedure.optionsJson, parametersArray); 1026 1027 } else if (nonNativeProcedure.procedureType == ProcedureType.URL) { // URL 1028 Map<String, Object> parametersMap = new LinkedHashMap<>(); 1029 parametersMap.put("instanceName", instanceName); 1030 parametersMap.put("userInfoJson", userInfoJson); 1031 parametersMap.put("selfUserInfoJson", selfUserInfoJson); 1032 parametersMap.put("statementInfoJson", statementInfoJson); 1033 parametersMap.put("executionResultJson", executionResultJson); 1034 1035 outEvent = CallUtils.executeUrl(instanceDirectory, nonNativeProcedure.optionsJson, parametersMap); 1036 } 1037 } else { // Native 1038 CallableStatement cs = dbConnection0.getCallableStatement("{call " + notifierStoredProcedureName + "(?,?,?,?)}"); 1039 cs.setString(1, userInfoJson); 1040 cs.setString(2, selfUserInfoJson); 1041 cs.setString(3, statementInfoJson); 1042 cs.setString(4, executionResultJson); 1043 1044 try (ResultSet rs = cs.executeQuery()) { 1045 if (rs.next()) 1046 outEvent = rs.getString(1); 1047 } 1048 } 1049 1050 } finally { 1051 if (dbConnection0 != null) 1052 connectionPoolManager.releaseConnection(dbConnection0); 1053 if (outEvent != null) { // send if outEvent is not null 1054 Writer writer = ac.getResponse().getWriter(); 1055 writer.append(outEvent + '\n'); 1056 writer.flush(); 1057 if (debug) { 1058 System.out.println("Event was delivered to client:"); 1059 System.out.println(outEvent); 1060 } 1061 } 1062 } 1063 } 1064 } catch (Exception e) { 1065 e.printStackTrace(); 1066 } 1067 } 1068 } 1069 } 1070 } else { 1071 bs = readyResult.bs; 1072 etag = readyResult.etag; 1073 compressed = readyResult.compressed; 1074 } 1075 if (executeTypeQuery) { // Execute Query 1076 if (etag.equals(etagFromClient)) { 1077 response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); // setting HTTP 304 and returning with empty body 1078 asyncContext.complete(); 1079 return; 1080 } else { 1081 response.setContentType("application/json"); 1082 response.setCharacterEncoding("UTF-8"); 1083 response.setContentLength(bs.length); 1084 if (compressed) 1085 response.setHeader("Content-Encoding", "deflate"); 1086 response.setHeader("Cache-Control", "no-cache"); 1087 response.addHeader("ETag", etag); 1088 } 1089 } else if (executeTypeUpdate) { // Execute Update 1090 response.setContentType("application/json"); 1091 response.setCharacterEncoding("UTF-8"); 1092 response.setContentLength(bs.length); 1093 } 1094 try (OutputStream os = response.getOutputStream()) { 1095 os.write(bs); 1096 os.flush(); 1097 } 1098 } catch (Throwable e) { 1099 e.printStackTrace(); 1100 try (OutputStream os = response.getOutputStream()) { 1101 os.write(("{" + q("message") + ":" + q(e.getMessage()) + "}").getBytes(StandardCharsets.UTF_8)); 1102 os.flush(); 1103 } catch (IOException e1) { 1104 e1.printStackTrace(); 1105 } 1106 } finally { 1107 try { 1108 asyncContext.complete(); 1109 } catch (Throwable t) { 1110 t.printStackTrace(); 1111 } 1112 } 1113 } 1114 1115 private static String getClientInfo(HttpServletRequest request, Decoder decoder) throws IOException { 1116 String base64 = request.getHeader(DbServlet.CUSTOM_HTTP_HEADER_CLIENT_INFO); 1117 byte[] bs = decoder.decode(base64); 1118 return new String(bs, StandardCharsets.UTF_8); 1119 } 1120 1121 private static BufferedReader getCustomDataReader(HttpServletRequest request, Decoder decoder) throws IOException { 1122 String method = request.getMethod().toUpperCase(Locale.ENGLISH); 1123 BufferedReader br; 1124 if (method.equals("GET")) { 1125 String base64 = request.getHeader(DbServlet.CUSTOM_HTTP_HEADER_STATEMENT); 1126 byte[] bs = decoder.decode(base64); 1127 String s = new String(bs, StandardCharsets.UTF_8); 1128 br = new BufferedReader(new StringReader(s)); 1129 } else if (method.equals("POST")) 1130 br = request.getReader(); 1131 else 1132 throw new IllegalArgumentException(method); 1133 return br; 1134 } 1135 1136 /** 1137 * Utility method generates ExecuteUpdate result JSON 1138 * 1139 * @param rowCount 1140 * @return 1141 */ 1142 private static String simpleExecuteUpdateResultJson(int rowCount) { 1143 StringBuilder sb = new StringBuilder(); 1144 sb.append('{'); 1145 sb.append(q("rowCount") + ':' + Integer.toString(rowCount)); 1146 sb.append(','); 1147 sb.append(q("generatedKeys") + ':' + "[]"); 1148 sb.append('}'); 1149 return sb.toString(); 1150 } 1151 1152 /** 1153 * Generate User Info JSON-formatted string 1154 * 1155 * @param request 1156 * @param clientInfoJson 1157 * @return 1158 * @throws IOException 1159 * @throws ParseException 1160 */ 1161 private static String generateUserInfoJson( // 1162 HttpServletRequest request, // 1163 String clientInfoJson, // 1164 CharSequence sessionInfoJson // 1165 ) throws IOException, ParseException { 1166 String remoteAddr = request.getRemoteAddr(); 1167 String remoteHost = request.getRemoteHost(); 1168 Integer remotePort = request.getRemotePort(); 1169 String remoteUser = (String) request.getAttribute(DbServlet.REQUEST_ATTRIBUTE_USER); 1170 String remoteRole = request.getHeader(DbServlet.CUSTOM_HTTP_HEADER_ROLE); 1171 Cookie[] cookies = request.getCookies(); 1172 CharSequence cookiesJson = HttpRequestUtils.getCookiesJson(cookies); 1173 CharSequence headersJson = HttpRequestUtils.getHttpHeadersJson(request); 1174 1175 final String NULL = "null"; 1176 final char COLON = ':'; 1177 final char COMMA = ','; 1178 StringBuilder sb = new StringBuilder(); 1179 1180 sb.append('{'); 1181 sb.append(q("remoteUser")).append(COLON).append(remoteUser == null ? NULL : q(remoteUser)).append(COMMA); 1182 sb.append(q("remoteRole")).append(COLON).append(remoteRole == null ? NULL : q(remoteRole)).append(COMMA); 1183 sb.append(q("remoteAddr")).append(COLON).append(remoteAddr == null ? NULL : q(remoteAddr)).append(COMMA); 1184 sb.append(q("remoteHost")).append(COLON).append(remoteHost == null ? NULL : q(remoteHost)).append(COMMA); 1185 sb.append(q("remotePort")).append(COLON).append(remotePort == null ? NULL : Integer.toString(remotePort)).append(COMMA); 1186 sb.append(q("client")).append(COLON).append(clientInfoJson).append(COMMA); 1187 sb.append(q("headers")).append(COLON).append(headersJson).append(COMMA); 1188 sb.append(q("cookies")).append(COLON).append("[]".equals(cookiesJson) ? NULL : cookiesJson).append(COMMA); 1189 sb.append(q("httpSession")).append(COLON).append(sessionInfoJson); 1190 sb.append('}'); 1191 return sb.toString(); 1192 } 1193 1194 /** 1195 * Generate Statement Info JSON-formatted string 1196 * 1197 * @param instanceName 1198 * @param statementId 1199 * @param unprocessedNamedPreparedStatement 1200 * @param paramArrayJson 1201 * @return 1202 */ 1203 private static String generateStatementInfoJson( // 1204 String instanceName, // 1205 String statementId, // 1206 String unprocessedNamedPreparedStatement, // 1207 String paramArrayJson) { 1208 final String NULL = "null"; 1209 final char COLON = ':'; 1210 final char COMMA = ','; 1211 StringBuilder sb = new StringBuilder(); 1212 1213 sb.append('{'); 1214 sb.append(q("instanceName")).append(COLON).append(q(instanceName)).append(COMMA); 1215 sb.append(q("statementId")).append(COLON).append(statementId == null ? NULL : q(statementId)).append(COMMA); 1216 sb.append(q("statement")).append(COLON).append(q(unprocessedNamedPreparedStatement)).append(COMMA); 1217 sb.append(q("parameters")).append(COLON).append(paramArrayJson); 1218 sb.append('}'); 1219 return sb.toString(); 1220 } 1221 1222 /** 1223 * Generate Execution Result JSON-formatted string 1224 * 1225 * @param timestamp 1226 * @param updateResultJson 1227 * @return 1228 */ 1229 private static String generateExecutionResultJson( // 1230 long timestamp, // 1231 String updateResultJson // 1232 ) { 1233 final char COLON = ':'; 1234 final char COMMA = ','; 1235 StringBuilder sb = new StringBuilder(); 1236 1237 sb.append('{'); 1238 sb.append(q("timestamp")).append(COLON).append(timestamp).append(COMMA); 1239 sb.append(q("updateResult")).append(COLON).append(updateResultJson); 1240 sb.append('}'); 1241 return sb.toString(); 1242 } 1243} 1244 1245/* 1246Please contact FBSQL Team by E-Mail fbsql.team@gmail.com 1247or visit https://fbsql.github.io if you need additional 1248information or have any questions. 1249*/ 1250 1251/* EOF */