1 /*
2  * Copyright 2021 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 android.app.appsearch.util;
18 
19 import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA;
20 
21 import android.annotation.NonNull;
22 import android.app.appsearch.AppSearchSchema;
23 import android.app.appsearch.InternalSetSchemaResponse;
24 import android.app.appsearch.Migrator;
25 import android.app.appsearch.SetSchemaResponse;
26 import android.app.appsearch.exceptions.AppSearchException;
27 import android.util.ArrayMap;
28 import android.util.ArraySet;
29 
30 import java.util.Collections;
31 import java.util.Map;
32 import java.util.Set;
33 
34 /**
35  * Utilities for schema migration.
36  *
37  * @hide
38  */
39 public final class SchemaMigrationUtil {
SchemaMigrationUtil()40     private SchemaMigrationUtil() {}
41 
42     /**
43      * Returns all active {@link Migrator}s that need to be triggered in this migration.
44      *
45      * <p>{@link Migrator#shouldMigrate} returns {@code true} will make the {@link Migrator} active.
46      */
47     @NonNull
getActiveMigrators( @onNull Set<AppSearchSchema> existingSchemas, @NonNull Map<String, Migrator> migrators, int currentVersion, int finalVersion)48     public static Map<String, Migrator> getActiveMigrators(
49             @NonNull Set<AppSearchSchema> existingSchemas,
50             @NonNull Map<String, Migrator> migrators,
51             int currentVersion,
52             int finalVersion) {
53         if (currentVersion == finalVersion) {
54             return Collections.emptyMap();
55         }
56         Set<String> existingTypes = new ArraySet<>(existingSchemas.size());
57         for (AppSearchSchema schema : existingSchemas) {
58             existingTypes.add(schema.getSchemaType());
59         }
60 
61         Map<String, Migrator> activeMigrators = new ArrayMap<>();
62         for (Map.Entry<String, Migrator> entry : migrators.entrySet()) {
63             // The device contains the source type, and we should trigger migration for the type.
64             String schemaType = entry.getKey();
65             Migrator migrator = entry.getValue();
66             if (existingTypes.contains(schemaType)
67                     && migrator.shouldMigrate(currentVersion, finalVersion)) {
68                 activeMigrators.put(schemaType, migrator);
69             }
70         }
71         return activeMigrators;
72     }
73 
74     /**
75      * Checks the setSchema() call won't delete any types or has incompatible types after all {@link
76      * Migrator} has been triggered.
77      */
checkDeletedAndIncompatibleAfterMigration( @onNull InternalSetSchemaResponse internalSetSchemaResponse, @NonNull Set<String> activeMigrators)78     public static void checkDeletedAndIncompatibleAfterMigration(
79             @NonNull InternalSetSchemaResponse internalSetSchemaResponse,
80             @NonNull Set<String> activeMigrators)
81             throws AppSearchException {
82         if (internalSetSchemaResponse.isSuccess()) {
83             return;
84         }
85         SetSchemaResponse setSchemaResponse = internalSetSchemaResponse.getSetSchemaResponse();
86         Set<String> unmigratedIncompatibleTypes =
87                 new ArraySet<>(setSchemaResponse.getIncompatibleTypes());
88         unmigratedIncompatibleTypes.removeAll(activeMigrators);
89 
90         Set<String> unmigratedDeletedTypes = new ArraySet<>(setSchemaResponse.getDeletedTypes());
91         unmigratedDeletedTypes.removeAll(activeMigrators);
92 
93         // check if there are any unmigrated incompatible types or deleted types. If there
94         // are, we will throw an exception. That's the only case we swallowed in the
95         // AppSearchImpl#setSchema().
96         // Since the force override is false, the schema will not have been set if there are
97         // any incompatible or deleted types.
98         if (!unmigratedIncompatibleTypes.isEmpty() || !unmigratedDeletedTypes.isEmpty()) {
99             throw new AppSearchException(
100                     RESULT_INVALID_SCHEMA, internalSetSchemaResponse.getErrorMessage());
101         }
102     }
103 }
104