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