• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.appsearch.annotation.CanIgnoreReturnValue;
22 import android.os.Bundle;
23 import android.util.ArraySet;
24 
25 import com.android.internal.util.Preconditions;
26 
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.Set;
33 
34 /** The response class of {@link AppSearchSession#setSchema} */
35 public class SetSchemaResponse {
36 
37     private static final String DELETED_TYPES_FIELD = "deletedTypes";
38     private static final String INCOMPATIBLE_TYPES_FIELD = "incompatibleTypes";
39     private static final String MIGRATED_TYPES_FIELD = "migratedTypes";
40 
41     private final Bundle mBundle;
42     /**
43      * The migrationFailures won't be saved in the bundle. Since:
44      *
45      * <ul>
46      *   <li>{@link MigrationFailure} is generated in {@link AppSearchSession} which will be the SDK
47      *       side in platform. We don't need to pass it from service side via binder.
48      *   <li>Translate multiple {@link MigrationFailure}s to bundles in {@link Builder} and then
49      *       back in constructor will be a huge waste.
50      * </ul>
51      */
52     private final List<MigrationFailure> mMigrationFailures;
53 
54     /** Cache of the inflated deleted schema types. Comes from inflating mBundles at first use. */
55     @Nullable private Set<String> mDeletedTypes;
56 
57     /** Cache of the inflated migrated schema types. Comes from inflating mBundles at first use. */
58     @Nullable private Set<String> mMigratedTypes;
59 
60     /**
61      * Cache of the inflated incompatible schema types. Comes from inflating mBundles at first use.
62      */
63     @Nullable private Set<String> mIncompatibleTypes;
64 
SetSchemaResponse(@onNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures)65     SetSchemaResponse(@NonNull Bundle bundle, @NonNull List<MigrationFailure> migrationFailures) {
66         mBundle = Objects.requireNonNull(bundle);
67         mMigrationFailures = Objects.requireNonNull(migrationFailures);
68     }
69 
SetSchemaResponse(@onNull Bundle bundle)70     SetSchemaResponse(@NonNull Bundle bundle) {
71         this(bundle, /*migrationFailures=*/ Collections.emptyList());
72     }
73 
74     /**
75      * Returns the {@link Bundle} populated by this builder.
76      *
77      * @hide
78      */
79     @NonNull
getBundle()80     public Bundle getBundle() {
81         return mBundle;
82     }
83 
84     /**
85      * Returns a {@link List} of all failed {@link MigrationFailure}.
86      *
87      * <p>A {@link MigrationFailure} will be generated if the system trying to save a post-migrated
88      * {@link GenericDocument} but fail.
89      *
90      * <p>{@link MigrationFailure} contains the namespace, id and schemaType of the post-migrated
91      * {@link GenericDocument} and the error reason. Mostly it will be mismatch the schema it
92      * migrated to.
93      */
94     @NonNull
getMigrationFailures()95     public List<MigrationFailure> getMigrationFailures() {
96         return Collections.unmodifiableList(mMigrationFailures);
97     }
98 
99     /**
100      * Returns a {@link Set} of deleted schema types.
101      *
102      * <p>A "deleted" type is a schema type that was previously a part of the database schema but
103      * was not present in the {@link SetSchemaRequest} object provided in the {@link
104      * AppSearchSession#setSchema} call.
105      *
106      * <p>Documents for a deleted type are removed from the database.
107      */
108     @NonNull
getDeletedTypes()109     public Set<String> getDeletedTypes() {
110         if (mDeletedTypes == null) {
111             mDeletedTypes =
112                     new ArraySet<>(
113                             Objects.requireNonNull(
114                                     mBundle.getStringArrayList(DELETED_TYPES_FIELD)));
115         }
116         return Collections.unmodifiableSet(mDeletedTypes);
117     }
118 
119     /**
120      * Returns a {@link Set} of schema type that were migrated by the {@link
121      * AppSearchSession#setSchema} call.
122      *
123      * <p>A "migrated" type is a schema type that has triggered a {@link Migrator} instance to
124      * migrate documents of the schema type to another schema type, or to another version of the
125      * schema type.
126      *
127      * <p>If a document fails to be migrated, a {@link MigrationFailure} will be generated for that
128      * document.
129      *
130      * @see Migrator
131      */
132     @NonNull
getMigratedTypes()133     public Set<String> getMigratedTypes() {
134         if (mMigratedTypes == null) {
135             mMigratedTypes =
136                     new ArraySet<>(
137                             Objects.requireNonNull(
138                                     mBundle.getStringArrayList(MIGRATED_TYPES_FIELD)));
139         }
140         return Collections.unmodifiableSet(mMigratedTypes);
141     }
142 
143     /**
144      * Returns a {@link Set} of schema type whose new definitions set in the {@link
145      * AppSearchSession#setSchema} call were incompatible with the pre-existing schema.
146      *
147      * <p>If a {@link Migrator} is provided for this type and the migration is success triggered.
148      * The type will also appear in {@link #getMigratedTypes()}.
149      *
150      * @see SetSchemaRequest
151      * @see AppSearchSession#setSchema
152      * @see SetSchemaRequest.Builder#setForceOverride
153      */
154     @NonNull
getIncompatibleTypes()155     public Set<String> getIncompatibleTypes() {
156         if (mIncompatibleTypes == null) {
157             mIncompatibleTypes =
158                     new ArraySet<>(
159                             Objects.requireNonNull(
160                                     mBundle.getStringArrayList(INCOMPATIBLE_TYPES_FIELD)));
161         }
162         return Collections.unmodifiableSet(mIncompatibleTypes);
163     }
164 
165     /**
166      * Translates the {@link SetSchemaResponse}'s bundle to {@link Builder}.
167      *
168      * @hide
169      */
170     @NonNull
171     // TODO(b/179302942) change to Builder(mBundle) powered by mBundle.deepCopy
toBuilder()172     public Builder toBuilder() {
173         return new Builder()
174                 .addDeletedTypes(getDeletedTypes())
175                 .addIncompatibleTypes(getIncompatibleTypes())
176                 .addMigratedTypes(getMigratedTypes())
177                 .addMigrationFailures(mMigrationFailures);
178     }
179 
180     /** Builder for {@link SetSchemaResponse} objects. */
181     public static final class Builder {
182         private List<MigrationFailure> mMigrationFailures = new ArrayList<>();
183         private ArrayList<String> mDeletedTypes = new ArrayList<>();
184         private ArrayList<String> mMigratedTypes = new ArrayList<>();
185         private ArrayList<String> mIncompatibleTypes = new ArrayList<>();
186         private boolean mBuilt = false;
187 
188         /** Adds {@link MigrationFailure}s to the list of migration failures. */
189         @CanIgnoreReturnValue
190         @NonNull
addMigrationFailures( @onNull Collection<MigrationFailure> migrationFailures)191         public Builder addMigrationFailures(
192                 @NonNull Collection<MigrationFailure> migrationFailures) {
193             Objects.requireNonNull(migrationFailures);
194             resetIfBuilt();
195             mMigrationFailures.addAll(migrationFailures);
196             return this;
197         }
198 
199         /** Adds a {@link MigrationFailure} to the list of migration failures. */
200         @CanIgnoreReturnValue
201         @NonNull
addMigrationFailure(@onNull MigrationFailure migrationFailure)202         public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) {
203             Objects.requireNonNull(migrationFailure);
204             resetIfBuilt();
205             mMigrationFailures.add(migrationFailure);
206             return this;
207         }
208 
209         /** Adds {@code deletedTypes} to the list of deleted schema types. */
210         @CanIgnoreReturnValue
211         @NonNull
addDeletedTypes(@onNull Collection<String> deletedTypes)212         public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) {
213             Objects.requireNonNull(deletedTypes);
214             resetIfBuilt();
215             mDeletedTypes.addAll(deletedTypes);
216             return this;
217         }
218 
219         /** Adds one {@code deletedType} to the list of deleted schema types. */
220         @CanIgnoreReturnValue
221         @NonNull
addDeletedType(@onNull String deletedType)222         public Builder addDeletedType(@NonNull String deletedType) {
223             Objects.requireNonNull(deletedType);
224             resetIfBuilt();
225             mDeletedTypes.add(deletedType);
226             return this;
227         }
228 
229         /** Adds {@code incompatibleTypes} to the list of incompatible schema types. */
230         @CanIgnoreReturnValue
231         @NonNull
addIncompatibleTypes(@onNull Collection<String> incompatibleTypes)232         public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) {
233             Objects.requireNonNull(incompatibleTypes);
234             resetIfBuilt();
235             mIncompatibleTypes.addAll(incompatibleTypes);
236             return this;
237         }
238 
239         /** Adds one {@code incompatibleType} to the list of incompatible schema types. */
240         @CanIgnoreReturnValue
241         @NonNull
addIncompatibleType(@onNull String incompatibleType)242         public Builder addIncompatibleType(@NonNull String incompatibleType) {
243             Objects.requireNonNull(incompatibleType);
244             resetIfBuilt();
245             mIncompatibleTypes.add(incompatibleType);
246             return this;
247         }
248 
249         /** Adds {@code migratedTypes} to the list of migrated schema types. */
250         @CanIgnoreReturnValue
251         @NonNull
addMigratedTypes(@onNull Collection<String> migratedTypes)252         public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) {
253             Objects.requireNonNull(migratedTypes);
254             resetIfBuilt();
255             mMigratedTypes.addAll(migratedTypes);
256             return this;
257         }
258 
259         /** Adds one {@code migratedType} to the list of migrated schema types. */
260         @CanIgnoreReturnValue
261         @NonNull
addMigratedType(@onNull String migratedType)262         public Builder addMigratedType(@NonNull String migratedType) {
263             Objects.requireNonNull(migratedType);
264             resetIfBuilt();
265             mMigratedTypes.add(migratedType);
266             return this;
267         }
268 
269         /** Builds a {@link SetSchemaResponse} object. */
270         @NonNull
build()271         public SetSchemaResponse build() {
272             Bundle bundle = new Bundle();
273             bundle.putStringArrayList(INCOMPATIBLE_TYPES_FIELD, mIncompatibleTypes);
274             bundle.putStringArrayList(DELETED_TYPES_FIELD, mDeletedTypes);
275             bundle.putStringArrayList(MIGRATED_TYPES_FIELD, mMigratedTypes);
276             mBuilt = true;
277             // Avoid converting the potential thousands of MigrationFailures to Pracelable and
278             // back just for put in bundle. In platform, we should set MigrationFailures in
279             // AppSearchSession after we pass SetSchemaResponse via binder.
280             return new SetSchemaResponse(bundle, mMigrationFailures);
281         }
282 
resetIfBuilt()283         private void resetIfBuilt() {
284             if (mBuilt) {
285                 mMigrationFailures = new ArrayList<>(mMigrationFailures);
286                 mDeletedTypes = new ArrayList<>(mDeletedTypes);
287                 mMigratedTypes = new ArrayList<>(mMigratedTypes);
288                 mIncompatibleTypes = new ArrayList<>(mIncompatibleTypes);
289                 mBuilt = false;
290             }
291         }
292     }
293 
294     /**
295      * The class represents a post-migrated {@link GenericDocument} that failed to be saved by
296      * {@link AppSearchSession#setSchema}.
297      */
298     public static class MigrationFailure {
299         private static final String SCHEMA_TYPE_FIELD = "schemaType";
300         private static final String NAMESPACE_FIELD = "namespace";
301         private static final String DOCUMENT_ID_FIELD = "id";
302         private static final String ERROR_MESSAGE_FIELD = "errorMessage";
303         private static final String RESULT_CODE_FIELD = "resultCode";
304 
305         private final Bundle mBundle;
306 
307         /**
308          * Constructs a new {@link MigrationFailure}.
309          *
310          * @param namespace The namespace of the document which failed to be migrated.
311          * @param documentId The id of the document which failed to be migrated.
312          * @param schemaType The type of the document which failed to be migrated.
313          * @param failedResult The reason why the document failed to be indexed.
314          * @throws IllegalArgumentException if the provided {@code failedResult} was not a failure.
315          */
MigrationFailure( @onNull String namespace, @NonNull String documentId, @NonNull String schemaType, @NonNull AppSearchResult<?> failedResult)316         public MigrationFailure(
317                 @NonNull String namespace,
318                 @NonNull String documentId,
319                 @NonNull String schemaType,
320                 @NonNull AppSearchResult<?> failedResult) {
321             mBundle = new Bundle();
322             mBundle.putString(NAMESPACE_FIELD, Objects.requireNonNull(namespace));
323             mBundle.putString(DOCUMENT_ID_FIELD, Objects.requireNonNull(documentId));
324             mBundle.putString(SCHEMA_TYPE_FIELD, Objects.requireNonNull(schemaType));
325 
326             Objects.requireNonNull(failedResult);
327             Preconditions.checkArgument(
328                     !failedResult.isSuccess(), "failedResult was actually successful");
329             mBundle.putString(ERROR_MESSAGE_FIELD, failedResult.getErrorMessage());
330             mBundle.putInt(RESULT_CODE_FIELD, failedResult.getResultCode());
331         }
332 
MigrationFailure(@onNull Bundle bundle)333         MigrationFailure(@NonNull Bundle bundle) {
334             mBundle = Objects.requireNonNull(bundle);
335         }
336 
337         /**
338          * Returns the Bundle of the {@link MigrationFailure}.
339          *
340          * @hide
341          */
342         @NonNull
getBundle()343         public Bundle getBundle() {
344             return mBundle;
345         }
346 
347         /** Returns the namespace of the {@link GenericDocument} that failed to be migrated. */
348         @NonNull
getNamespace()349         public String getNamespace() {
350             return mBundle.getString(NAMESPACE_FIELD, /*defaultValue=*/ "");
351         }
352 
353         /** Returns the id of the {@link GenericDocument} that failed to be migrated. */
354         @NonNull
getDocumentId()355         public String getDocumentId() {
356             return mBundle.getString(DOCUMENT_ID_FIELD, /*defaultValue=*/ "");
357         }
358 
359         /** Returns the schema type of the {@link GenericDocument} that failed to be migrated. */
360         @NonNull
getSchemaType()361         public String getSchemaType() {
362             return mBundle.getString(SCHEMA_TYPE_FIELD, /*defaultValue=*/ "");
363         }
364 
365         /**
366          * Returns the {@link AppSearchResult} that indicates why the post-migration {@link
367          * GenericDocument} failed to be indexed.
368          */
369         @NonNull
getAppSearchResult()370         public AppSearchResult<Void> getAppSearchResult() {
371             return AppSearchResult.newFailedResult(
372                     mBundle.getInt(RESULT_CODE_FIELD),
373                     mBundle.getString(ERROR_MESSAGE_FIELD, /*defaultValue=*/ ""));
374         }
375 
376         @NonNull
377         @Override
toString()378         public String toString() {
379             return "MigrationFailure { schemaType: "
380                     + getSchemaType()
381                     + ", namespace: "
382                     + getNamespace()
383                     + ", documentId: "
384                     + getDocumentId()
385                     + ", appSearchResult: "
386                     + getAppSearchResult().toString()
387                     + "}";
388         }
389     }
390 }
391