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