1 | /** |
2 | * Copyright 2007 ATG DUST Project |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * |
7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 |
8 | * |
9 | * Unless required by applicable law or agreed to in writing, software |
10 | * distributed under the License is distributed on an "AS IS" BASIS, |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | * See the License for the specific language governing permissions and limitations under the License. |
13 | */ |
14 | |
15 | package atg.adapter.gsa; |
16 | |
17 | import java.io.BufferedReader; |
18 | import java.io.File; |
19 | import java.io.FileInputStream; |
20 | import java.io.InputStreamReader; |
21 | import java.util.ArrayList; |
22 | import java.util.Arrays; |
23 | import java.util.Collection; |
24 | import java.util.Iterator; |
25 | import java.util.List; |
26 | |
27 | import org.apache.log4j.Logger; |
28 | |
29 | /** |
30 | * THIS IS A REALLY SIMPLE APPLICATION THAT |
31 | * TAKES A STRING ARRAY OF SQL FILES AND |
32 | * RETURNS ALL OF THE SQL STATEMENTS THAT |
33 | * MUST BE EXECUTED WITHIN THE FILES |
34 | * DATE: MAY 23, 2000 |
35 | * |
36 | * |
37 | * @author jb |
38 | * 7/14/2002 |
39 | */ |
40 | public class SQLFileParser |
41 | { |
42 | |
43 | private static Logger log = Logger.getLogger(SQLFileParser.class); |
44 | |
45 | // **************************************** |
46 | // THERE IS ONLY ONE CLASS VARIABLE |
47 | // FOR THIS CLASS. THAT VARIABLE IS TO |
48 | // STORE THE FILE NAMES. |
49 | // **************************************** |
50 | |
51 | private String[] sFileNameArray = {""}; |
52 | /** sets the names of files to be parsed */ |
53 | private void setFileNameArray(String[] pFileNameArray) |
54 | { |
55 | sFileNameArray = pFileNameArray; |
56 | } |
57 | |
58 | // /** returns the names of files to be parsed */ |
59 | // private String[] getFileNameArray() |
60 | // { |
61 | // return (sFileNameArray); |
62 | // } |
63 | |
64 | // ============== CONSTRUCTORS ================= |
65 | /** |
66 | * no arg constructor |
67 | */ |
68 | public SQLFileParser() {} |
69 | |
70 | // ================= PUBLIC METHODS ================= |
71 | |
72 | |
73 | private String trimDebuggingCharacters(String sText) |
74 | { |
75 | if ( sText == null ) return sText; |
76 | return sText.trim(); |
77 | /* |
78 | char[] ch = new char [sText.length()]; |
79 | ch = sText.toCharArray(); |
80 | String sTemp = ""; |
81 | for (int i=0; i < sText.length(); i++) |
82 | { |
83 | if ((ch[i] == '\n') || (ch[i] == '\r') || (ch[i] == '\t') || (ch[i] == ' ')) |
84 | { |
85 | // Keep going |
86 | sTemp = sTemp + " "; |
87 | } |
88 | else |
89 | { |
90 | sTemp = sTemp + ch[i]; |
91 | } |
92 | } |
93 | if (sTemp.length() > DEBUGGING_LENGTH) |
94 | { |
95 | return (sTemp.substring(0,DEBUGGING_LENGTH)); |
96 | } |
97 | else |
98 | { |
99 | return (sTemp); |
100 | } |
101 | */ |
102 | } |
103 | |
104 | private String RemoveWhiteSpaceInFront (String sText) |
105 | { |
106 | // ********************************************************************************** |
107 | // THIS FUNCTION REMOVES WHITESPACE IN THE FRONT OF A STRING. FOR INSTANCE |
108 | // IF A STRING IS PASSED TO IT AS ' SPACE', THEN IT WILL RETURN 'SPACE. |
109 | // ********************************************************************************** |
110 | char[] ch = new char [sText.length()]; |
111 | ch = sText.toCharArray(); |
112 | //String sTemp; |
113 | for (int i=0; i < sText.length(); i++) |
114 | { |
115 | if ((ch[i] == '\n') || (ch[i] == '\r') || (ch[i] == '\t')|| (ch[i] == ' ') || (ch[i] == ' ')) |
116 | { |
117 | // Keep going |
118 | } |
119 | else |
120 | { |
121 | return (sText.substring (i, sText.length())); |
122 | } |
123 | } |
124 | return (sText); |
125 | } |
126 | |
127 | // private String RemoveWhiteSpaceInEnd (String sText) |
128 | // { |
129 | //// ********************************************************************************** |
130 | //// THIS FUNCTION REMOVES WHITESPACE IN THE END OF A STRING. FOR INSTANCE |
131 | //// IF A STRING IS PASSED TO IT AS 'SPACE ', THEN IT WILL RETURN 'SPACE. |
132 | //// ********************************************************************************** |
133 | // char[] ch = new char [sText.length()]; |
134 | // ch = sText.toCharArray(); |
135 | // String sTemp; |
136 | // for (int i= sText.length() - 1; i > 0; i--) |
137 | // { |
138 | // if ((ch[i] == '\n') || (ch[i] == '\r') || (ch[i] == '\t')|| (ch[i] == ' ') || (ch[i] == ' ')) |
139 | // { |
140 | // // Keep going |
141 | // } |
142 | // else |
143 | // { |
144 | // return (sText.substring (0, i +1)); |
145 | // } |
146 | // } |
147 | // return (sText); |
148 | // } |
149 | |
150 | private String RemoveWhiteSpaceFromString (String sText) |
151 | { |
152 | if ( sText == null ) return null; |
153 | return sText.trim(); |
154 | |
155 | // ********************************************************************************** |
156 | // THIS FUNCTION REMOVES WHITESPACE IN THE FRONT AND END OF A STRING. FOR INSTANCE |
157 | // IF A STRING IS PASSED TO IT AS ' SPACE ', THEN IT WILL RETURN 'SPACE. |
158 | // ********************************************************************************** |
159 | /* |
160 | sText = RemoveWhiteSpaceInFront (sText); |
161 | sText = RemoveWhiteSpaceInEnd (sText); |
162 | return (sText); |
163 | */ |
164 | } |
165 | |
166 | |
167 | private int countFileArraySize(String[] sArray) |
168 | { |
169 | |
170 | // **************************************** |
171 | // THIS IS A USEFUL FUNCTION TO |
172 | // COUNT THE SIZE OF AN ARRAY. IT |
173 | // TAKES AN ARRAY AS A PARAMETER |
174 | // AND IT RETURNS AN INTEGER AS |
175 | // THE COUNT. IN THIS CASE WE |
176 | // DO NOT NEED TO PASS THE ARRAY |
177 | // SINCE IT IS A CLASS VARIABLE |
178 | // **************************************** |
179 | |
180 | List<String> newList = Arrays.asList(sArray); |
181 | return (newList.size()); |
182 | } |
183 | |
184 | private boolean checkForKeywords (String sLineRead) |
185 | { |
186 | |
187 | // **************************************** |
188 | // THIS IS ONE OF THE MOST CRITICAL |
189 | // FUNCTIONS IN THIS PROGRAM. IT |
190 | // TAKES THE RESERVED WORDS FROM |
191 | // SQL INSTALL SCRIPTS AND RESERVES |
192 | // THEM WITHIN THIS APPLICATION. |
193 | // EACH WORD LISTED BELOW STARTS |
194 | // A SQL STATEMENT. |
195 | // **************************************** |
196 | |
197 | String[] sKeywordList = {"BEGIN ", "CREATE ", "INSERT ", "UPDATE ", "DELETE ", "COMMIT ", "COMMIT", "ALTER ", "DROP ", "GRANT ", "GO", "IF OBJECT_ID"}; |
198 | List<String> lList = Arrays.asList(sKeywordList); |
199 | int iArraySize = lList.size(); |
200 | |
201 | sLineRead = RemoveWhiteSpaceInFront(sLineRead); |
202 | //sLineRead = sLineRead.trim(); |
203 | // something about using trim() doesn't work with the overall logic of the program. |
204 | |
205 | for (int i=0; i< iArraySize; i++) |
206 | { |
207 | if (sLineRead.toUpperCase().startsWith(sKeywordList[i].toUpperCase()) ) |
208 | { |
209 | // we need to check more than just whether the line starts |
210 | // with the keyword. we also have to try to determine that |
211 | // the keyword we found is actually NOT just the beginning of a |
212 | // bigger word. for example, GOogle. |
213 | // so... |
214 | if ( sLineRead.length() == sKeywordList[i].trim().length() ) { |
215 | // if the whole line is just the keyword... |
216 | return true; |
217 | } else if ( sLineRead.charAt( sKeywordList[i].trim().length() ) == ' ' ) { |
218 | // if the char after the word is a space |
219 | return true; |
220 | } else if ( sLineRead.charAt( sKeywordList[i].trim().length() ) == '(' ) { |
221 | // if the char after the word is a right paren "(" |
222 | return true; |
223 | } |
224 | } |
225 | } |
226 | return (false); |
227 | } |
228 | |
229 | private String checkForComments(String sLineRead) |
230 | { |
231 | |
232 | // **************************************** |
233 | // THIS FUNCTION HAS ONE DUTY. IT CHECKS |
234 | // FOR LINES IN SQL SCRIPT THAT HAVE |
235 | // COMMENTS IN THEM. IF IT FINDS A LINE |
236 | // IN A SQL SCRIPT THAT WOULD BE RESERVED |
237 | // FOR A COMMENT IT WILL SKIP THAT LINE. |
238 | // **************************************** |
239 | |
240 | String[] sKeywordList = {"--", "//"}; |
241 | List<String> lList = Arrays.asList(sKeywordList); |
242 | int iArraySize = lList.size(); |
243 | |
244 | //sLineRead = sLineRead; |
245 | for (int i=0; i< iArraySize; i++) |
246 | { |
247 | if (sLineRead.toUpperCase().indexOf(sKeywordList[i]) > -1) |
248 | { |
249 | return (sLineRead.substring(0,sLineRead.indexOf(sKeywordList[i]))); |
250 | } |
251 | } |
252 | return (sLineRead); |
253 | } |
254 | |
255 | private String checkForSemiColons(String sCurrentLine) |
256 | { |
257 | |
258 | // **************************************** |
259 | // THIS FUNCTION HAS ONE DUTY. IT CHECKS |
260 | // FOR SQL STATEMENTS THAT END IN THE |
261 | // SEMICOLON PUNCTUATION AND REMOVES |
262 | // THE SEMICOLON. |
263 | // **************************************** |
264 | |
265 | if (sCurrentLine.indexOf(";") > -1) |
266 | { |
267 | sCurrentLine = sCurrentLine.substring (0, sCurrentLine.indexOf(";")); |
268 | } |
269 | return (sCurrentLine); |
270 | |
271 | } |
272 | |
273 | private String checkForEndLines(String sCurrentLine) |
274 | { |
275 | |
276 | // **************************************** |
277 | // THIS FUNCTION HAS ONE DUTY. IT CHECKS |
278 | // FOR SQL STATEMENTS THAT END IN THE |
279 | // SEMICOLON PUNCTUATION AND REMOVES |
280 | // THE SEMICOLON. Sometimes if there |
281 | // is division in a view that is created |
282 | // the end line will be used, and sometimes |
283 | // it is used to execute a statement. |
284 | // **************************************** |
285 | sCurrentLine = RemoveWhiteSpaceFromString(sCurrentLine); |
286 | int iLength = sCurrentLine.length() -2; |
287 | //int iIndex = sCurrentLine.indexOf("/"); |
288 | if ((sCurrentLine.indexOf("/") > -1) && (sCurrentLine.indexOf("/") >= iLength)) |
289 | { |
290 | sCurrentLine = sCurrentLine.substring (0, sCurrentLine.indexOf("/")); |
291 | } |
292 | else if ((sCurrentLine.indexOf("/") > -1) && (sCurrentLine.indexOf("/") == 0)) |
293 | { |
294 | sCurrentLine = ""; |
295 | } |
296 | |
297 | sCurrentLine = sCurrentLine + " "; |
298 | return (sCurrentLine); |
299 | } |
300 | |
301 | |
302 | |
303 | private List<String> readFileAndLoadData () |
304 | { |
305 | |
306 | // **************************************** |
307 | // THIS FUNCTION IS THE ENGINE FOR THIS |
308 | // APPLICATION. THIS FUNCTION OPENS |
309 | // THE FIRST FILE FROM THE FILE ARRAY |
310 | // LIST. THEN IT READS THROUGH THE |
311 | // FILE LINE BY LINE BUILDING A VECTOR |
312 | // OF SQL SCRIPT COMMANDS. WHEN IT IS |
313 | // DONE PROCESSING IT PASSES A NEAT |
314 | // VECTOR OF SQL COMMANDS TO THE |
315 | // PROGRAM THAT CALLED THIS FUNCTION |
316 | // **************************************** |
317 | // THIS PARTICULAR METHOD IS LONG AND |
318 | // TERRIBLE TO READ BECAUSE IT WAS WRITTEN |
319 | // IN ONE SITTING. AND SINCE IT SEEMED |
320 | // LIKE THE TYPE OF FUNCTION THAT LOOKS |
321 | // SO BAD TO A CODE REVIEWER, BY LEAVING |
322 | // IT IN THE CODE IT PROVIDES GOOD |
323 | // HUMOR RELIEF FOR THE DEVELOPER GIVING |
324 | // A CODE REVIEW TO THIS PROGRAM. |
325 | // **************************************** |
326 | |
327 | // **************************************** |
328 | // VARIABLE DECLARATIONS |
329 | String sFileName = ""; |
330 | String line = ""; |
331 | String lineTemp = ""; |
332 | String sSqlBuffer = ""; |
333 | //String sFindSemiColon = ""; |
334 | |
335 | boolean bKeywordAlreadyFound = false; |
336 | boolean bKeywordInString = false; |
337 | |
338 | List<String> vSql = new ArrayList<String>(); |
339 | |
340 | BufferedReader in = null; |
341 | |
342 | File fFile = null; |
343 | FileInputStream fis = null; |
344 | |
345 | InputStreamReader Fin = null; |
346 | |
347 | //Object obj = null; |
348 | |
349 | int iSizeOfVector = countFileArraySize(sFileNameArray); |
350 | // END VARIABLE DECLARATIONS |
351 | // **************************************** |
352 | |
353 | // **************************************** |
354 | // LOOPING THROUGH THE VECTOR OF FILE NAMES |
355 | for (int iFile = 0; iFile < iSizeOfVector; iFile++) |
356 | { |
357 | |
358 | // GETTING THE FIRST FILE NAME FROM THE VECTOR |
359 | sFileName = sFileNameArray[iFile]; |
360 | |
361 | try // Try to open the file |
362 | { |
363 | //File fFile = new File (sDirectory, sFilename); |
364 | fFile = new File (sFileName); |
365 | fis = new FileInputStream(fFile); |
366 | Fin = new InputStreamReader(fis); |
367 | in = new BufferedReader(Fin); |
368 | |
369 | //if (isLoggingDebug()) logDebug ("Able to open file: " + sFileName); |
370 | } |
371 | catch (Exception e) |
372 | { |
373 | // logError("* Error opening file :" + sFileName + ": *"); |
374 | return (vSql); |
375 | } // Unable to open the file |
376 | |
377 | |
378 | try // Try to read the file line by line |
379 | { |
380 | //String sSqlStatement = ""; |
381 | sSqlBuffer = ""; |
382 | // Open file and read first line |
383 | line = in.readLine(); |
384 | |
385 | lineTemp = line; |
386 | |
387 | while (line != null) // It is not the end of the file, LOOK FOR KEY WORDS |
388 | { |
389 | //log.info ("DEBUG:" + line); |
390 | |
391 | lineTemp = line; |
392 | lineTemp = checkForComments(lineTemp); |
393 | bKeywordInString = checkForKeywords(lineTemp); |
394 | |
395 | // if this line is starting a stored procedure declaration, parse it |
396 | // specially. added this block to correctly parse stored procedures in oracle. |
397 | // marty - 7/11/2002 |
398 | if ( bKeywordInString && isStartOfStoredProcedure( lineTemp ) ) { |
399 | |
400 | String command = parseStoredProcedure( lineTemp, in ); |
401 | vSql.add( command ); |
402 | line = in.readLine(); |
403 | continue; |
404 | } |
405 | |
406 | // if this line is starting an 'if exists...' statement for MS SQL Server, parse |
407 | // if specially. added to work around bug 64622 |
408 | if ( bKeywordInString && isStartOfIfExistsCommand( lineTemp ) ) { |
409 | String command = parseIfExistsCommand( lineTemp, in ); |
410 | vSql.add( command ); |
411 | line = in.readLine(); |
412 | continue; |
413 | } |
414 | |
415 | if (bKeywordAlreadyFound) |
416 | { |
417 | if (bKeywordInString) |
418 | { |
419 | sSqlBuffer = checkForSemiColons(sSqlBuffer); |
420 | sSqlBuffer = checkForEndLines(sSqlBuffer); |
421 | vSql.add(sSqlBuffer); |
422 | sSqlBuffer = lineTemp; |
423 | } |
424 | else // (!bKeywordInString} |
425 | { |
426 | sSqlBuffer = sSqlBuffer + " " + lineTemp; |
427 | } |
428 | } |
429 | else |
430 | { |
431 | if (bKeywordInString) |
432 | { |
433 | bKeywordAlreadyFound = bKeywordInString; |
434 | sSqlBuffer = sSqlBuffer + lineTemp; |
435 | } |
436 | } |
437 | |
438 | //Get the next line |
439 | line = in.readLine(); |
440 | } // End while |
441 | |
442 | try |
443 | { |
444 | sSqlBuffer = checkForSemiColons(sSqlBuffer); |
445 | sSqlBuffer = checkForEndLines(sSqlBuffer); |
446 | vSql.add(sSqlBuffer); |
447 | } |
448 | catch (Exception ex) |
449 | { |
450 | //logError ("No element to add" + ex); |
451 | |
452 | } |
453 | |
454 | } |
455 | catch (Exception e) |
456 | { |
457 | //logError("Could not read one line in file: " + sFileName + "\n"); |
458 | return (vSql); |
459 | } // Unable to read one of the lines in the file |
460 | |
461 | try // To close the file streams |
462 | { |
463 | Fin.close(); |
464 | fis.close(); |
465 | //fFile.close(); |
466 | } |
467 | catch (Exception ex) |
468 | { |
469 | //logError ("Unable to close files\n"); |
470 | return (vSql); |
471 | } // If the files could not be closed |
472 | } |
473 | return (vSql); |
474 | } |
475 | |
476 | /** this method returns true if this line is the start of a check for whether an object exists |
477 | * in MS SQL Server. otherwise it returns false. |
478 | */ |
479 | private boolean isStartOfIfExistsCommand( String pLine ) { |
480 | // i think this is only on MS SQL Server. hard-coding the logic. |
481 | return pLine.trim().toUpperCase().startsWith("IF OBJECT_ID"); |
482 | } |
483 | |
484 | /** used to parse an if-exists method. the parsing logic is different than our usual behavior |
485 | * so it has been special cased. |
486 | * @return String the parsed SQL command |
487 | * @exception Exception if an error occurs |
488 | */ |
489 | private String parseIfExistsCommand( String pCommand, BufferedReader pIn ) |
490 | throws Exception |
491 | { |
492 | // a pretty big hack, but just parse until we find the next "go" |
493 | String line = pIn.readLine(); |
494 | if ( line != null ) line = line.trim(); |
495 | while ( line != null && (! line.toUpperCase().startsWith("GO")) ) |
496 | { |
497 | pCommand = pCommand + " " + line; |
498 | line = pIn.readLine(); |
499 | if ( line != null ) line = line.trim(); |
500 | } |
501 | |
502 | return pCommand; |
503 | } |
504 | |
505 | /** this method returns true if this line is the start of a definition for a stored procedure. |
506 | * otherwise, it returns false. |
507 | */ |
508 | private boolean isStartOfStoredProcedure( String pLine ) { |
509 | // i only know of this on oracle, so i'm temporarily coding it to work specifically |
510 | // with oracle. |
511 | return pLine.trim().toUpperCase().startsWith( "CREATE OR REPLACE PROCEDURE" ); |
512 | } |
513 | |
514 | /** this method is used to parse a stored procedure. since stored procedures may contain |
515 | * sub-commands within them, we use a special process to parse them. |
516 | * |
517 | * @return String the parsed SQL command |
518 | * @exception Exception if an error occurs |
519 | */ |
520 | private String parseStoredProcedure( String pCommand, BufferedReader pIn ) |
521 | throws Exception |
522 | { |
523 | // this is pretty much a total hack, but i just want to get it working for now |
524 | // ASSUME we just parse until we find a line that starts with "/" |
525 | String line = pIn.readLine(); |
526 | if ( line != null ) line = line.trim(); |
527 | while ( line != null && (! line.startsWith("/")) ) |
528 | { |
529 | pCommand = pCommand + " " + line; |
530 | line = pIn.readLine(); |
531 | if ( line != null ) line = line.trim(); |
532 | } |
533 | |
534 | return pCommand; |
535 | } |
536 | |
537 | |
538 | public synchronized Collection<String> parseSQLFiles( String[] pFiles ) |
539 | { |
540 | setFileNameArray( pFiles ); |
541 | // **************************************** |
542 | // THIS FUNCTION CALLS THE FUNCTION THAT |
543 | // READS THROUGH THE ARRAY OF FILES PASSED |
544 | // TO IT AND IT RETURNS A VECTOR OF SQL |
545 | // STATEMENTS. MOST OF THESE WILL TEND |
546 | // TO BE CREATE AND DROP STATEMENTS. |
547 | // **************************************** |
548 | List<String> v = new ArrayList<String>(); |
549 | v = readFileAndLoadData(); |
550 | String s = ""; |
551 | for (int i=0;i<v.size();i++) |
552 | { |
553 | s = v.get(i).toString(); |
554 | s = trimDebuggingCharacters (s); |
555 | |
556 | if ( logToSystemOut ) { |
557 | //log.info("\n\n" + s ); |
558 | } else { |
559 | // if (isLoggingInfo ()) logInfo(s); |
560 | } |
561 | } |
562 | |
563 | return v; |
564 | } |
565 | |
566 | public Collection<String> parseSQLFile( String pFile ) |
567 | { |
568 | String[] files = { pFile }; |
569 | return parseSQLFiles( files ); |
570 | } |
571 | |
572 | // This is useful for debugging this application |
573 | public boolean logToSystemOut = false; |
574 | public static void main (String[] args) |
575 | { |
576 | SQLFileParser t = new SQLFileParser(); |
577 | t.logToSystemOut = true; |
578 | Iterator<String> cmds = t.parseSQLFiles( args ).iterator(); |
579 | while ( cmds.hasNext() ) { |
580 | log.info("\n\n" + cmds.next() ); |
581 | } |
582 | } |
583 | |
584 | |
585 | |
586 | } |
587 | |