|
1 package hirondelle.web4j.readconfig; |
|
2 |
|
3 import java.util.*; |
|
4 import java.util.logging.*; |
|
5 import java.io.*; |
|
6 import java.util.regex.*; |
|
7 import java.lang.reflect.*; |
|
8 import javax.servlet.ServletContext; |
|
9 |
|
10 import hirondelle.web4j.util.Args; |
|
11 import hirondelle.web4j.util.Util; |
|
12 |
|
13 /** |
|
14 (UNPUBLISHED) Reads text files (having specific formats) located under the <tt>WEB-INF</tt> directory, |
|
15 and returns their contents as {@link Properties}. |
|
16 |
|
17 <P>In addition, this class returns {@link Set}s of {@link Class}es that are present under |
|
18 <tt>/WEB-INF/classes</tt>. This unusual policy allows the caller to use reflection upon |
|
19 such classes, to extract what would otherwise be configuration information. For example, the |
|
20 default implementation of {@link hirondelle.web4j.request.RequestParser} uses this technique to |
|
21 automatically map request URIs to concrete implementations of {@link hirondelle.web4j.action.Action}. |
|
22 Thus, the action implementor can configure when the action is called simply by adding a |
|
23 field satisfying some simple conventions. |
|
24 |
|
25 <P><em>Design Note</em>: In addition to the configuration facilities present in |
|
26 <tt>web.xml</tt>, it is sometimes useful to use properties files (see {@link Properties}), |
|
27 or similar items. Such files may be placed in the same directory as the class |
|
28 which uses them, but, in a web application, there is also the option of placing |
|
29 such files in the <tt>WEB-INF</tt> directory. |
|
30 */ |
|
31 public final class ConfigReader { |
|
32 |
|
33 /** Must be called upon startup in a web container. */ |
|
34 public static void init(ServletContext aContext){ |
|
35 fContext = aContext; |
|
36 } |
|
37 |
|
38 /** Type-safe enumeration for the kinds of text file supported by {@link ConfigReader}. */ |
|
39 public enum FileType { |
|
40 |
|
41 /** |
|
42 Standard java <tt>.properties</tt> file. |
|
43 See {@link Properties} for more information. |
|
44 */ |
|
45 PROPERTIES_FILE, |
|
46 |
|
47 /** |
|
48 Text file containing blocks of text, constants, and comments. |
|
49 <P>The {@link hirondelle.web4j.database.SqlStatement} class uses this style for |
|
50 underlying SQL statements, but it may be used for any similar case, |
|
51 where the format specified below is adequate. |
|
52 |
|
53 <P>The following terminology is used here : |
|
54 <ul> |
|
55 <li>Block - a multiline block of text with within braces, with an associated |
|
56 identifier (similar to a typical Java block) |
|
57 <li>Block Name - the identifier of a block, appearing on the first line, before the |
|
58 opening brace |
|
59 <li>Block Body - the text appearing between the opening and closing braces. One |
|
60 advantage of using this file type, instead of a |
|
61 {@link ConfigReader.FileType#PROPERTIES_FILE}, is |
|
62 that no line continuation characters are needed. |
|
63 </ul> |
|
64 |
|
65 <P>Example of a typical <tt>TEXT_BLOCK</tt> file (using |
|
66 SQL statements for Block Bodies) : |
|
67 <PRE> |
|
68 -- This is an example comment. |
|
69 -- This item exercises slight textual variations. |
|
70 -- The Block Name here is 'ADD_MESSAGE'. |
|
71 ADD_MESSAGE { |
|
72 INSERT INTO MyMessage -- another example comment |
|
73 (LoginName, Body, CreationDate) |
|
74 -- this line and the following line are also commented out |
|
75 -- VALUES (?,?,?) |
|
76 VALUES (?,?,?) |
|
77 } |
|
78 |
|
79 -- Here, 'constants' is a reserved Block Name. |
|
80 -- Any number of 'constants' blocks can be defined, anywhere |
|
81 -- in the file. Such constants must be defined before being |
|
82 -- referenced in a Block Body, however. |
|
83 constants { |
|
84 num_messages_to_view = 5 |
|
85 } |
|
86 |
|
87 -- Example of referring to a constant defined above. |
|
88 FETCH_RECENT_MESSAGES { |
|
89 SELECT |
|
90 LoginName, Body, CreationDate |
|
91 FROM MyMessage |
|
92 ORDER BY Id DESC LIMIT ${num_messages_to_view} |
|
93 } |
|
94 |
|
95 FETCH_NUM_MSGS_FOR_USER { |
|
96 SELECT COUNT(Id) FROM MyMessage WHERE LoginName=? |
|
97 } |
|
98 |
|
99 -- Browse Messages according to various criteria. |
|
100 -- Constants block used here just to demonstrate it |
|
101 -- is possible. |
|
102 constants { |
|
103 -- Each constant must appear on a *single line* only, which |
|
104 -- is not the best for long SQL statements. |
|
105 -- Typically, if a previously defined *sub-query* is needed, |
|
106 -- then a SQL statment may simply refer directly to that previously |
|
107 -- defined SQL statement. (See oracle.sql in the example application.) |
|
108 base_query = SELECT Id, LoginName, Body, CreationDate FROM MyMessage |
|
109 } |
|
110 |
|
111 BROWSE_MESSAGES { |
|
112 ${base_query} |
|
113 ORDER BY {0} {1} |
|
114 } |
|
115 |
|
116 BROWSE_MESSAGES_WITH_FILTER { |
|
117 ${base_query} |
|
118 WHERE {0} LIKE '{1}%' |
|
119 ORDER BY {2} {3} |
|
120 } |
|
121 </PRE> |
|
122 |
|
123 <P>The format details are as follows : |
|
124 <ul> |
|
125 <li>empty lines can appear only outside of blocks |
|
126 <li>the '<tt>--</tt>' character denotes a comment |
|
127 <li>there are no multiline comments. (Such comments are easier for the writer, |
|
128 but are much less clear for the reader.) |
|
129 <li>the body of each item is placed in a named block, bounded by |
|
130 '<tt><name> {</tt>' on an initial line, and '<tt>}</tt>' on an end line. |
|
131 The name given to the the block (<tt>ADD_MESSAGE</tt> for example) is how |
|
132 <tt>WEB4J</tt> identifies each block. The Block Name may correspond one to |
|
133 one with some item in code. For SQL statements, the Block Name must correspond |
|
134 to a <tt>public static final SqlId</tt> field. Upon startup, this allows verification |
|
135 that all items defined in the text source have a corresponding item in code. |
|
136 <li>'<tt>--</tt>' comments can appear in the Block Body as well |
|
137 <li>if desired, the Block Body may be indented, to make the Block more legible |
|
138 <li>the Block Name of '<tt>constants</tt>' is reserved. A <tt>constants</tt> |
|
139 block defines one or more simple textual substitution constants, as |
|
140 <tt>name = value</tt> pairs, one per line, that may be |
|
141 referenced later on in the file (see example). They are defined only to allow |
|
142 such substitution to occur <em>later</em> in the file. Any number of <tt>constants</tt> |
|
143 blocks can appear in a file. |
|
144 <li>Block Names and the names of constants satisfy |
|
145 {@link hirondelle.web4j.util.Regex#SIMPLE_SCOPED_IDENTIFIER}. |
|
146 <li>inside a Block Body, substitutions are denoted by the common |
|
147 syntax '<tt>${blah}</tt>', where <tt>blah</tt> refers to an item appearing |
|
148 earlier in the file, either the content |
|
149 of a previously defined Block, or the value of a constant defined in a |
|
150 <tt>constants</tt> Block |
|
151 <li>an item must be defined before it can be used in a substitution ; that is, |
|
152 it must appear earlier in the file |
|
153 <li>no substitutions are permitted in a <tt>constants</tt> Block |
|
154 <li>Block Names, and the names of constants, should be unique. |
|
155 </ul> |
|
156 */ |
|
157 TEXT_BLOCK, |
|
158 } |
|
159 |
|
160 /** |
|
161 Fetch a single, specific file located in the <tt>WEB-INF</tt> |
|
162 directory, and populate a corresponding <tt>Properties</tt> object. |
|
163 |
|
164 @param aConfigFileName has content and is the "simple" name (without path info) |
|
165 of a readable file in the <tt>WEB-INF</tt> directory (for example, <tt>"config.properties"</tt> or |
|
166 <tt>"statements.sql"</tt>). |
|
167 */ |
|
168 public static Properties fetch(String aConfigFileName, FileType aFileType){ |
|
169 return basicFetch(fWEBINF + aConfigFileName, ! FOR_TESTING, aFileType); |
|
170 } |
|
171 |
|
172 /** |
|
173 Fetch all config files located anywhere under the <tt>WEB-INF</tt> directory whose |
|
174 "simple" file name (without path info) matches <tt>aFileNamePattern</tt>, and |
|
175 translate their content into a single {@link Properties} object. |
|
176 |
|
177 <P>If the caller needs only one file of a specific name, and that file is |
|
178 located in the <tt>WEB-INF</tt> directory itself, then use {@link #fetch} instead. |
|
179 |
|
180 <P><span class="highlight">The keys in all of the matching files should be unique.</span> |
|
181 If a duplicate key exists, then the second instance will overwrite the first (as in |
|
182 {@link HashMap#put}), and a <tt>SEVERE</tt> warning is logged. |
|
183 |
|
184 <P><em>Design Note:</em><br> |
|
185 This method was initially created to reduce contention for the <tt>*.sql</tt> file, |
|
186 on projects having more than one developer. See |
|
187 {@link hirondelle.web4j.database.SqlStatement} and the example <tt>.sql</tt> files for |
|
188 more information. |
|
189 |
|
190 @param aFileNamePattern regular expression for the desired file name, without path |
|
191 information |
|
192 @param aConfigFileType identifies the layout style of the config file |
|
193 */ |
|
194 public static Properties fetchMany(Pattern aFileNamePattern, FileType aConfigFileType){ |
|
195 Properties result = new Properties(); |
|
196 Set<String> allFilePaths = getFilePathsBelow(fWEBINF); |
|
197 Set<String> matchingFilePaths = matchTargetPattern(allFilePaths, aFileNamePattern); |
|
198 fLogger.config( |
|
199 "Desired configuration files under /WEB-INF/: " + Util.logOnePerLine(matchingFilePaths) |
|
200 ); |
|
201 for (String matchingFilePath : matchingFilePaths){ |
|
202 Properties properties = basicFetch(matchingFilePath, ! FOR_TESTING, aConfigFileType); |
|
203 addProperties(properties, result); |
|
204 } |
|
205 fLogger.config( |
|
206 "Total number of distinct keys in configuration files : " + result.keySet().size() |
|
207 ); |
|
208 logKeysFromManyFiles(result); |
|
209 return result; |
|
210 } |
|
211 |
|
212 /** |
|
213 <em>Intended for testing only</em>, outside of a web container environment. |
|
214 |
|
215 <P>Fetches properties not from a file in the WEB-INF directory, |
|
216 but from a hard-coded absolute file name. |
|
217 |
|
218 @param aConfigFileName absolute file name, including path information |
|
219 */ |
|
220 public static Properties fetchForTesting(String aConfigFileName, FileType aFileType){ |
|
221 return basicFetch(aConfigFileName, FOR_TESTING, aFileType); |
|
222 } |
|
223 |
|
224 /** |
|
225 Return a possibly-empty {@link Set} of {@link Class} objects, for all concrete classes |
|
226 under <tt>/WEB-INF/classes/</tt> that implement <tt>aInterface</tt>. |
|
227 |
|
228 <P>More specifically, the classes returned here are |
|
229 not <tt>abstract</tt>, and not an <tt>interface</tt>. Only such classes |
|
230 should be of interest to the caller, since only such classes can be instantiated. |
|
231 It is expected that almost all classes in an application will be concrete. |
|
232 */ |
|
233 public static <T> Set<Class<T>> fetchConcreteClassesThatImplement(Class<T> aInterface){ |
|
234 if(aInterface != null){ |
|
235 fLogger.config("Fetching concrete classes that implement " + aInterface); |
|
236 } |
|
237 else { |
|
238 fLogger.config("Fetching all concrete classes."); |
|
239 } |
|
240 Set<Class<T>> result = new LinkedHashSet<Class<T>>(); |
|
241 Set<String> allFilePaths = getFilePathsBelow(fWEBINF_CLASSES); |
|
242 Set<String> classFilePaths = matchTargetPattern(allFilePaths, CLASS_FILES); |
|
243 for (String classFilePath: classFilePaths){ |
|
244 String className = classFilePath.replace('/','.'); |
|
245 if( className.endsWith("package-info.class")){ |
|
246 fLogger.finest("Ignoring package-info.class."); |
|
247 } |
|
248 else { |
|
249 className = className.substring(fWEBINF_CLASSES.length()); |
|
250 className = className.substring(0,className.lastIndexOf(fDOT_CLASS)); |
|
251 try { |
|
252 Class<T> thisClass = (Class<T>)Class.forName(className); //cast OK |
|
253 if ( isConcrete(thisClass) ){ |
|
254 if(aInterface == null){ |
|
255 result.add(thisClass); |
|
256 } |
|
257 else { |
|
258 if(aInterface.isAssignableFrom(thisClass)){ |
|
259 result.add(thisClass); |
|
260 } |
|
261 } |
|
262 } |
|
263 } |
|
264 catch(ClassNotFoundException ex){ |
|
265 fLogger.severe("Cannot load class using name : " + className); |
|
266 } |
|
267 } |
|
268 } |
|
269 return result; |
|
270 } |
|
271 |
|
272 /** |
|
273 As in {@link #fetchConcreteClassesThatImplement(Class)}, but with no test for implementing |
|
274 a specific interface. |
|
275 */ |
|
276 public static Set fetchConcreteClasses(){ |
|
277 return fetchConcreteClassesThatImplement(null); |
|
278 } |
|
279 |
|
280 /** |
|
281 Return all <tt>public static final</tt> fields declared in an application of type <tt>aFieldClass</tt>. |
|
282 |
|
283 <P>Restrict the search to concrete classes that implement an interface <tt>aContainingClassInterface</tt>, |
|
284 as in {@link #fetchConcreteClassesThatImplement(Class)}. If <tt>aContainingClassInterface</tt> is |
|
285 <tt>null</tt>, then scan all classes for such fields. |
|
286 |
|
287 <P>Return a possibly-empty {@link Map} having : |
|
288 <br>KEY - Class object of class from which the field is visible; see {@link Class#getFields()}. |
|
289 <br>VALUE - {@link Set} of objects of class aFieldClass |
|
290 */ |
|
291 public static /*T(Contain),V(field)*/ Map/*<Class<T>, Set<V>>*/ fetchPublicStaticFinalFields(Class aContainingClassInterface, Class aFieldClass){ |
|
292 //see AppFirewallImpl for example |
|
293 if(aContainingClassInterface == null){ |
|
294 fLogger.config("Fetching public static final fields of " + aFieldClass + ", from all concrete classes."); |
|
295 } |
|
296 else { |
|
297 fLogger.config("Fetching public static final fields of " + aFieldClass + ", from concrete classes that implement " + aContainingClassInterface); |
|
298 } |
|
299 Map result = new LinkedHashMap(); |
|
300 Set classesToScan = null; |
|
301 if(aContainingClassInterface == null){ |
|
302 classesToScan = fetchConcreteClasses(); |
|
303 } |
|
304 else { |
|
305 classesToScan = fetchConcreteClassesThatImplement(aContainingClassInterface); |
|
306 } |
|
307 Iterator iter = classesToScan.iterator(); |
|
308 while (iter.hasNext()){ |
|
309 Class thisClass = (Class)iter.next(); |
|
310 result.put(thisClass, getFields(thisClass, aFieldClass)); |
|
311 } |
|
312 return result; |
|
313 } |
|
314 |
|
315 /** |
|
316 As in {@link #fetchPublicStaticFinalFields(Class, Class)}, but with <tt>null</tt> value |
|
317 for <tt>aContainingClassInterface</tt>. |
|
318 */ |
|
319 public static Map fetchPublicStaticFinalFields(Class aFieldClass){ |
|
320 return fetchPublicStaticFinalFields(null, aFieldClass); |
|
321 } |
|
322 |
|
323 /** |
|
324 Process all the raw <tt>.sql</tt> text data into a consolidated Map of SqlId to SQL statements. |
|
325 @param aRawSql - the raw, unprocessed content of each <tt>.sql</tt> file (or data) |
|
326 */ |
|
327 public static Map<String, String> processRawSql(List<String> aRawSql){ |
|
328 Map<String, String> result = new LinkedHashMap<String, String>(); |
|
329 for(String rawSqlFile : aRawSql){ |
|
330 result.putAll(processedSql(rawSqlFile)); |
|
331 } |
|
332 return result; |
|
333 } |
|
334 |
|
335 // PRIVATE |
|
336 |
|
337 /** This field requires the servlet jar to be present. It will be init-ed when this class is loaded (?). */ |
|
338 private static ServletContext fContext; |
|
339 |
|
340 private static final boolean FOR_TESTING = true; |
|
341 |
|
342 private static final String fWEBINF = "/WEB-INF/"; |
|
343 private static final String fWEBINF_CLASSES = "/WEB-INF/classes/"; |
|
344 |
|
345 private static final Pattern CLASS_FILES = Pattern.compile("(?:.)*\\.class"); |
|
346 private static final String fSLASH = "/"; |
|
347 private static final String fDOT_CLASS = ".class"; |
|
348 private static final Logger fLogger = Util.getLogger(ConfigReader.class); |
|
349 |
|
350 private ConfigReader (){ |
|
351 //prevent construction |
|
352 } |
|
353 |
|
354 /** |
|
355 Return a <tt>Properties</tt> which reflects the contents of the |
|
356 given config file located under WEB-INF. |
|
357 */ |
|
358 private static Properties basicFetch(String aConfigFilePath, boolean aIsTesting, FileType aFileType){ |
|
359 Args.checkForContent(aConfigFilePath); |
|
360 if ( aIsTesting ) { |
|
361 checkIsAbsolute(aConfigFilePath); |
|
362 } |
|
363 Properties result = new Properties(); |
|
364 InputStream input = null; |
|
365 try { |
|
366 if ( ! aIsTesting ) { |
|
367 input = fContext.getResourceAsStream(aConfigFilePath); |
|
368 } |
|
369 else { |
|
370 input = new FileInputStream(aConfigFilePath); |
|
371 } |
|
372 if (FileType.PROPERTIES_FILE == aFileType) { |
|
373 result.load(input); |
|
374 } |
|
375 else { |
|
376 result = loadTextBlockFile(input, aConfigFilePath); |
|
377 } |
|
378 } |
|
379 catch (IOException ex){ |
|
380 vomit(aConfigFilePath, aIsTesting); |
|
381 } |
|
382 finally { |
|
383 shutdown(input); |
|
384 } |
|
385 fLogger.finest("Number of keys in properties object : " + result.keySet().size()); |
|
386 return result; |
|
387 } |
|
388 |
|
389 private static void checkIsAbsolute(String aConfigFileName){ |
|
390 if ( !isAbsolute(aConfigFileName) ) { |
|
391 throw new IllegalArgumentException( |
|
392 "Configuration file name is not absolute, "+ aConfigFileName |
|
393 ); |
|
394 } |
|
395 } |
|
396 |
|
397 private static boolean isAbsolute(String aFileName){ |
|
398 File file = new File(aFileName); |
|
399 return file.isAbsolute(); |
|
400 } |
|
401 |
|
402 private static void shutdown(InputStream aInput){ |
|
403 try { |
|
404 if (aInput != null) aInput.close(); |
|
405 } |
|
406 catch (IOException ex ){ |
|
407 throw new IllegalStateException("Cannot close config file in WEB-INF directory."); |
|
408 } |
|
409 } |
|
410 |
|
411 private static void vomit(String aConfigFileName, boolean aIsTesting){ |
|
412 String message = null; |
|
413 if ( ! aIsTesting ){ |
|
414 message = ( |
|
415 "Cannot open and load configuration file from WEB-INF directory: " + |
|
416 Util.quote(aConfigFileName) |
|
417 ); |
|
418 } |
|
419 else { |
|
420 message = ( |
|
421 "Cannot open and load configuration file named " + |
|
422 Util.quote(aConfigFileName) |
|
423 ); |
|
424 } |
|
425 throw new IllegalStateException(message); |
|
426 } |
|
427 |
|
428 /** |
|
429 Return a Set of Strings starting with <tt>aStartDirectory</tt> and containing the full |
|
430 file path for all files under <tt>aStartDirectory</tt>. No sorting is performed. |
|
431 |
|
432 @param aStartDirectory starts with <tt>/WEB-INF/</tt> |
|
433 */ |
|
434 private static Set<String> getFilePathsBelow(String aStartDirectory){ |
|
435 Set<String> result = new LinkedHashSet<String>(); |
|
436 Set<String> paths = fContext.getResourcePaths(aStartDirectory); |
|
437 for ( String path : paths) { |
|
438 if ( isDirectory(path) ) { |
|
439 //recursive call !!! |
|
440 result.addAll(getFilePathsBelow(path)); |
|
441 } |
|
442 else { |
|
443 result.add(path); |
|
444 } |
|
445 } |
|
446 return result; |
|
447 } |
|
448 |
|
449 /** |
|
450 Return a Set of paths (as Strings), starting with "/WEB-INF/", for files under |
|
451 <tt>WEB-INF</tt> whose simple file name (<em>without</em> the path info) |
|
452 matches <tt>aFileNamePattern</tt>. |
|
453 |
|
454 @param aFullFilePaths Set of Strings starting with "<tt>/WEB-INF/</tt>", which |
|
455 denote paths to <b>all</b> files under the <tt>WEB-INF</tt> directory (recursive) |
|
456 */ |
|
457 private static Set<String> matchTargetPattern(Set<String> aFullFilePaths, Pattern aFileNamePattern) { |
|
458 Set<String> result = new LinkedHashSet<String>(); |
|
459 for ( String fullFilePath : aFullFilePaths){ |
|
460 int lastSlash = fullFilePath.lastIndexOf(fSLASH); |
|
461 assert(lastSlash != -1); |
|
462 if ( ! isDirectory(fullFilePath) ){ |
|
463 String simpleFileName = fullFilePath.substring(lastSlash + 1); |
|
464 Matcher matcher = aFileNamePattern.matcher(simpleFileName); |
|
465 if ( matcher.matches() ) { |
|
466 result.add(fullFilePath); |
|
467 } |
|
468 } |
|
469 } |
|
470 return result; |
|
471 } |
|
472 |
|
473 private static boolean isDirectory(String aFullFilePath){ |
|
474 return aFullFilePath.endsWith(fSLASH); |
|
475 } |
|
476 |
|
477 /** |
|
478 Add all key-value pairs in <tt>aProperties</tt> to <tt>aResult</tt>. |
|
479 |
|
480 <P>If duplicate key is found, replaces old with new, and log the occurrence. |
|
481 */ |
|
482 private static void addProperties(Properties aProperties, Properties aResult){ |
|
483 Enumeration keys = aProperties.propertyNames(); |
|
484 while ( keys.hasMoreElements() ) { |
|
485 String key = (String)keys.nextElement(); |
|
486 if ( aResult.containsKey(key) ) { |
|
487 fLogger.severe( |
|
488 "WARNING. Same key found in more than one configuration file: " +Util.quote(key)+ |
|
489 "This condition almost always indicates an error. " + |
|
490 "Overwriting old key-value pair with new key-value pair." |
|
491 ); |
|
492 } |
|
493 String value = aProperties.getProperty(key); |
|
494 aResult.setProperty(key, value); |
|
495 } |
|
496 } |
|
497 |
|
498 /** |
|
499 Log all key names in <tt>aProperties</tt>, in alphabetical order. |
|
500 */ |
|
501 private static void logKeysFromManyFiles(Properties aProperties){ |
|
502 SortedSet sortedKeys = new TreeSet(aProperties.keySet()); |
|
503 fLogger.config(Util.logOnePerLine(sortedKeys)); |
|
504 } |
|
505 |
|
506 private static Properties loadTextBlockFile(InputStream aInput,String aSqlFileName) throws IOException { |
|
507 TextBlockReader sqlReader = new TextBlockReader(aInput, aSqlFileName); |
|
508 return sqlReader.read(); |
|
509 } |
|
510 |
|
511 private static boolean isConcrete(Class aClass){ |
|
512 int modifiers = aClass.getModifiers(); |
|
513 return |
|
514 ! Modifier.isInterface(modifiers) && |
|
515 ! Modifier.isAbstract(modifiers) |
|
516 ; |
|
517 } |
|
518 |
|
519 private static Set getFields(Class aContainingClass, Class aFieldClass){ |
|
520 Set result = new LinkedHashSet(); |
|
521 List fields = Arrays.asList(aContainingClass.getFields()); |
|
522 Iterator fieldsIter = fields.iterator(); |
|
523 while (fieldsIter.hasNext()){ |
|
524 Field field = (Field)fieldsIter.next(); |
|
525 if( field.getType() == aFieldClass ){ |
|
526 int modifiers = field.getModifiers(); |
|
527 if(Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)){ |
|
528 try { |
|
529 result.add(field.get(null)); |
|
530 } |
|
531 catch (IllegalAccessException ex){ |
|
532 fLogger.severe("Cannot get value of public static final field in " + aContainingClass); |
|
533 } |
|
534 } |
|
535 } |
|
536 } |
|
537 return result; |
|
538 } |
|
539 |
|
540 private static Map<String, String> processedSql(String aRawSql){ |
|
541 TextBlockReader textBlockReader = new TextBlockReader(aRawSql); |
|
542 Properties props = null; |
|
543 try { |
|
544 props = textBlockReader.read(); |
|
545 } |
|
546 catch (IOException ex) { |
|
547 ex.printStackTrace(); |
|
548 } |
|
549 return new LinkedHashMap(props); |
|
550 } |
|
551 } |