Main Page | Packages | Class Hierarchy | Alphabetical List | Class List | File List | Class Members | Related Pages

MacrosHandler.java

00001 /**
00002  * C-JDBC: Clustered JDBC.
00003  * Copyright (C) 2002-2004 French National Institute For Research In Computer
00004  * Science And Control (INRIA).
00005  * Contact: c-jdbc@objectweb.org
00006  * 
00007  * This library is free software; you can redistribute it and/or modify it
00008  * under the terms of the GNU Lesser General Public License as published by the
00009  * Free Software Foundation; either version 2.1 of the License, or any later
00010  * version.
00011  * 
00012  * This library is distributed in the hope that it will be useful, but WITHOUT
00013  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
00014  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
00015  * for more details.
00016  * 
00017  * You should have received a copy of the GNU Lesser General Public License
00018  * along with this library; if not, write to the Free Software Foundation,
00019  * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
00020  *
00021  * Initial developer(s): Marc Wick.
00022  * Contributor(s): ______________________.
00023  */
00024 
00025 package org.objectweb.cjdbc.common.sql.filters;
00026 
00027 import java.sql.Date;
00028 import java.sql.Time;
00029 import java.sql.Timestamp;
00030 import java.util.ArrayList;
00031 import java.util.Random;
00032 
00033 import org.objectweb.cjdbc.common.util.Strings;
00034 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
00035 import org.objectweb.cjdbc.common.xml.XmlComponent;
00036 
00037 /**
00038  * This class defines a MacrosHandler
00039  * 
00040  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00041  * @version 1.0
00042  */
00043 public class MacrosHandler implements XmlComponent
00044 {
00045   /** Used when level is unknown */
00046   public static final int     UNKNOWN_INT_VALUE       = -1;
00047   /** Used when level is unknown */
00048   public static final String  UNKNOWN_STRING_VALUE    = "unknown";
00049 
00050   /** String for rand() macro */
00051   private static final String MACRO_RAND              = "rand()";
00052 
00053   /** Value if rand() macro should not be replaced */
00054   public static final int     RAND_OFF                = 0;
00055   /** Value if rand() macro should be replaced by an integer value */
00056   public static final int     RAND_INT                = 1;
00057   /** Value if rand() macro should be replaced by an long value */
00058   public static final int     RAND_LONG               = 2;
00059   /** Value if rand() macro should be replaced by an float value (default) */
00060   public static final int     RAND_FLOAT              = 3;
00061   /** Value if rand() macro should be replaced by an double value */
00062   public static final int     RAND_DOUBLE             = 4;
00063 
00064   private final Random        randGenerator           = new Random();
00065 
00066   private int                 replaceRand             = RAND_FLOAT;
00067 
00068   /** String for now() macro */
00069   private static final String MACRO_NOW               = "now()";
00070   /** String for current_date macro */
00071   private static final String MACRO_CURRENT_DATE      = "current_date";
00072   /** String for current_times macro */
00073   private static final String MACRO_CURRENT_TIME      = "current_time";
00074   /** String for timeofday() macro */
00075   private static final String MACRO_TIMEODFAY         = "timeofday()";
00076   /** String for current_timestamp macro */
00077   private static final String MACRO_CURRENT_TIMESTAMP = "current_timestamp";
00078 
00079   /** Value if a date macro should not be replaced */
00080   public static final int     DATE_OFF                = 0;
00081   /** Value if date macro should be replaced by an java.sql.Date value */
00082   public static final int     DATE_DATE               = 1;
00083   /** Value if date macro should be replaced by an java.sql.Time value */
00084   public static final int     DATE_TIME               = 2;
00085   /** Value if date macro should be replaced by an java.sql.Timestamp value */
00086   public static final int     DATE_TIMESTAMP          = 3;
00087 
00088   private long                clockResolution         = 0;
00089   private int                 now                     = DATE_TIMESTAMP;
00090   private int                 currentDate             = DATE_DATE;
00091   private int                 currentTime             = DATE_TIME;
00092   private int                 timeOfDay               = DATE_TIMESTAMP;
00093   private int                 currentTimestamp        = DATE_TIMESTAMP;
00094 
00095   private boolean             needsProcessing;
00096   private boolean             needsDateProcessing;
00097 
00098   /**
00099    * Creates a new <code>MacrosHandler</code> object
00100    * 
00101    * @param replaceRand replacement of rand() macro
00102    * @param clockResolution clock resolution for date macros
00103    * @param now replacement of now()
00104    * @param currentDate replacement of current_date
00105    * @param currentTime replacement of current_time
00106    * @param timeOfDay replacement of timeofday()
00107    * @param currentTimestamp replacement of current_timestamp
00108    */
00109   public MacrosHandler(int replaceRand, long clockResolution, int now,
00110       int currentDate, int currentTime, int timeOfDay, int currentTimestamp)
00111   {
00112     if ((replaceRand < RAND_OFF) || (replaceRand > RAND_DOUBLE))
00113       throw new RuntimeException("Invalid value for " + MACRO_RAND
00114           + " macro replacement (" + replaceRand + ")");
00115     this.replaceRand = replaceRand;
00116     if (clockResolution < 0)
00117       throw new RuntimeException(
00118           "Invalid negative value for clock resolution in date macros");
00119     this.clockResolution = clockResolution;
00120     if ((now < DATE_OFF) || (now > DATE_TIMESTAMP))
00121       throw new RuntimeException("Invalid value for " + MACRO_NOW
00122           + " macro replacement (" + now + ")");
00123     this.now = now;
00124     if ((currentDate < DATE_OFF) || (currentDate > DATE_DATE))
00125       throw new RuntimeException("Invalid value for " + MACRO_CURRENT_DATE
00126           + " macro replacement (" + currentDate + ")");
00127     this.currentDate = currentDate;
00128     if ((currentTime < DATE_OFF) || (currentTime > DATE_TIMESTAMP))
00129       throw new RuntimeException("Invalid value for " + MACRO_CURRENT_TIME
00130           + " macro replacement (" + currentTime + ")");
00131     this.currentTime = currentTime;
00132     if ((timeOfDay < DATE_OFF) || (timeOfDay > DATE_TIMESTAMP))
00133       throw new RuntimeException("Invalid value for " + MACRO_TIMEODFAY
00134           + " macro replacement (" + timeOfDay + ")");
00135     this.timeOfDay = timeOfDay;
00136     if ((currentTimestamp < DATE_OFF) || (currentTimestamp > DATE_TIMESTAMP))
00137       throw new RuntimeException("Invalid value for " + MACRO_CURRENT_TIMESTAMP
00138           + " macro replacement (" + currentTimestamp + ")");
00139     this.currentTimestamp = currentTimestamp;
00140     needsDateProcessing = (now + currentDate + timeOfDay + currentTimestamp) > 0;
00141     needsProcessing = needsDateProcessing || (replaceRand > 0);
00142   }
00143 
00144   /**
00145    * Convert the rand level from string (xml value) to integer
00146    * 
00147    * @param randLevel the rand level
00148    * @return an int corresponding to the string description
00149    */
00150   public static final int getIntRandLevel(String randLevel)
00151   {
00152     if (randLevel.equalsIgnoreCase(DatabasesXmlTags.VAL_off))
00153       return RAND_OFF;
00154     else if (randLevel.equalsIgnoreCase(DatabasesXmlTags.VAL_double))
00155       return RAND_DOUBLE;
00156     else if (randLevel.equalsIgnoreCase(DatabasesXmlTags.VAL_float))
00157       return RAND_FLOAT;
00158     else if (randLevel.equalsIgnoreCase(DatabasesXmlTags.VAL_int))
00159       return RAND_INT;
00160     else if (randLevel.equalsIgnoreCase(DatabasesXmlTags.VAL_long))
00161       return RAND_LONG;
00162     else
00163       return UNKNOWN_INT_VALUE;
00164   }
00165 
00166   /**
00167    * Return this <code>MacrosHandler</code> to the corresponding xml form
00168    * 
00169    * @return the XML representation of this element
00170    */
00171   public String getXml()
00172   {
00173     StringBuffer sb = new StringBuffer();
00174     sb.append("<" + DatabasesXmlTags.ELT_MacroHandling + " "
00175         + DatabasesXmlTags.ATT_rand + "=\"" + getStringRandLevel(replaceRand)
00176         + "\" " + DatabasesXmlTags.ATT_now + "=\"" + getStringDateLevel(now)
00177         + "\" " + DatabasesXmlTags.ATT_currentDate + "=\""
00178         + getStringDateLevel(currentDate) + "\" "
00179         + DatabasesXmlTags.ATT_currentTime + "=\""
00180         + getStringDateLevel(currentTime) + "\" "
00181         + DatabasesXmlTags.ATT_currentTimestamp + "=\""
00182         + getStringDateLevel(currentTimestamp) + "\" "
00183         + DatabasesXmlTags.ATT_timeOfDay + "=\""
00184         + getStringDateLevel(timeOfDay) + "\" "
00185         + DatabasesXmlTags.ATT_timeResolution + "=\"" + clockResolution + "\" "
00186         + "/>");
00187     return sb.toString();
00188   }
00189 
00190   /**
00191    * Convert the rand level from int (java code) to string (xml value)
00192    * 
00193    * @param randLevel the rand level
00194    * @return a string description corresponding to that level
00195    */
00196   public static final String getStringRandLevel(int randLevel)
00197   {
00198     switch (randLevel)
00199     {
00200       case RAND_OFF :
00201         return DatabasesXmlTags.VAL_off;
00202       case RAND_DOUBLE :
00203         return DatabasesXmlTags.VAL_double;
00204       case RAND_FLOAT :
00205         return DatabasesXmlTags.VAL_float;
00206       case RAND_INT :
00207         return DatabasesXmlTags.VAL_int;
00208       case RAND_LONG :
00209         return DatabasesXmlTags.VAL_long;
00210       default :
00211         return UNKNOWN_STRING_VALUE;
00212     }
00213   }
00214 
00215   /**
00216    * Convert the date level from string (xml value) to integer
00217    * 
00218    * @param dateLevel the date level
00219    * @return an int corresponding to the string description
00220    */
00221   public static final int getIntDateLevel(String dateLevel)
00222   {
00223     if (dateLevel.equals(DatabasesXmlTags.VAL_off))
00224       return DATE_OFF;
00225     else if (dateLevel.equals(DatabasesXmlTags.VAL_date))
00226       return DATE_DATE;
00227     else if (dateLevel.equals(DatabasesXmlTags.VAL_time))
00228       return DATE_TIME;
00229     else if (dateLevel.equals(DatabasesXmlTags.VAL_timestamp))
00230       return DATE_TIMESTAMP;
00231     else
00232       return UNKNOWN_INT_VALUE;
00233   }
00234 
00235   /**
00236    * Convert the date level from int (java code) to string (xml value)
00237    * 
00238    * @param dateLevel the date level
00239    * @return a string description corresponding to that level
00240    */
00241   public static final String getStringDateLevel(int dateLevel)
00242   {
00243     switch (dateLevel)
00244     {
00245       case DATE_OFF :
00246         return DatabasesXmlTags.VAL_off;
00247       case DATE_DATE :
00248         return DatabasesXmlTags.VAL_date;
00249       case DATE_TIME :
00250         return DatabasesXmlTags.VAL_time;
00251       case DATE_TIMESTAMP :
00252         return DatabasesXmlTags.VAL_timestamp;
00253       default :
00254         return UNKNOWN_STRING_VALUE;
00255     }
00256   }
00257 
00258   /**
00259    * Processes a date related macro using the given timestamp.
00260    * 
00261    * @param originalSql original SQL request
00262    * @param macroPattern macro text to look for
00263    * @param replacementPolicy DATE_DATE, DATE_TIME or DATE_TIMESTAMP
00264    * @param currentClock current time in ms
00265    * @param idxs quote indexes
00266    * @return new SQL statement
00267    */
00268   public String macroDate(String originalSql, String macroPattern,
00269       int replacementPolicy, long currentClock, Integer[] idxs)
00270   {
00271     if (idxs == null)
00272       idxs = getQuoteIndexes(originalSql);
00273     String lower = originalSql.toLowerCase();
00274     int idx = lower.indexOf(macroPattern.toLowerCase());
00275     if (idx == -1 || !shouldReplaceMacro(idx, idxs))
00276       return originalSql;
00277 
00278     String date;
00279     switch (replacementPolicy)
00280     {
00281       case DATE_DATE :
00282         date = "{d '" + new Date(currentClock).toString() + "'}";
00283         break;
00284       case DATE_TIME :
00285         date = "{t '" + new Time(currentClock).toString() + "'}";
00286         break;
00287       case DATE_TIMESTAMP :
00288         date = "{ts '" + new Timestamp(currentClock).toString() + "'}";
00289         break;
00290       default :
00291         throw new RuntimeException(
00292             "Unexpected replacement strategy for date macro ("
00293                 + replacementPolicy + ")");
00294     }
00295     return Strings.replaceCasePreserving(originalSql, macroPattern, date);
00296   }
00297 
00298   /**
00299    * Replaces rand() with a randomized value.
00300    * 
00301    * @param originalSql original SQL request
00302    * @param idxs quote indexes
00303    * @return new SQL statement
00304    */
00305   public String macroRand(String originalSql, Integer[] idxs)
00306   {
00307     if (idxs == null)
00308       idxs = getQuoteIndexes(originalSql);
00309     String lower = originalSql.toLowerCase();
00310     int idx = lower.indexOf(MACRO_RAND);
00311     if (idx > 0)
00312     {
00313       String rand;
00314       StringBuffer sql = new StringBuffer(originalSql);
00315       int shift = 0;
00316       do
00317       {
00318         if (shouldReplaceMacro(idx, idxs))
00319         {
00320           switch (replaceRand)
00321           {
00322             case RAND_INT :
00323               rand = Integer.toString(randGenerator.nextInt());
00324               break;
00325             case RAND_LONG :
00326               rand = Long.toString(randGenerator.nextLong());
00327               break;
00328             case RAND_FLOAT :
00329               rand = Float.toString(randGenerator.nextFloat());
00330               break;
00331             case RAND_DOUBLE :
00332               rand = Double.toString(randGenerator.nextDouble());
00333               break;
00334             default :
00335               throw new RuntimeException(
00336                   "Unexpected replacement strategy for rand() macro ("
00337                       + replaceRand + ")");
00338           }
00339           sql = sql.replace(idx + shift, idx + shift + MACRO_RAND.length(),
00340               rand);
00341           shift += rand.length() - MACRO_RAND.length();
00342         }
00343         idx = lower.indexOf(MACRO_RAND, idx + MACRO_RAND.length());
00344       }
00345       while (idx > 0);
00346       return sql.toString();
00347     }
00348     else
00349     {
00350       return originalSql;
00351     }
00352   }
00353 
00354   /**
00355    * Processes all macros in the given request and returns a new String with the
00356    * processed macros. If no macro has to be processed, the original String is
00357    * returned.
00358    * 
00359    * @param sql SQL statement to process
00360    * @return processed statement
00361    */
00362   public final String processMacros(String sql)
00363   {
00364     if (!needsProcessing)
00365       return sql;
00366     if(sql==null)
00367       return null;
00368     Integer[] idxs = this.getQuoteIndexes(sql);
00369     if (replaceRand > RAND_OFF)
00370       sql = macroRand(sql, idxs);
00371     if (!needsDateProcessing)
00372       return sql;
00373     long currentClock = System.currentTimeMillis();
00374     if (clockResolution > 0)
00375       currentClock = currentClock - (currentClock % clockResolution);
00376     if (now > DATE_OFF)
00377       sql = macroDate(sql, MACRO_NOW, now, currentClock, idxs);
00378     if (currentDate > DATE_OFF)
00379       sql = macroDate(sql, MACRO_CURRENT_DATE, currentDate, currentClock, idxs);
00380     if (currentTimestamp > DATE_OFF)
00381       sql = macroDate(sql, MACRO_CURRENT_TIMESTAMP, currentTimestamp,
00382           currentClock, idxs);
00383     if (currentTime > DATE_OFF)
00384       sql = macroDate(sql, MACRO_CURRENT_TIME, currentTime, currentClock, idxs);
00385     if (timeOfDay > DATE_OFF)
00386       sql = macroDate(sql, MACRO_TIMEODFAY, timeOfDay, currentClock, idxs);
00387 
00388     return sql;
00389   }
00390 
00391   /**
00392    * Retrieve all the indexes of quotes in the string
00393    * 
00394    * @param sql the original query
00395    * @return an array of integer corresponding to the quote indexes
00396    */
00397   private Integer[] getQuoteIndexes(String sql)
00398   {
00399     ArrayList list = new ArrayList();
00400     for (int i = 0; i < sql.length(); i++)
00401     {
00402       char c = sql.charAt(i);
00403       if (c == '\'')
00404         list.add(new Integer(i));
00405     }
00406     Integer[] intlist = new Integer[list.size()];
00407     return (Integer[]) list.toArray(intlist);
00408   }
00409 
00410   /**
00411    * Should we replace a macro situated at index idx, knowing that the quotes
00412    * are at indexes list
00413    * 
00414    * @param idx the index of the macro
00415    * @param list the indexes of quotes
00416    * @return <code>true</code> if we should change the macro,
00417    *         <code>false</code> if the macro is within a string
00418    */
00419   private boolean shouldReplaceMacro(int idx, Integer[] list)
00420   {
00421     int count = 0;
00422     while (count < list.length && list[count].intValue() < idx)
00423     {
00424       count++;
00425     }
00426     return count % 2 == 0;
00427   }
00428 
00429 }

Generated on Mon Apr 11 22:01:32 2005 for C-JDBC by  doxygen 1.3.9.1