classes/hirondelle/web4j/readconfig/ConfigReader.java
changeset 0 3060119b1292
equal deleted inserted replaced
-1:000000000000 0:3060119b1292
       
     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>&lt;name&gt; {</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 }