All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sourceforge.squirrel_sql.client.session.schemainfo.SchemaInfoCache Maven / Gradle / Ivy

package net.sourceforge.squirrel_sql.client.session.schemainfo;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;

import net.sourceforge.squirrel_sql.client.gui.db.SQLAliasSchemaProperties;
import net.sourceforge.squirrel_sql.client.gui.db.SchemaLoadInfo;
import net.sourceforge.squirrel_sql.client.gui.db.SchemaNameLoadInfo;
import net.sourceforge.squirrel_sql.client.gui.db.SchemaTableTypeCombination;
import net.sourceforge.squirrel_sql.client.session.ExtendedColumnInfo;
import net.sourceforge.squirrel_sql.client.session.ISession;
import net.sourceforge.squirrel_sql.client.session.SessionManager;
import net.sourceforge.squirrel_sql.fw.sql.*;
import net.sourceforge.squirrel_sql.fw.util.Utilities;
import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;

/**
 * This class is Serializable and yet doesn't declare serialVersionUID.  This is done intentionally so that 
 * the SchemaInfoCacheSerializer can detect incompatible class changes (by catching Exception when attempting
 * to read the serialized file).  This was deemed to be a less error-prone way of handling changes to this
 * class file's definition, then having to remember to consider whether or not serialVersionUID should be 
 * incremented for any given change.  Therefore, it is very important to not introduce a serialVersionUID
 * class member, as forgetting to update it might lead to undetected incompatible class changes that don't 
 * manifest themselves during de-serialization, but occur later in the application when missing members are
 * invoked (for example).  This more conservative approach can lead to the serialized file being removed upon
 * installing a newer version of SQuirreL more often than necessary, but it seemed to us to be better than the 
 * alternative.
 */
@SuppressWarnings("serial")
public class SchemaInfoCache implements Serializable
{
   private static final ILogger s_log =
       LoggerController.createLogger(SchemaInfoCache.class);

   private List _catalogs = new ArrayList();
   private List _schemas = new ArrayList();

   private TreeMap _keywords = 
       new TreeMap();
   private TreeMap _dataTypes = 
       new TreeMap();
   private Map _functions = 
       Collections.synchronizedMap(new TreeMap());

   /////////////////////////////////////////////////////////////////////////////
   // Schema dependent data.
   // Are changed only in this class
   //
   private TreeMap _internalTableNameTreeMap =
       new TreeMap();
   
   private Map _tableNames = 
       Collections.synchronizedMap(_internalTableNameTreeMap);
   

   /** 
    * This data structure can be accessed by multiple concurrent threads.  
    * Traversal via iterators is fast and cannot encounter interference from 
    * other threads otherwise ConcurrentModificationExceptions may 
    * result (Bug #1752089)
    * 
    * One other thing: it must maintain the order in which items were inserted
    * so that traversal yeilds insertion order (Bug 1805954).
    */
   private CopyOnWriteArrayList _iTableInfos =
       new CopyOnWriteArrayList();
   
   private Hashtable> _tableInfosBySimpleName = 
       new Hashtable>();

   private SchemaInfoColumnCache _schemaInfoColumnCache = new SchemaInfoColumnCache();


   private Map _procedureNames = 
       Collections.synchronizedMap(new TreeMap());
   
   private Map _iProcedureInfos = 
       Collections.synchronizedMap(new TreeMap());
   
   private Hashtable> _procedureInfosBySimpleName = 
       new Hashtable>();
   //
   ///////////////////////////////////////////////////////////////////////////

   private SQLAliasSchemaProperties _schemaPropsCacheIsBasedOn;

   private transient String[] _viewTableTypesCacheable;
   private transient String[] _tabelTableTypesCacheable;
   //private transient String[] availableTypesInDataBase;

   private transient ISession _session = null;


   void setSession(ISession session)
   {
      _session = session;
      initTypes();
   }


   boolean loadSchemaIndependentMetaData()
   {
      return _session.getAlias().getSchemaProperties().loadSchemaIndependentMetaData(_schemaPropsCacheIsBasedOn);
   }

