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