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.antlr4.parser;
029
030import java.nio.charset.StandardCharsets;
031import java.util.ArrayList;
032import java.util.Base64;
033import java.util.List;
034import java.util.Locale;
035import java.util.UUID;
036
037import javax.servlet.ServletConfig;
038
039import org.antlr.v4.runtime.CharStreams;
040import org.antlr.v4.runtime.CommonTokenStream;
041import org.antlr.v4.runtime.Lexer;
042import org.antlr.v4.runtime.misc.Interval;
043import org.antlr.v4.runtime.tree.ParseTree;
044import org.antlr.v4.runtime.tree.ParseTreeWalker;
045import org.fbsql.antlr4.generated.FbsqlBaseListener;
046import org.fbsql.antlr4.generated.FbsqlLexer;
047import org.fbsql.antlr4.generated.FbsqlParser;
048import org.fbsql.antlr4.generated.FbsqlParser.Connect_to_stmtContext;
049import org.fbsql.antlr4.generated.FbsqlParser.Connection_aliasContext;
050import org.fbsql.antlr4.generated.FbsqlParser.Connection_pool_size_maxContext;
051import org.fbsql.antlr4.generated.FbsqlParser.Connection_pool_size_minContext;
052import org.fbsql.antlr4.generated.FbsqlParser.Jdbc_connection_propertiesContext;
053import org.fbsql.antlr4.generated.FbsqlParser.Jdbc_driver_class_nameContext;
054import org.fbsql.antlr4.generated.FbsqlParser.Jdbc_urlContext;
055import org.fbsql.antlr4.generated.FbsqlParser.Native_sqlContext;
056import org.fbsql.antlr4.generated.FbsqlParser.PasswordContext;
057import org.fbsql.antlr4.generated.FbsqlParser.UserContext;
058import org.fbsql.servlet.CommonUtils;
059import org.fbsql.servlet.DbServlet;
060import org.fbsql.servlet.SqlParseUtils;
061import org.fbsql.servlet.StringUtils;
062
063/*
064 * ANTLR4 grammar:
065 *
066 * connect_to_stmt
067 *  : CONNECT TO jdbc_url
068 *    (
069 *     ( USER user ) |
070 *     ( PASSWORD password ) |
071 *     ( PROPERTIES jdbc_connection_properties ) |
072 *     ( DRIVER jdbc_driver_class_name ) |
073 *     ( LIB jar_file ( ',' jar_file )* ) |
074 *     ( CONNECTION POOL MIN connection_pool_size_min ) |
075 *     ( CONNECTION POOL MAX connection_pool_size_max ) |
076 *     ( UNDECLARED STATEMENTS ALLOW ) |
077 *     ( UNDECLARED STATEMENTS REJECT ) |
078 *     (
079 *      INCOMING CONNECTIONS ( ALLOW | REJECT )+ ( IF EXISTS '(' native_sql ')' )?
080 *     ) |
081 *    ( AS? connection_alias )
082 *    )*
083 */ ;
084
085public class ParseStmtConnectTo {
086        public static final String  NONEXPOSABLE_NAME_PREFIX = "NONEXPOSABLE_NAME:";
087        private static final String ENCODED_PASSWORD_PREFIX  = "base64:";
088
089        private static final int DEFAULT_CONNECTION_POOL_SIZE_MIN = 1;
090        private static final int DEFAULT_CONNECTION_POOL_SIZE_MAX = 100;
091
092        /**
093         * DTO (Data Transfer Object) that holds RDBMS connection meta data
094         * Field values comes from parsing of "CONNECT TO" SQL statement
095         * 
096         * @see SqlParseUtils#parseConnectStatement(String)
097         */
098        public class StmtConnectTo {
099
100                /**
101                 * Value from "CONNECT TO" clause
102                 */
103                public String jdbcUrl;
104
105                /**
106                 * Value from "DRIVER" clause
107                 */
108                public String driverClassName;
109
110                /**
111                 * Value from "JARS" clause
112                 */
113                public List<String> driverJars;
114
115                /**
116                 * Value from "USER" clause
117                 */
118                public String user;
119
120                /**
121                 * Value from "PASSWORD" clause
122                 */
123                public String password;
124
125                /**
126                 * Value from "PROPERTIES" clause
127                 * Java (*.properties) file contains JDBC properties
128                 */
129                public String jdbcPropertiesFile;
130
131                /**
132                 * Value from "CONNECTION POOL -> MIN" clause
133                 */
134                public int connectionPoolSizeMin = DEFAULT_CONNECTION_POOL_SIZE_MIN;
135
136                /**
137                 * Value from "CONNECTION POOL -> MAX" clause
138                 */
139                public int connectionPoolSizeMax = DEFAULT_CONNECTION_POOL_SIZE_MAX;
140
141                public boolean exposeUndeclaredStatements;
142
143                /**
144                 * Value from "IF EXISTS" clause
145                 */
146                public String authenticationQuery;
147
148                public boolean allowConnections;
149
150                /**
151                 * Value from "AS" clause
152                 */
153                public String instanceName;
154
155                @Override
156                public String toString() {
157                        return "StmtConnectTo [jdbcUrl=" + jdbcUrl + ", driverClassName=" + driverClassName + ", driverJars=" + driverJars + ", user=" + user + ", password=" + password + ", jdbcPropertiesFile=" + jdbcPropertiesFile + ", connectionPoolSizeMin=" + connectionPoolSizeMin + ", connectionPoolSizeMax=" + connectionPoolSizeMax + ", exposeUndeclaredStatements=" + exposeUndeclaredStatements + ", authenticationQuery=" + authenticationQuery + ", allowConnections=" + allowConnections + ", instanceName=" + instanceName + "]";
158                }
159
160        }
161
162        private static final String[] INCOMING_CONNECTIONS_ALLOW  = new String[] { "INCOMING", "CONNECTIONS", "ALLOW" };
163        private static final String[] INCOMING_CONNECTIONS_REJECT = new String[] { "INCOMING", "CONNECTIONS", "REJECT" };
164
165        private static final String[] UNDECLARED_STATEMENTS_ALLOW  = new String[] { "UNDECLARED", "STATEMENTS", "ALLOW" };
166        private static final String[] UNDECLARED_STATEMENTS_REJECT = new String[] { "UNDECLARED", "STATEMENTS", "REJECT" };
167
168        /**
169         * StmtConnectTo transfer object
170         */
171        private StmtConnectTo st;
172
173        public ParseStmtConnectTo() {
174                st            = new StmtConnectTo();
175                st.driverJars = new ArrayList<>();
176        }
177
178        /**
179         * CONNECT TO Statement parser
180         *
181         * E.g.: CONNECT TO ( SELECT * FROM MYTABLE ) ROLES (aamin, manager) AS myselect
182         *
183         * @param sql
184         * @return
185         */
186        public StmtConnectTo parse(ServletConfig servletConfig, String sql) {
187                Lexer       lexer  = new FbsqlLexer(CharStreams.fromString(sql));
188                FbsqlParser parser = new FbsqlParser(new CommonTokenStream(lexer));
189                ParseTree   tree   = parser.connect_to_stmt();
190
191                ParseTreeWalker.DEFAULT.walk(new FbsqlBaseListener() {
192
193                        @Override
194                        public void enterConnect_to_stmt(Connect_to_stmtContext ctx) {
195                                String[] array = new String[ctx.children.size()];
196                                int      n     = 0;
197                                for (ParseTree parseTree : ctx.children)
198                                        array[n++] = parseTree.getText().toUpperCase(Locale.ENGLISH);
199
200                                if (CommonUtils.indexOf(array, UNDECLARED_STATEMENTS_ALLOW) != -1)
201                                        st.exposeUndeclaredStatements = true;
202                                else if (CommonUtils.indexOf(array, UNDECLARED_STATEMENTS_REJECT) != -1)
203                                        st.exposeUndeclaredStatements = false;
204
205                                if (CommonUtils.indexOf(array, INCOMING_CONNECTIONS_ALLOW) != -1)
206                                        st.allowConnections = true;
207                                else if (CommonUtils.indexOf(array, INCOMING_CONNECTIONS_REJECT) != -1)
208                                        st.allowConnections = false;
209                        }
210
211                        @Override
212                        public void enterJdbc_url(Jdbc_urlContext ctx) {
213                                st.jdbcUrl = StringUtils.unquote(ctx.getText());
214                        }
215
216                        @Override
217                        public void enterUser(UserContext ctx) {
218                                st.user = StringUtils.unquote(ctx.getText());
219                        }
220
221                        @Override
222                        public void enterPassword(PasswordContext ctx) {
223                                st.password = StringUtils.unquote(ctx.getText());
224                        }
225
226                        @Override
227                        public void enterJdbc_driver_class_name(Jdbc_driver_class_nameContext ctx) {
228                                st.driverClassName = StringUtils.unquote(ctx.getText());
229                        }
230
231                        @Override
232                        public void enterJdbc_connection_properties(Jdbc_connection_propertiesContext ctx) {
233                                st.jdbcPropertiesFile = StringUtils.unquote(ctx.getText());
234                        }
235
236                        @Override
237                        public void enterConnection_pool_size_min(Connection_pool_size_minContext ctx) {
238                                try {
239                                        st.connectionPoolSizeMin = Integer.parseInt(ctx.getText());
240                                } catch (NumberFormatException e) {
241                                        if (servletConfig != null) {
242                                                String s = servletConfig.getInitParameter("CONNECTION_POOL_SIZE_MIN");
243                                                if (s != null && !s.trim().isEmpty())
244                                                        try {
245                                                                st.connectionPoolSizeMin = Integer.parseInt(s.trim());
246                                                        } catch (NumberFormatException e1) {
247                                                                e1.printStackTrace();
248                                                        }
249                                        }
250                                }
251                        }
252
253                        @Override
254                        public void enterConnection_pool_size_max(Connection_pool_size_maxContext ctx) {
255                                try {
256                                        st.connectionPoolSizeMax = Integer.parseInt(ctx.getText());
257                                } catch (NumberFormatException e) {
258                                        if (servletConfig != null) {
259                                                String s = servletConfig.getInitParameter("CONNECTION_POOL_SIZE_MAX");
260                                                if (s != null && !s.trim().isEmpty())
261                                                        try {
262                                                                st.connectionPoolSizeMax = Integer.parseInt(s.trim());
263                                                        } catch (NumberFormatException e1) {
264                                                                e1.printStackTrace();
265                                                        }
266                                        }
267                                }
268                        }
269
270                        @Override
271                        public void enterNative_sql(Native_sqlContext ctx) {
272                                int      startIndex = ctx.start.getStartIndex();
273                                int      stopIndex  = ctx.stop.getStopIndex();
274                                Interval interval   = new Interval(startIndex, stopIndex);
275                                st.authenticationQuery = ctx.start.getInputStream().getText(interval);
276                        }
277
278                        @Override
279                        public void enterConnection_alias(Connection_aliasContext ctx) {
280                                st.instanceName = StringUtils.unquote(ctx.getText());
281                        }
282
283                }, tree);
284
285                if (st.instanceName == null)
286                        st.instanceName = NONEXPOSABLE_NAME_PREFIX + UUID.randomUUID().toString();
287
288                if (st.password != null)
289                        if (st.password.startsWith(ENCODED_PASSWORD_PREFIX)) {
290                                st.password = st.password.substring(ENCODED_PASSWORD_PREFIX.length());
291                                st.password = new String(Base64.getDecoder().decode(st.password), StandardCharsets.UTF_8);
292                        }
293
294                if (DbServlet.DEBUG)
295                        System.out.println(st);
296
297                return st;
298        }
299}
300
301/*
302Please contact FBSQL Team by E-Mail fbsql.team@gmail.com
303or visit https://fbsql.github.io if you need additional
304information or have any questions.
305*/
306
307/* EOF */