   private SchemaLoadInfo[] getAllSchemaLoadInfos()
   {
      SQLAliasSchemaProperties schemaProps =  
          _session.getAlias().getSchemaProperties();
      SchemaLoadInfo[] schemaLoadInfos = 
          schemaProps.getSchemaLoadInfos(_schemaPropsCacheIsBasedOn, 
                                         _tabelTableTypesCacheable, 
                                         _viewTableTypesCacheable);
      SessionManager sessionMgr = _session.getApplication().getSessionManager();
      boolean allSchemasAllowed = sessionMgr.areAllSchemasAllowed(_session);
      
      if(   1 == schemaLoadInfos.length
         && null == schemaLoadInfos[0].schemaName
         && false == allSchemasAllowed)
      {
         if(false == allSchemasAllowed)
         {
            String[] allowedSchemas = sessionMgr.getAllowedSchemas(_session);

            ArrayList ret = new ArrayList();

            for (int i = 0; i < allowedSchemas.length; i++)
            {
               SchemaLoadInfo buf = (SchemaLoadInfo) Utilities.cloneObject(
                     schemaLoadInfos[0], getClass().getClassLoader());
               buf.schemaName = allowedSchemas[i];
               
               ret.add(buf);
            }
            schemaLoadInfos = ret.toArray(new SchemaLoadInfo[ret.size()]);
         }
      }
      return schemaLoadInfos;
   }

   SchemaLoadInfo[] getMatchingSchemaLoadInfos(String schemaName)
   {
      return getMatchingSchemaLoadInfos(schemaName, null);
   }

   SchemaLoadInfo[] getMatchingSchemaLoadInfos(String schemaName, String[] tableTypes)
   {
      if(null == schemaName)
      {
         return getAllSchemaLoadInfos();
      }

      SchemaLoadInfo[] schemaLoadInfos = getAllSchemaLoadInfos();
      for (int i = 0; i < schemaLoadInfos.length; i++)
      {
         if(null == schemaLoadInfos[i].schemaName || schemaLoadInfos[i].schemaName.equals(schemaName))
         {
            
            // null == schemaLoadInfos[0].schemaName is the case when there are no _schemas specified
            // schemaLoadInfos.length will then be 1.
            schemaLoadInfos[i].schemaName = schemaName;
            if(null != tableTypes)
            {
               SchemaLoadInfo buf = (SchemaLoadInfo) Utilities.cloneObject(
                     schemaLoadInfos[i], getClass().getClassLoader());
               buf.tableTypes = tableTypes;
               return new SchemaLoadInfo[]{buf};
            }

            return new SchemaLoadInfo[]{schemaLoadInfos[i]};
         }
      }
      throw new IllegalArgumentException("Unknown Schema " + schemaName);
   }

   private void initTypes()
   {
      ArrayList tableTypeCandidates = new ArrayList();
      tableTypeCandidates.add("TABLE");
      tableTypeCandidates.add("SYSTEM TABLE");

      ArrayList viewTypeCandidates = new ArrayList();
      viewTypeCandidates.add("VIEW");

      try
      {
         ArrayList availableBuf = new ArrayList();
         String[] buf = _session.getSQLConnection().getSQLMetaData().getTableTypes();
         availableBuf.addAll(Arrays.asList(buf));

         for(Iterator i=tableTypeCandidates.iterator();i.hasNext();)
         {
            if(false == availableBuf.contains(i.next()))
            {
               i.remove();
            }
         }

         for(Iterator i=viewTypeCandidates.iterator();i.hasNext();)
         {
            if(false == availableBuf.contains(i.next()))
            {
               i.remove();
            }
         }
      }
      catch (SQLException e)
      {
         s_log.error("Could not get table types", e);
      }

      _tabelTableTypesCacheable = tableTypeCandidates.toArray(new String[tableTypeCandidates.size()]);
      _viewTableTypesCacheable = viewTypeCandidates.toArray(new String[viewTypeCandidates.size()]);
   }

   public boolean isCachedTableType(String type)
   {
      boolean found = false;

      for (int i = 0; i < _viewTableTypesCacheable.length; i++)
      {
         if(_viewTableTypesCacheable[i].equals(type))
         {
            found = true;
            break;
         }
      }

      for (int i = 0; i < _tabelTableTypesCacheable.length; i++)
      {
         if(_tabelTableTypesCacheable[i].equals(type))
         {
            found = true;
            break;
         }
      }

      return found;
   }

