1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.adservices.data.measurement; 18 19 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_DATASTORE_FAILURE; 20 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_DATASTORE_UNKNOWN_FAILURE; 21 import static com.android.adservices.service.stats.AdServicesStatsLog.AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT; 22 23 import com.android.adservices.LoggerFactory; 24 import com.android.adservices.service.FlagsFactory; 25 import com.android.adservices.shared.errorlogging.AdServicesErrorLogger; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.util.Optional; 29 import java.util.concurrent.ThreadLocalRandom; 30 31 /** 32 * Abstract class for Datastore management. 33 */ 34 public abstract class DatastoreManager { 35 final AdServicesErrorLogger mErrorLogger; 36 DatastoreManager(AdServicesErrorLogger errorLogger)37 protected DatastoreManager(AdServicesErrorLogger errorLogger) { 38 mErrorLogger = errorLogger; 39 } 40 41 /** 42 * Consumer interface for Dao operations. 43 */ 44 @FunctionalInterface 45 public interface ThrowingCheckedConsumer { 46 /** 47 * Performs the operation on {@link IMeasurementDao}. 48 */ accept(IMeasurementDao measurementDao)49 void accept(IMeasurementDao measurementDao) throws DatastoreException; 50 } 51 52 /** 53 * Function interface for Dao operations that returns {@link Output}. 54 * 55 * @param <Output> output type 56 */ 57 @FunctionalInterface 58 public interface ThrowingCheckedFunction<Output> { 59 /** 60 * Performs the operation on Dao. 61 * 62 * @return Output result of the operation 63 */ apply(IMeasurementDao measurementDao)64 Output apply(IMeasurementDao measurementDao) throws DatastoreException; 65 } 66 67 /** 68 * Creates a new transaction object for use in Dao. 69 * 70 * @return transaction 71 */ createNewTransaction()72 protected abstract ITransaction createNewTransaction(); 73 74 /** 75 * Acquire an instance of Dao object for querying the datastore. 76 * 77 * @return Dao object. 78 */ 79 @VisibleForTesting getMeasurementDao()80 public abstract IMeasurementDao getMeasurementDao(); 81 82 /** 83 * Runs the {@code execute} lambda in a transaction. 84 * 85 * @param execute lambda to be executed in a transaction 86 * @param <T> the class for result 87 * @return Optional<T>, empty in case of an error, output otherwise 88 */ runInTransactionWithResult(ThrowingCheckedFunction<T> execute)89 public final <T> Optional<T> runInTransactionWithResult(ThrowingCheckedFunction<T> execute) { 90 IMeasurementDao measurementDao = getMeasurementDao(); 91 ITransaction transaction = createNewTransaction(); 92 if (transaction == null) { 93 return Optional.empty(); 94 } 95 measurementDao.setTransaction(transaction); 96 transaction.begin(); 97 98 Optional<T> result; 99 try { 100 result = Optional.ofNullable(execute.apply(measurementDao)); 101 } catch (DatastoreException ex) { 102 result = Optional.empty(); 103 safePrintDataStoreVersion(); 104 LoggerFactory.getMeasurementLogger() 105 .e(ex, "DatastoreException thrown during transaction"); 106 mErrorLogger.logErrorWithExceptionInfo( 107 ex, 108 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_DATASTORE_FAILURE, 109 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT); 110 transaction.rollback(); 111 112 if (FlagsFactory.getFlags() 113 .getMeasurementEnableDatastoreManagerThrowDatastoreException() 114 && ThreadLocalRandom.current().nextFloat() 115 < FlagsFactory.getFlags() 116 .getMeasurementThrowUnknownExceptionSamplingRate()) { 117 throw new IllegalStateException(ex); 118 } 119 } catch (Exception ex) { 120 // Catch all exceptions for rollback 121 safePrintDataStoreVersion(); 122 LoggerFactory.getMeasurementLogger() 123 .e(ex, "Unhandled exception thrown during transaction"); 124 mErrorLogger.logErrorWithExceptionInfo( 125 ex, 126 AD_SERVICES_ERROR_REPORTED__ERROR_CODE__MEASUREMENT_DATASTORE_UNKNOWN_FAILURE, 127 AD_SERVICES_ERROR_REPORTED__PPAPI_NAME__MEASUREMENT); 128 transaction.rollback(); 129 throw ex; 130 } finally { 131 transaction.end(); 132 } 133 134 return result; 135 } 136 137 /** 138 * Runs the {@code execute} lambda in a transaction. 139 * 140 * @param execute lambda to be executed in transaction 141 * @return success true if execution succeeded, false otherwise 142 */ runInTransaction(ThrowingCheckedConsumer execute)143 public final boolean runInTransaction(ThrowingCheckedConsumer execute) { 144 return runInTransactionWithResult((measurementDao) -> { 145 execute.accept(measurementDao); 146 return true; 147 }).orElse(false); 148 } 149 150 /** Prints the underlying data store version catching exceptions it can raise. */ safePrintDataStoreVersion()151 private void safePrintDataStoreVersion() { 152 try { 153 LoggerFactory.getMeasurementLogger() 154 .w("Underlying datastore version: " + getDataStoreVersion()); 155 } catch (Exception e) { 156 // If fetching data store version throws an exception, skip printing the DB version. 157 LoggerFactory.getMeasurementLogger().e(e, "Failed to print data store version."); 158 } 159 } 160 161 /** Returns the version the underlying data store is at. E.g. user version of the DB. */ getDataStoreVersion()162 protected abstract int getDataStoreVersion(); 163 } 164