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

ParsingCache.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): Emmanuel Cecchet.
00022  * Contributor(s): ______________________________________.
00023  */
00024 
00025 package org.objectweb.cjdbc.controller.cache.parsing;
00026 
00027 import java.sql.SQLException;
00028 import java.util.Hashtable;
00029 
00030 import org.objectweb.cjdbc.common.i18n.Translate;
00031 import org.objectweb.cjdbc.common.log.Trace;
00032 import org.objectweb.cjdbc.common.sql.AbstractRequest;
00033 import org.objectweb.cjdbc.common.sql.ParsingGranularities;
00034 import org.objectweb.cjdbc.common.sql.RequestType;
00035 import org.objectweb.cjdbc.common.xml.DatabasesXmlTags;
00036 import org.objectweb.cjdbc.controller.requestmanager.ParserThread;
00037 import org.objectweb.cjdbc.controller.requestmanager.RequestManager;
00038 
00039 /**
00040  * This class implements a request parsing cache.
00041  * 
00042  * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00043  * @version 1.0
00044  */
00045 public class ParsingCache
00046 {
00047   private static Trace logger = Trace.getLogger(ParsingCache.class.getName());
00048   private Hashtable      cache;               // SQL -> parsed request
00049   private Hashtable      currentlyParsing;    // SQL -> CurrentlyParsingEntry
00050   private RequestManager requestManager;
00051   private int            granularity;
00052   private int            maxNbOfEntries;
00053   private boolean        backgroundParsing;   // Default is parse when needed
00054   private boolean        caseSensitiveParsing; // Default is case insensitive
00055 
00056   /**
00057    * CurrentlyParsingEntry contains a (Request,ParserThread) which is an element
00058    * of the currentlyParsing Hashtable.
00059    * 
00060    * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
00061    * @version 1.0
00062    */
00063   private class CurrentlyParsingEntry
00064   {
00065     private ParserThread    parserThread;
00066     private AbstractRequest request;
00067 
00068     /**
00069      * Constructor for CurrentlyParsingEntry.
00070      * 
00071      * @param parserThread creating parser thread
00072      * @param request request to parse
00073      */
00074     public CurrentlyParsingEntry(ParserThread parserThread,
00075         AbstractRequest request)
00076     {
00077       this.parserThread = parserThread;
00078       this.request = request;
00079     }
00080 
00081     /**
00082      * Returns the parserThread.
00083      * 
00084      * @return ParserThread
00085      */
00086     public ParserThread getParserThread()
00087     {
00088       return parserThread;
00089     }
00090 
00091     /**
00092      * Returns the request.
00093      * 
00094      * @return AbstractRequest
00095      */
00096     public AbstractRequest getRequest()
00097     {
00098       return request;
00099     }
00100 
00101   }
00102 
00103   /**
00104    * Constructor for ParsingCache.
00105    * 
00106    * @param size maximum cache size in nb of entries
00107    * @param backgroundParsing true if the parsing should be done in background
00108    *          by a ParserThread
00109    */
00110   public ParsingCache(int size, boolean backgroundParsing)
00111   {
00112     cache = new Hashtable(size == 0 ? 10000 : size);
00113     currentlyParsing = new Hashtable();
00114     if (size < 0)
00115       throw new RuntimeException(Translate.get("cache.parsing.invalid.size",
00116           size));
00117     if (size == 0)
00118       this.maxNbOfEntries = Integer.MAX_VALUE;
00119     else
00120       this.maxNbOfEntries = size;
00121     this.backgroundParsing = backgroundParsing;
00122     caseSensitiveParsing = false;
00123   }
00124 
00125   /**
00126    * Returns the granularity value.
00127    * 
00128    * @return Returns the granularity.
00129    */
00130   public int getGranularity()
00131   {
00132     return granularity;
00133   }
00134 
00135   /**
00136    * Sets the granularity value.
00137    * 
00138    * @param granularity The granularity to set.
00139    */
00140   public void setGranularity(int granularity)
00141   {
00142     this.granularity = granularity;
00143   }
00144 
00145   /**
00146    * Returns the requestManager value.
00147    * 
00148    * @return Returns the requestManager.
00149    */
00150   public RequestManager getRequestManager()
00151   {
00152     return requestManager;
00153   }
00154 
00155   /**
00156    * Sets the requestManager value.
00157    * 
00158    * @param requestManager The requestManager to set.
00159    */
00160   public void setRequestManager(RequestManager requestManager)
00161   {
00162     this.requestManager = requestManager;
00163   }
00164 
00165   /**
00166    * If the same SQL query is found in the cache, the parsing is cloned into the
00167    * given request. If backgroundParsing is set to true, then a ParserThread
00168    * starts parsing the request in background else nothing is done on a cache
00169    * miss.
00170    * 
00171    * @param request the request you look for
00172    */
00173   public void getParsingFromCache(AbstractRequest request)
00174   {
00175     if (request.isParsed())
00176       return;
00177 
00178     String sql = request.getSqlSkeleton();
00179     if (sql == null)
00180       sql = request.getSQL();
00181     AbstractRequest parsedRequest = (AbstractRequest) cache.get(sql);
00182 
00183     if (parsedRequest != null)
00184     { // Cache hit, clone the parsing
00185       request.cloneParsing(parsedRequest);
00186       return;
00187     }
00188     else if (backgroundParsing)
00189     { // Cache miss, start parsing the request in background
00190       synchronized (currentlyParsing)
00191       {
00192         if (!currentlyParsing.contains(sql))
00193         { // Nobody else is trying to parse the same SQL query
00194           ParserThread pt = new ParserThread(request, requestManager
00195               .getDatabaseSchema(), granularity, caseSensitiveParsing);
00196           currentlyParsing.put(sql, new CurrentlyParsingEntry(pt, request));
00197         }
00198       }
00199     }
00200   }
00201 
00202   /**
00203    * Method getParsingFromCacheAndParseIfMissing.
00204    * 
00205    * @param request the request we look for
00206    * @exception SQLException if an error occurs
00207    */
00208   public void getParsingFromCacheAndParseIfMissing(AbstractRequest request)
00209       throws SQLException
00210   {
00211     if (request.isParsed())
00212       return;
00213 
00214     // Check cache
00215     String instanciatedSQL = request.getSQL();
00216     AbstractRequest parsedRequest = (AbstractRequest) cache
00217         .get(instanciatedSQL);
00218 
00219 
00220     try
00221     {
00222     
00223     if (parsedRequest == null)
00224     { // Cache miss
00225       String sqlSkeleton = request.getSqlSkeleton();
00226       String sql;
00227       if (sqlSkeleton != null)
00228       { // Missed with instanciated query, try with skeleton
00229         sql = sqlSkeleton;
00230         parsedRequest = (AbstractRequest) cache.get(sql);
00231         if (parsedRequest != null)
00232         { // Cache hit with skeleton
00233           request.cloneParsing(parsedRequest);
00234           return;
00235         }
00236       }
00237       else
00238         sql = instanciatedSQL;
00239 
00240       // Full cache miss. Note that the underlying cache Hashtable is
00241       // synchronized and we usually do not need to synchronize on it.
00242       // As we will have to add a cache entry, check if the cache size is ok
00243       // else remove the first entry of the hashtable.
00244       while (cache.size() > maxNbOfEntries)
00245       { // Remove first entry from Hashtable. We need to synchronize here to be
00246         // sure that we are not trying to concurrently remove the first cache
00247         // entry.
00248         synchronized (cache)
00249         {
00250           try
00251           {
00252             cache.remove(cache.keys().nextElement());
00253           }
00254           catch (Exception ignore)
00255           {
00256             break;
00257           }
00258         }
00259       }
00260 
00261       
00262       // Both skeleton and instanciated missed
00263       if (backgroundParsing)
00264       {
00265         // Find the parsing thread and request (note that Hasthtable is
00266         // synchronized)
00267         CurrentlyParsingEntry cpe = (CurrentlyParsingEntry) currentlyParsing
00268             .get(sql);
00269         if (cpe != null)
00270         {
00271           ParserThread pt = cpe.getParserThread();
00272           try
00273           {
00274             if (pt != null)
00275             {
00276               // Wait for completion
00277               pt.join();
00278               synchronized (currentlyParsing)
00279               {
00280                 currentlyParsing.remove(sql);
00281               }
00282 
00283               // Update cache
00284               if ((granularity != ParsingGranularities.COLUMN_UNIQUE)
00285                   || (sqlSkeleton == null))
00286                 // No skeleton or no uniqueness criteria, add the query
00287                 cache.put(instanciatedSQL, cpe.getRequest());
00288               else
00289               { // We have a skeleton and COLUMN_UNIQUE parsing
00290                 if (request.getCacheAbility() != RequestType.UNIQUE_CACHEABLE)
00291                   // It is NOT UNIQUE, add the skeleton
00292                   cache.put(sqlSkeleton, cpe.getRequest());
00293                 else
00294                   // It is UNIQUE, add the instanciated query
00295                   cache.put(instanciatedSQL, cpe.getRequest());
00296               }
00297             }
00298           }
00299           catch (InterruptedException failed)
00300           {
00301             throw new SQLException(Translate.get(
00302                 "cache.parsing.failed.join.parser.thread", new String[]{
00303                     "" + request.getId(), failed.getMessage()}));
00304           }
00305         }
00306       }
00307       // Parse it now because we didn't parse in background or
00308       // backgroundParsing has failed for any obscure reason.
00309       request.parse(requestManager.getDatabaseSchema(), granularity,
00310           caseSensitiveParsing);
00311 
00312       // Update cache
00313       if ((sqlSkeleton != null)
00314           && (granularity == ParsingGranularities.COLUMN_UNIQUE)
00315           && (request.getCacheAbility() == RequestType.UNIQUE_CACHEABLE))
00316         // If this is a unique request, we must put the instanciated query in
00317         // the cache to retrieve the exact pk value.
00318         cache.put(instanciatedSQL, request);
00319       else
00320         cache.put(sql, request);
00321     }
00322     else
00323       // Cache hit
00324       request.cloneParsing(parsedRequest);
00325     
00326     }
00327     catch (OutOfMemoryError oome)
00328     {
00329       synchronized (cache)
00330       {
00331         cache.clear();
00332       }
00333       System.gc();
00334       logger.warn(Translate.get("cache.memory.error.cache.flushed", this
00335           .getClass()));
00336     }
00337   }
00338 
00339   /**
00340    * Returns the backgroundParsing.
00341    * 
00342    * @return boolean
00343    */
00344   public boolean isBackgroundParsing()
00345   {
00346     return backgroundParsing;
00347   }
00348 
00349   /**
00350    * Sets the background parsing. If true the request are parsed in background
00351    * by a separate thread that is created for this purpose.
00352    * 
00353    * @param backgroundParsing The backgroundParsing to set
00354    */
00355   public void setBackgroundParsing(boolean backgroundParsing)
00356   {
00357     this.backgroundParsing = backgroundParsing;
00358   }
00359 
00360   /**
00361    * Sets the parsing case sensitivity
00362    * 
00363    * @param isCaseSensitiveParsing true if parsing is case sensitive
00364    */
00365   public void setCaseSensitiveParsing(boolean isCaseSensitiveParsing)
00366   {
00367     this.caseSensitiveParsing = isCaseSensitiveParsing;
00368   }
00369 
00370   /**
00371    * Returns the caseSensitiveParsin.
00372    * 
00373    * @return boolean
00374    */
00375   public boolean isCaseSensitiveParsing()
00376   {
00377     return caseSensitiveParsing;
00378   }
00379 
00380   /**
00381    * Get xml information about this ParsingCache
00382    * 
00383    * @return <code>String</code> in xml formatted text
00384    */
00385   public String getXml()
00386   {
00387     return "<" + DatabasesXmlTags.ELT_ParsingCache + " "
00388         + DatabasesXmlTags.ATT_backgroundParsing + "=\"" + backgroundParsing
00389         + "\" " + DatabasesXmlTags.ATT_maxNbOfEntries + "=\"" + maxNbOfEntries
00390         + "\"/>";
00391   }
00392 
00393 }

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