   static boolean containsType(String[] types, String type)
   {
      if(null == types)
      {
         return true;
      }

      for (int i = 0; i < types.length; i++)
      {
         if(type.trim().equalsIgnoreCase(types[i]))
         {
            return true;
         }
      }
      return false;
   }

   /**
    * Adds the specified array of ITableInfos to the internal list(s), and sorts
    * the combination.
    *  
    * @param infos the array of ITableInfos to add.
    */
   public void writeToTableCache(ITableInfo[] infos) {
      for (ITableInfo info : infos) {
         String tableName = info.getSimpleName();
         CaseInsensitiveString ciTableName = new CaseInsensitiveString(tableName);
         _tableNames.put(ciTableName, tableName);
         
         List aITabInfos = _tableInfosBySimpleName.get(ciTableName);
         if(null == aITabInfos)
         {
            aITabInfos = new ArrayList();
            _tableInfosBySimpleName.put(ciTableName, aITabInfos);
         }
         aITabInfos.add(info);         
      }
      // CopyOnWriteArrayList is unfortunately not sort-able as a List.  So this
      // will throw an UnsupportedOperationException:
      //
      // Collections.sort(_iTableInfos, new TableInfoSimpleNameComparator());
      //
      // The following is the best approach according to concurrency master 
      // Doug Lea, in this post: 
      // http://osdir.com/ml/java.jsr.166-concurrency/2004-06/msg00001.html
      //
      // Here we copy the existing internal array into a new array that
      // is large enough to hold the original and new elements.  Then sort it.  
      // And finally, create a new CopyOnWriteArrayList with the sorted array.
      
      /* Now, create an array large enough to hold the original and the new */
      int currSize = _iTableInfos.size();
      ITableInfo[] tableArr = 
         _iTableInfos.toArray(new ITableInfo[currSize+infos.length]);
      /* 
       * Append the new tables to the new array, starting at the end of the 
       * original 
       */
      for (int i = 0; i < infos.length; i++) {
         tableArr[currSize + i] = infos[i];
      }
      
      /* Sort it and store in a new CopyOnWriteArrayList */
      Arrays.sort(tableArr, new TableInfoSimpleNameComparator());
      _iTableInfos = new CopyOnWriteArrayList(tableArr);
   }
   
   /**
    * Adds a single ITableInfo to the internal list(s) and re-sorts.  This 
    * should not be called in a tight loop iterating over a list of ITableInfos.
    * If the caller is looping over an array of ITableInfo objects, please use 
    * the version that accepts the ITableInfo array instead.
    * 
    * @param info the ITableInfo to add.
    */
   public void writeToTableCache(ITableInfo info)
   {
      writeToTableCache(new ITableInfo[] { info });      
   }


   public void writeToProcedureCache(IProcedureInfo procedure)
   {
      String proc = procedure.getSimpleName();
      if (proc.length() > 0)
      {
         CaseInsensitiveString ciProc = new CaseInsensitiveString(proc);
         _procedureNames.put(ciProc ,proc);

         List aIProcInfos = _procedureInfosBySimpleName.get(ciProc);
         if(null == aIProcInfos)
         {
            aIProcInfos = new ArrayList();
            _procedureInfosBySimpleName.put(ciProc, aIProcInfos);
         }
         aIProcInfos.add(procedure);
      }
      _iProcedureInfos.put(procedure, procedure);
   }


   public void writeColumsToCache(TableColumnInfo[] infos, CaseInsensitiveString simpleTableName)
   {
      _schemaInfoColumnCache.writeColumsToCache(infos, simpleTableName);
   }

   public void writeColumsNotAccessible(Throwable th, CaseInsensitiveString tableName)
   {
      _schemaInfoColumnCache.writeColumsNotAccessible(th, tableName);
   }



   void initialLoadDone()
   {
      /**
       * When _schemaPropsCacheIsBasedOn is null all loading will be done like there was no cache.
       *
       * This will make sure loading only heeds the cache during initial loading.
       *
       * Any further loading (via Object tree or tool bar) will be treated as a Cache refresh.
       */
      _schemaPropsCacheIsBasedOn = null;
   }

