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 */