mobile/android/base/db/SQLiteBridgeContentProvider.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.db;
     7 import java.io.File;
     8 import java.util.HashMap;
    10 import org.mozilla.gecko.GeckoProfile;
    11 import org.mozilla.gecko.GeckoThread;
    12 import org.mozilla.gecko.Telemetry;
    13 import org.mozilla.gecko.mozglue.GeckoLoader;
    14 import org.mozilla.gecko.sqlite.SQLiteBridge;
    15 import org.mozilla.gecko.sqlite.SQLiteBridgeException;
    17 import android.content.ContentProvider;
    18 import android.content.ContentUris;
    19 import android.content.ContentValues;
    20 import android.content.Context;
    21 import android.database.Cursor;
    22 import android.net.Uri;
    23 import android.text.TextUtils;
    24 import android.util.Log;
    26 /*
    27  * Provides a basic ContentProvider that sets up and sends queries through
    28  * SQLiteBridge. Content providers should extend this by setting the appropriate
    29  * table and version numbers in onCreate, and implementing the abstract methods:
    30  *
    31  *  public abstract String getTable(Uri uri);
    32  *  public abstract String getSortOrder(Uri uri, String aRequested);
    33  *  public abstract void setupDefaults(Uri uri, ContentValues values);
    34  *  public abstract void initGecko();
    35  */
    37 public abstract class SQLiteBridgeContentProvider extends ContentProvider {
    38     private static final String ERROR_MESSAGE_DATABASE_IS_LOCKED = "Can't step statement: (5) database is locked";
    40     private HashMap<String, SQLiteBridge> mDatabasePerProfile;
    41     protected Context mContext = null;
    42     private final String mLogTag;
    44     protected SQLiteBridgeContentProvider(String logTag) {
    45         mLogTag = logTag;
    46     }
    48     /**
    49      * Subclasses must override this to allow error reporting code to compose
    50      * the correct histogram name.
    51      *
    52      * Ensure that you define the new histograms if you define a new class!
    53      */
    54     protected abstract String getTelemetryPrefix();
    56     /**
    57      * Errors are recorded in telemetry using an enumerated histogram.
    58      *
    59      * <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/
    60      * Adding_a_new_Telemetry_probe#Choosing_a_Histogram_Type>
    61      *
    62      * These are the allowable enumeration values. Keep these in sync with the
    63      * histogram definition!
    64      *
    65      */
    66     private static enum TelemetryErrorOp {
    67         BULKINSERT (0),
    68         DELETE     (1),
    69         INSERT     (2),
    70         QUERY      (3),
    71         UPDATE     (4);
    73         private final int bucket;
    75         TelemetryErrorOp(final int bucket) {
    76             this.bucket = bucket;
    77         }
    79         public int getBucket() {
    80             return bucket;
    81         }
    82     }
    84     @Override
    85     public void shutdown() {
    86         if (mDatabasePerProfile == null) {
    87             return;
    88         }
    90         synchronized (this) {
    91             for (SQLiteBridge bridge : mDatabasePerProfile.values()) {
    92                 if (bridge != null) {
    93                     try {
    94                         bridge.close();
    95                     } catch (Exception ex) { }
    96                 }
    97             }
    98             mDatabasePerProfile = null;
    99         }
   100     }
   102     @Override
   103     public void finalize() {
   104         shutdown();
   105     }
   107     /**
   108      * Return true of the query is from Firefox Sync.
   109      * @param uri query URI
   110      */
   111     public static boolean isCallerSync(Uri uri) {
   112         String isSync = uri.getQueryParameter(BrowserContract.PARAM_IS_SYNC);
   113         return !TextUtils.isEmpty(isSync);
   114     }
   116     private SQLiteBridge getDB(Context context, final String databasePath) {
   117         SQLiteBridge bridge = null;
   119         boolean dbNeedsSetup = true;
   120         try {
   121             String resourcePath = context.getPackageResourcePath();
   122             GeckoLoader.loadSQLiteLibs(context, resourcePath);
   123             GeckoLoader.loadNSSLibs(context, resourcePath);
   124             bridge = SQLiteBridge.openDatabase(databasePath, null, 0);
   125             int version = bridge.getVersion();
   126             dbNeedsSetup = version != getDBVersion();
   127         } catch (SQLiteBridgeException ex) {
   128             // close the database
   129             if (bridge != null) {
   130                 bridge.close();
   131             }
   133             // this will throw if the database can't be found
   134             // we should attempt to set it up if Gecko is running
   135             dbNeedsSetup = true;
   136             Log.e(mLogTag, "Error getting version ", ex);
   138             // if Gecko is not running, we should bail out. Otherwise we try to
   139             // let Gecko build the database for us
   140             if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
   141                 Log.e(mLogTag, "Can not set up database. Gecko is not running");
   142                 return null;
   143             }
   144         }
   146         // If the database is not set up yet, or is the wrong schema version, we send an initialize
   147         // call to Gecko. Gecko will handle building the database file correctly, as well as any
   148         // migrations that are necessary
   149         if (dbNeedsSetup) {
   150             bridge = null;
   151             initGecko();
   152         }
   153         return bridge;
   154     }
   156     /**
   157      * Returns the absolute path of a database file depending on the specified profile and dbName.
   158      * @param profile
   159      *          the profile whose dbPath must be returned
   160      * @param dbName
   161      *          the name of the db file whose absolute path must be returned
   162      * @return the absolute path of the db file or <code>null</code> if it was not possible to retrieve a valid path
   163      *
   164      */
   165     private String getDatabasePathForProfile(String profile, String dbName) {
   166         // Depends on the vagaries of GeckoProfile.get, so null check for safety.
   167         File profileDir = GeckoProfile.get(mContext, profile).getDir();
   168         if (profileDir == null) {
   169             return null;
   170         }
   172         String databasePath = new File(profileDir, dbName).getAbsolutePath();
   173         return databasePath;
   174     }
   176     /**
   177      * Returns a SQLiteBridge object according to the specified profile id and to the name of db related to the
   178      * current provider instance.
   179      * @param profile
   180      *          the id of the profile to be used to retrieve the related SQLiteBridge
   181      * @return the <code>SQLiteBridge</code> related to the specified profile id or <code>null</code> if it was 
   182      *         not possible to retrieve a valid SQLiteBridge
   183      */
   184     private SQLiteBridge getDatabaseForProfile(String profile) {
   185         if (TextUtils.isEmpty(profile)) {
   186             profile = GeckoProfile.get(mContext).getName();
   187             Log.d(mLogTag, "No profile provided, using '" + profile + "'");
   188         }
   190         final String dbName = getDBName();
   191         String mapKey = profile + "/" + dbName;
   193         SQLiteBridge db = null;
   194         synchronized (this) {
   195             db = mDatabasePerProfile.get(mapKey);
   196             if (db != null) {
   197                 return db;
   198             }
   199             final String dbPath = getDatabasePathForProfile(profile, dbName);
   200             if (dbPath == null) {   
   201                 Log.e(mLogTag, "Failed to get a valid db path for profile '" + profile + "'' dbName '" + dbName + "'");
   202                 return null;
   203             }
   204             db = getDB(mContext, dbPath);
   205             if (db != null) {
   206                 mDatabasePerProfile.put(mapKey, db);
   207             }
   208         }
   209         return db;
   210     }
   212     /**
   213      * Returns a SQLiteBridge object according to the specified profile path and to the name of db related to the
   214      * current provider instance.
   215      * @param profilePath
   216      *          the profilePath to be used to retrieve the related SQLiteBridge
   217      * @return the <code>SQLiteBridge</code> related to the specified profile path or <code>null</code> if it was
   218      *         not possible to retrieve a valid <code>SQLiteBridge</code>
   219      */
   220     private SQLiteBridge getDatabaseForProfilePath(String profilePath) {
   221         File profileDir = new File(profilePath, getDBName());
   222         final String dbPath = profileDir.getPath();
   223         return getDatabaseForDBPath(dbPath);
   224     }
   226     /**
   227      * Returns a SQLiteBridge object according to the specified file path.
   228      * @param dbPath
   229      *          the path of the file to be used to retrieve the related SQLiteBridge
   230      * @return the <code>SQLiteBridge</code> related to the specified file path or <code>null</code> if it was  
   231      *         not possible to retrieve a valid <code>SQLiteBridge</code>
   232      *
   233      */
   234     private SQLiteBridge getDatabaseForDBPath(String dbPath) {
   235         SQLiteBridge db = null;
   236         synchronized (this) {
   237             db = mDatabasePerProfile.get(dbPath);
   238             if (db != null) {
   239                 return db;
   240             }
   241             db = getDB(mContext, dbPath);
   242             if (db != null) {
   243                 mDatabasePerProfile.put(dbPath, db);
   244             }
   245         }
   246         return db;
   247     }
   249     /**
   250      * Returns a SQLiteBridge object to be used to perform operations on the given <code>Uri</code>.
   251      * @param uri
   252      *          the <code>Uri</code> to be used to retrieve the related SQLiteBridge
   253      * @return a <code>SQLiteBridge</code> object to be used on the given uri or <code>null</code> if it was 
   254      *         not possible to retrieve a valid <code>SQLiteBridge</code>
   255      *
   256      */
   257     private SQLiteBridge getDatabase(Uri uri) {
   258         String profile = null;
   259         String profilePath = null;
   261         profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
   262         profilePath = uri.getQueryParameter(BrowserContract.PARAM_PROFILE_PATH);
   264         // Testing will specify the absolute profile path
   265         if (profilePath != null) {
   266             return getDatabaseForProfilePath(profilePath);
   267         }
   268         return getDatabaseForProfile(profile);
   269     }
   271     @Override
   272     public boolean onCreate() {
   273         mContext = getContext();
   274         synchronized (this) {
   275             mDatabasePerProfile = new HashMap<String, SQLiteBridge>();
   276         }
   277         return true;
   278     }
   280     @Override
   281     public String getType(Uri uri) {
   282         return null;
   283     }
   285     @Override
   286     public int delete(Uri uri, String selection, String[] selectionArgs) {
   287         int deleted = 0;
   288         final SQLiteBridge db = getDatabase(uri);
   289         if (db == null) {
   290             return deleted;
   291         }
   293         try {
   294             deleted = db.delete(getTable(uri), selection, selectionArgs);
   295         } catch (SQLiteBridgeException ex) {
   296             reportError(ex, TelemetryErrorOp.DELETE);
   297             throw ex;
   298         }
   300         return deleted;
   301     }
   303     @Override
   304     public Uri insert(Uri uri, ContentValues values) {
   305         long id = -1;
   306         final SQLiteBridge db = getDatabase(uri);
   308         // If we can not get a SQLiteBridge instance, its likely that the database
   309         // has not been set up and Gecko is not running. We return null and expect
   310         // callers to try again later
   311         if (db == null) {
   312             return null;
   313         }
   315         setupDefaults(uri, values);
   317         boolean useTransaction = !db.inTransaction();
   318         try {
   319             if (useTransaction) {
   320                 db.beginTransaction();
   321             }
   323             // onPreInsert does a check for the item in the deleted table in some cases
   324             // so we put it inside this transaction
   325             onPreInsert(values, uri, db);
   326             id = db.insert(getTable(uri), null, values);
   328             if (useTransaction) {
   329                 db.setTransactionSuccessful();
   330             }
   331         } catch (SQLiteBridgeException ex) {
   332             reportError(ex, TelemetryErrorOp.INSERT);
   333             throw ex;
   334         } finally {
   335             if (useTransaction) {
   336                 db.endTransaction();
   337             }
   338         }
   340         return ContentUris.withAppendedId(uri, id);
   341     }
   343     @Override
   344     public int bulkInsert(Uri uri, ContentValues[] allValues) {
   345         final SQLiteBridge db = getDatabase(uri);
   346         // If we can not get a SQLiteBridge instance, its likely that the database
   347         // has not been set up and Gecko is not running. We return 0 and expect
   348         // callers to try again later
   349         if (db == null) {
   350             return 0;
   351         }
   353         int rowsAdded = 0;
   355         String table = getTable(uri);
   357         try {
   358             db.beginTransaction();
   359             for (ContentValues initialValues : allValues) {
   360                 ContentValues values = new ContentValues(initialValues);
   361                 setupDefaults(uri, values);
   362                 onPreInsert(values, uri, db);
   363                 db.insert(table, null, values);
   364                 rowsAdded++;
   365             }
   366             db.setTransactionSuccessful();
   367         } catch (SQLiteBridgeException ex) {
   368             reportError(ex, TelemetryErrorOp.BULKINSERT);
   369             throw ex;
   370         } finally {
   371             db.endTransaction();
   372         }
   374         if (rowsAdded > 0) {
   375             final boolean shouldSyncToNetwork = !isCallerSync(uri);
   376             mContext.getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
   377         }
   379         return rowsAdded;
   380     }
   382     @Override
   383     public int update(Uri uri, ContentValues values, String selection,
   384             String[] selectionArgs) {
   385         int updated = 0;
   386         final SQLiteBridge db = getDatabase(uri);
   388         // If we can not get a SQLiteBridge instance, its likely that the database
   389         // has not been set up and Gecko is not running. We return null and expect
   390         // callers to try again later
   391         if (db == null) {
   392             return updated;
   393         }
   395         onPreUpdate(values, uri, db);
   397         try {
   398             updated = db.update(getTable(uri), values, selection, selectionArgs);
   399         } catch (SQLiteBridgeException ex) {
   400             reportError(ex, TelemetryErrorOp.UPDATE);
   401             throw ex;
   402         }
   404         return updated;
   405     }
   407     @Override
   408     public Cursor query(Uri uri, String[] projection, String selection,
   409             String[] selectionArgs, String sortOrder) {
   410         Cursor cursor = null;
   411         final SQLiteBridge db = getDatabase(uri);
   413         // If we can not get a SQLiteBridge instance, its likely that the database
   414         // has not been set up and Gecko is not running. We return null and expect
   415         // callers to try again later
   416         if (db == null) {
   417             return cursor;
   418         }
   420         sortOrder = getSortOrder(uri, sortOrder);
   422         try {
   423             cursor = db.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder, null);
   424             onPostQuery(cursor, uri, db);
   425         } catch (SQLiteBridgeException ex) {
   426             reportError(ex, TelemetryErrorOp.QUERY);
   427             throw ex;
   428         }
   430         return cursor;
   431     }
   433     private String getHistogram(SQLiteBridgeException e) {
   434         // If you add values here, make sure to update
   435         // toolkit/components/telemetry/Histograms.json.
   436         if (ERROR_MESSAGE_DATABASE_IS_LOCKED.equals(e.getMessage())) {
   437             return getTelemetryPrefix() + "_LOCKED";
   438         }
   439         return null;
   440     }
   442     protected void reportError(SQLiteBridgeException e, TelemetryErrorOp op) {
   443         Log.e(mLogTag, "Error in database " + op.name(), e);
   444         final String histogram = getHistogram(e);
   445         if (histogram == null) {
   446             return;
   447         }
   449         Telemetry.HistogramAdd(histogram, op.getBucket());
   450     }
   452     protected abstract String getDBName();
   454     protected abstract int getDBVersion();
   456     protected abstract String getTable(Uri uri);
   458     protected abstract String getSortOrder(Uri uri, String aRequested);
   460     protected abstract void setupDefaults(Uri uri, ContentValues values);
   462     protected abstract void initGecko();
   464     protected abstract void onPreInsert(ContentValues values, Uri uri, SQLiteBridge db);
   466     protected abstract void onPreUpdate(ContentValues values, Uri uri, SQLiteBridge db);
   468     protected abstract void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db);
   469 }

mercurial