   void prepareSerialization()
   {
      _schemaPropsCacheIsBasedOn = _session.getAlias().getSchemaProperties();

      if(false == _schemaPropsCacheIsBasedOn.isCacheSchemaIndependentMetaData())
      {
         clearSchemaIndependentData();
      }

      if(SQLAliasSchemaProperties.GLOBAL_STATE_LOAD_ALL_CACHE_NONE == _schemaPropsCacheIsBasedOn.getGlobalState())
      {
         clearAllSchemaDependentData();
      }
      else if(SQLAliasSchemaProperties.GLOBAL_STATE_SPECIFY_SCHEMAS == _schemaPropsCacheIsBasedOn.getGlobalState())
      {
         SchemaTableTypeCombination[] tableTypeCombis =
            _schemaPropsCacheIsBasedOn.getAllSchemaTableTypeCombinationsNotToBeCached(_tabelTableTypesCacheable, _viewTableTypesCacheable);

         for (int i = 0; i < tableTypeCombis.length; i++)
         {
            clearTables(null, tableTypeCombis[i].schemaName, null, tableTypeCombis[i].types);
         }

         String[] procedureSchemas = _schemaPropsCacheIsBasedOn.getAllSchemaProceduresNotToBeCached();
         for (int i = 0; i < procedureSchemas.length; i++)
         {
            clearStoredProcedures(null, procedureSchemas[i], null);
         }


      }

   }

   void clearAll()
   {
      clearSchemaIndependentData();


      clearAllSchemaDependentData();

   }

   private void clearAllSchemaDependentData()
   {
      _tableNames.clear();
      synchronized(_iTableInfos) {
          _iTableInfos.clear();
      }
      _tableInfosBySimpleName.clear();

      _schemaInfoColumnCache.clearColumns();


      _procedureNames.clear();
      _iProcedureInfos.clear();
      _procedureInfosBySimpleName.clear();

      _schemas.clear();

   }

   private void clearSchemaIndependentData()
   {
      _catalogs.clear();

      _keywords.clear();
      _dataTypes.clear();
      _functions.clear();
   }

   void clearAllTableData() {
   	_iTableInfos = new CopyOnWriteArrayList();
   	_tableInfosBySimpleName = new Hashtable>();
   	_tableNames = Collections.synchronizedMap(_internalTableNameTreeMap);
   }
   
   void clearTables(String catalogName, String schemaName, String simpleName, String[] types)
   {
      for(Iterator i = _iTableInfos.iterator(); i.hasNext();)
      {
         ITableInfo ti = i.next();

         boolean matches = matchesMetaString(ti.getCatalogName(), catalogName);
         matches &= matchesMetaString(ti.getSchemaName(), schemaName);
         matches &= matchesMetaString(ti.getSimpleName(), simpleName);

         if(null != types)
         {
            boolean found = false;
            for (int j = 0; j < types.length; j++)
            {
               if(types[j].equals(ti.getType()))
               {
                  found = true;
                  break;
               }
            }

            matches &= found;
         }

         if(matches)
         {
             // CopyOnWriteArrayList has snapshot iterators that don't support 
             // iterator.remove()
             _iTableInfos.remove(ti);

            CaseInsensitiveString ciSimpleTableName = new CaseInsensitiveString(ti.getSimpleName());
            List tableInfos = _tableInfosBySimpleName.get(ciSimpleTableName);
            tableInfos.remove(ti);
            if(0 == tableInfos.size())
            {
               _tableInfosBySimpleName.remove(ciSimpleTableName);
               _tableNames.remove(ciSimpleTableName);
            }

            _schemaInfoColumnCache.clearColumns(ciSimpleTableName);
         }
      }

   }

   void clearStoredProcedures(String catalogName, String schemaName, String simpleName)
   {
      for(Iterator i = _iProcedureInfos.keySet().iterator(); i.hasNext();)
      {
         IProcedureInfo pi = i.next();


         boolean matches = matchesMetaString(pi.getCatalogName(), catalogName);
         matches &= matchesMetaString(pi.getSchemaName(), schemaName);
         matches &= matchesMetaString(pi.getSimpleName(), simpleName);


         if(matches)
         {
            i.remove();

            CaseInsensitiveString ciSimpleName = new CaseInsensitiveString(pi.getSimpleName());
            List procedureInfos = _procedureInfosBySimpleName.get(ciSimpleName);
            procedureInfos.remove(pi);
            if(0 == procedureInfos.size())
            {
               _procedureInfosBySimpleName.remove(ciSimpleName);
               _procedureNames.remove(ciSimpleName);
            }

         }
      }
   }


