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 java.io.BufferedReader; 031import java.io.FileInputStream; 032import java.io.IOException; 033import java.io.InputStream; 034import java.io.InputStreamReader; 035import java.lang.reflect.Method; 036import java.net.URL; 037import java.net.URLEncoder; 038import java.nio.charset.StandardCharsets; 039import java.nio.file.Files; 040import java.nio.file.Path; 041import java.nio.file.Paths; 042import java.text.NumberFormat; 043import java.text.ParseException; 044import java.util.ArrayList; 045import java.util.Collections; 046import java.util.HashMap; 047import java.util.List; 048import java.util.Locale; 049import java.util.Map; 050 051import org.fbsql.antlr4.parser.ParseNativeStmt; 052import org.fbsql.antlr4.parser.ParseNativeStmt.Procedure; 053import org.mozilla.javascript.Context; 054import org.mozilla.javascript.Function; 055import org.mozilla.javascript.Scriptable; 056 057public class CallUtils { 058 059 /* JS */ 060 private static final String PLT_OPTIONS_JS_FILE = "file"; 061 private static final String PLT_OPTIONS_JS_FUNCTION = "function"; 062 063 /* JVM */ 064 private static final String PLT_OPTIONS_JVM_CLASS = "class"; 065 private static final String PLT_OPTIONS_JVM_METHOD = "method"; 066 067 /* OS */ 068 private static final String PLT_OPTIONS_OS_FILE = "file"; 069 070 /* URL */ 071 private static final String PLT_OPTIONS_URL_URL = "url"; 072 073 /** 074 * Get CALL Statement Method 075 * 076 * @param sql 077 * @param proceduresMap 078 * @return 079 * @throws Exception 080 */ 081 public static NonNativeProcedure getCallStatementNonNativeProcedure(String sql, Map<String /* stored procedure name */, NonNativeProcedure> proceduresMap) { 082 String text = SqlParseUtils.canonizeSql(sql); 083 if (!text.startsWith(SqlParseUtils.SPECIAL_STATEMENT_CALL)) 084 return null; // Not a CALL statement 085 086 ParseNativeStmt parseNativeStmt = new ParseNativeStmt(proceduresMap.keySet()); 087 Procedure procedure = parseNativeStmt.parse(sql); 088 return proceduresMap.get(procedure.name); 089 } 090 091 public static String executeOsProgramm(String instanceDirectory, String optionsJson, Object[] parameters) throws Exception { 092 Map<String, Object> options = RhinoUtils.asMap(optionsJson); 093 String fileName = (String) options.get(PLT_OPTIONS_OS_FILE); 094 Path pgmPath; 095 if (fileName.charAt(0) == '/') 096 pgmPath = Paths.get(fileName); 097 else 098 pgmPath = Paths.get(instanceDirectory + '/' + fileName); 099 100 List<String> cmds = new ArrayList<>(parameters.length); 101 cmds.add(pgmPath.toString()); 102 for (Object parameter : parameters) 103 cmds.add(parameter.toString()); 104 105 ProcessBuilder processBuilder = new ProcessBuilder(cmds); 106 try { 107 108 Process process = processBuilder.start(); 109 110 BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); 111 112 StringBuilder sb = new StringBuilder(); 113 String line; 114 while ((line = reader.readLine()) != null) 115 sb.append(line); 116 int exitCode = process.waitFor(); 117 return sb.toString().trim(); 118 } catch (IOException e) { 119 e.printStackTrace(); 120 } catch (InterruptedException e) { 121 e.printStackTrace(); 122 } 123 return null; 124 } 125 126 public static String executeUrl(String instanceDirectory, String optionsJson, Map<String, Object> parametersMap) throws Exception { 127 Map<String, Object> options = RhinoUtils.asMap(optionsJson); 128 String urlStr = (String) options.get(PLT_OPTIONS_URL_URL); 129 Path path; 130 InputStream is; 131 if (urlStr.charAt(0) == '/') { 132 path = Paths.get(urlStr); 133 is = new FileInputStream(path.toFile()); 134 } else if (!urlStr.startsWith("http://") && !urlStr.startsWith("https://")) { 135 path = Paths.get(instanceDirectory + '/' + urlStr); 136 is = new FileInputStream(path.toFile()); 137 } else { 138 boolean first = true; 139 for (Map.Entry<String, Object> entry : parametersMap.entrySet()) { 140 String name = entry.getKey(); 141 String value = URLEncoder.encode(entry.getValue().toString().trim(), StandardCharsets.UTF_8.name()); 142 if (first) { 143 first = false; 144 urlStr += '?'; 145 } else 146 urlStr += '&'; 147 urlStr += name + '=' + value; 148 } 149 URL url = new URL(urlStr); 150 is = url.openStream(); 151 } 152 try (is) { 153 return StringUtils.inputSreamToString(is); 154 } 155 } 156 157 public static String executeFile(String instanceDirectory, String text) throws Exception { 158 return executeUrl(instanceDirectory, "file://" + text, Collections.EMPTY_MAP); 159 } 160 161 /** 162 * Get Java Method 163 * 164 * @param text 165 * @return 166 * @throws Exception 167 */ 168 public static Method getMethod(String text) throws Exception { 169 Map<String, Object> cfg = RhinoUtils.asMap(text); 170 String className = (String) cfg.get(PLT_OPTIONS_JVM_CLASS); 171 String methodName = (String) cfg.get(PLT_OPTIONS_JVM_METHOD); 172 173 Class<?> clazz = Class.forName(className); 174 Method[] methods = clazz.getMethods(); 175 for (Method method : methods) 176 if (method.getName().equals(methodName)) 177 return method; 178 throw new Exception("Java method declared, but not found in the class"); 179 } 180 181 /** 182 * Get JavaScript Function 183 * 184 * @param text 185 * @param mapScopes 186 * @param mapFunctions 187 * @return 188 * @throws Exception 189 */ 190 public static JsFunction getFunction(String instanceDirectory, String text, Map<String /* js file name */, Scriptable> mapScopes, Map<String /* js file name */, Map<String /* function name */, Function>> mapFunctions) throws Exception { 191 Map<String, Object> cfg = RhinoUtils.asMap(text); 192 String jsFileName = (String) cfg.get(PLT_OPTIONS_JS_FILE); 193 String functionName = (String) cfg.get(PLT_OPTIONS_JS_FUNCTION); 194 195 Path jsFilePath; 196 if (jsFileName.charAt(0) == '/') 197 jsFilePath = Paths.get(jsFileName); // convert className to JavaScript file name 198 else 199 jsFilePath = Paths.get(instanceDirectory + '/' + jsFileName); // convert className to JavaScript file name 200 201 // 202 // initize Rhino 203 // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino 204 // 205 Context ctx = Context.enter(); 206 207 ctx.setLanguageVersion(Context.VERSION_1_7); 208 ctx.setOptimizationLevel(9); // Rhino optimization: https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Optimization 209 210 Scriptable scope = mapScopes.get(jsFileName); 211 if (scope == null) { 212 String content = new String(Files.readAllBytes(jsFilePath), StandardCharsets.UTF_8); 213 scope = ctx.initStandardObjects(); 214 ctx.evaluateString(scope, content, "script", 1, null); 215 mapScopes.put(jsFileName, scope); 216 } 217 218 Map<String /* function name */, Function> map = mapFunctions.get(jsFileName); 219 if (map == null) { 220 map = new HashMap<>(); 221 mapFunctions.put(jsFileName, map); 222 } 223 224 Function fct = map.get(functionName); 225 if (fct == null) { 226 fct = (Function) scope.get(functionName, scope); 227 map.put(functionName, fct); 228 } 229 230 JsFunction jsFunction = new JsFunction(); 231 jsFunction.scope = scope; 232 jsFunction.function = fct; 233 234 return jsFunction; 235 } 236 237 /** 238 * Get CALL Statement Parameter Values 239 * 240 * @param sql 241 * @param namedParametersMap 242 * @param parameterValues 243 * @throws Exception 244 */ 245 public static void getCallStatementParameterValues(ParseNativeStmt parseNativeStmt, String sql, Map<String /* parameter name */, Object /* parameter value */> namedParametersMap, List<Object> parameterValues) throws Exception { 246 Procedure procedure = parseNativeStmt.parse(sql); 247 248 // int posLeft = sql.indexOf('('); 249 // if (posLeft == -1) 250 // throw new IllegalArgumentException(MessageFormat.format("Syntax error: missing left parenthesis: {0}", sql)); 251 // 252 // int posRight = sql.lastIndexOf(')'); 253 // if (posRight != sql.length() - 1) 254 // throw new IllegalArgumentException(MessageFormat.format("Syntax error: missing right parenthesis: {0}", sql)); 255 256 //String params = sql.substring(posLeft + 1, posRight).trim(); 257 258 List<String> parameterNames = new ArrayList<>(); 259 for (Object value : parameterValues) // add corresponding entries to parameterNames 260 parameterNames.add(null); 261 // 262 parseNamedSqlParameters(procedure.parameters, parameterNames, parameterValues); 263 264 for (int i = 0; i < parameterNames.size(); i++) { 265 String parameterName = parameterNames.get(i); 266 Object parameterValue = parameterValues.get(i); 267 if (parameterName != null) { 268 parameterValue = namedParametersMap.get(parameterName); 269 parameterValues.set(i, parameterValue); 270 } 271 } 272 } 273 274 /** 275 * Parse named SQL parameters 276 * 277 * @param s - parameters string E.g "'John', 123.45, NULL, :price" 278 * @param parameterNames - parsed parameter names (output) 279 * @param parameterValues - parsed parameter values (output) 280 */ 281 private static void parseNamedSqlParameters(List<String> strParameters, List<String> parameterNames, List<Object> parameterValues) { 282 for (String strParameter : strParameters) { 283 String parameterName; 284 Object parameterValue; 285 if (strParameter.startsWith("'") || strParameter.startsWith("\"")) { 286 parameterName = null; 287 parameterValue = strParameter.substring(1, strParameter.length() - 1); 288 } else if (strParameter.toUpperCase(Locale.ENGLISH).equals("NULL")) { 289 parameterName = null; 290 parameterValue = null; 291 } else if (strParameter.startsWith(":")) { 292 parameterName = strParameter.substring(1); 293 parameterValue = null; 294 } else { 295 parameterName = null; 296 try { 297 parameterValue = NumberFormat.getInstance().parse(strParameter); 298 } catch (ParseException e) { 299 parameterValue = strParameter; 300 } 301 } 302 parameterNames.add(parameterName); 303 parameterValues.add(parameterValue); 304 } 305 } 306 307 /** 308 * Parse SQL parameters 309 * 310 * @param s - parameters string E.g "'John', 123.45, NULL" 311 * @param parameterValues - parsed parameter values (output) 312 */ 313 public static void parseSqlParameters(ParseNativeStmt parseNativeStmt, String s, List<Object> parameterValues) { 314 Procedure procedure = parseNativeStmt.parse(s); 315 316 for (String strParameter : procedure.parameters) { 317 Object parameterValue; 318 if (strParameter.startsWith("'") || strParameter.startsWith("\"")) 319 parameterValue = strParameter.substring(1, strParameter.length() - 1); 320 else if (strParameter.toUpperCase(Locale.ENGLISH).equals("NULL")) 321 parameterValue = null; 322 else if (strParameter.startsWith(":")) 323 parameterValue = null; 324 else 325 try { 326 parameterValue = NumberFormat.getInstance().parse(strParameter); 327 } catch (ParseException e) { 328 parameterValue = strParameter; 329 } 330 parameterValues.add(parameterValue); 331 } 332 } 333 334} 335 336/* 337Please contact FBSQL Team by E-Mail fbsql.team@gmail.com 338or visit https://fbsql.github.io if you need additional 339information or have any questions. 340*/ 341 342/* EOF */