   private boolean matchesMetaString(String s, String toCheck)
   {
      if(null == s || null == toCheck)
      {
         return true;
      }

      return s.equals(toCheck);
   }

   SchemaNameLoadInfo getSchemaNameLoadInfo()
   {
      return _session.getAlias().getSchemaProperties().getSchemaNameLoadInfo(_schemaPropsCacheIsBasedOn);
   }

   void writeCatalogs(String[] catalogs)
   {
      this._catalogs.clear();
      this._catalogs.addAll(Arrays.asList(catalogs));
   }

   void writeSchemas(String[] schemasToWrite)
   {
      _schemas.clear();
      _schemas.addAll(Arrays.asList(schemasToWrite));
   }


   void writeKeywords(Hashtable keywordsBuf)
   {
      _keywords.clear();
      _keywords.putAll(keywordsBuf);
   }


   void writeDataTypes(Hashtable dataTypesBuf)
   {
      _dataTypes.clear();
      _dataTypes.putAll(dataTypesBuf);
   }

   void writeFunctions(Hashtable functionsBuf)
   {
      _functions.clear();
      _functions.putAll(functionsBuf);
   }

   List getCatalogsForReadOnly()
   {
      return _catalogs;
   }

   List getSchemasForReadOnly()
   {
      return _schemas;
   }

   TreeMap getKeywordsForReadOnly()
   {
      return _keywords;
   }

   TreeMap getDataTypesForReadOnly()
   {
      return _dataTypes;
   }

   Map getFunctionsForReadOnly()
   {
      return _functions;
   }

   Map getTableNamesForReadOnly()
   {
      return _internalTableNameTreeMap;
   }

   List getITableInfosForReadOnly()
   {
      return _iTableInfos;
   }

   Hashtable> getTableInfosBySimpleNameForReadOnly()
   {
      return _tableInfosBySimpleName;
   }


   public boolean didTryLoadingColumns(CaseInsensitiveString tableName)
   {
      return _schemaInfoColumnCache.didTryLoadingColumns(tableName);
   }

   public List getExtendedColumnInfosForReadOnly(CaseInsensitiveString cissTableName)
   {
      return _schemaInfoColumnCache.getExtendedColumnInfosForReadOnly(cissTableName);
   }


   Map> getExtColumnInfosByColumnNameForReadOnly()
   {
      return _schemaInfoColumnCache.getExtColumnInfosByColumnNameForReadOnly();
   }

   Map getProcedureNamesForReadOnly()
   {
      return _procedureNames;
   }

   Map getIProcedureInfosForReadOnly()
   {
      return _iProcedureInfos;
   }

   /**
    * When SchemaInfoCache has been deserialized the the constants in DatabaseObjectType
    * still come from the last serialisation. Thus the == operator won't work
    * unless we replace the DatabaseObjectTypes
    *
    */
   void replaceDatabaseObjectTypeConstantObjectsByConstantObjectsOfThisVM()
   {
      for (ITableInfo iTableInfo : _iTableInfos)
      {
         if(iTableInfo instanceof TableInfo)
         {
            ((TableInfo)iTableInfo).replaceDatabaseObjectTypeConstantObjectsByConstantObjectsOfThisVM();
         }
      }

      for (IProcedureInfo iProcedureInfo : _iProcedureInfos.keySet())
      {
         if(iProcedureInfo instanceof DatabaseObjectInfo)
         {
            ((DatabaseObjectInfo)iProcedureInfo).replaceDatabaseObjectTypeConstantObjectsByConstantObjectsOfThisVM(DatabaseObjectType.PROCEDURE);
         }
      }

   }

   /**
    * A comparator for ITableInfos that compares them using their simple name.
    * All other data (such as schema) is ignored, since it isn't likely that we 
    * will need to compare tables in multiple schemas/catalogs in the same list.
    */
   private class TableInfoSimpleNameComparator implements
         Comparator {
      public int compare(ITableInfo o1, ITableInfo o2) {
         return o1.getSimpleName().compareTo(o2.getSimpleName());
      }
   }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy