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 @SuppressWarnings("HiddenSuperclass") 42 public final class SetSchemaResponse extends AbstractSafeParcelable { 43 44 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 45 @NonNull 46 public static final 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 @Nullable private Set<String> mDeletedTypesCached; 75 76 /** 77 * Cache of the inflated migrated schema types. Comes from inflating mMigratedTypes at first 78 * use. 79 */ 80 @Nullable private Set<String> mMigratedTypesCached; 81 82 /** 83 * Cache of the inflated incompatible schema types. Comes from inflating mIncompatibleTypes at 84 * first use. 85 */ 86 @Nullable private 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 */ 120 @NonNull getMigrationFailures()121 public List<MigrationFailure> getMigrationFailures() { 122 return Collections.unmodifiableList(mMigrationFailures); 123 } 124 125 /** 126 * Returns a {@link Set} of deleted schema types. 127 * 128 * <p>A "deleted" type is a schema type that was previously a part of the database schema but 129 * was not present in the {@link SetSchemaRequest} object provided in the {@link 130 * AppSearchSession#setSchema} call. 131 * 132 * <p>Documents for a deleted type are removed from the database. 133 */ 134 @NonNull getDeletedTypes()135 public Set<String> getDeletedTypes() { 136 if (mDeletedTypesCached == null) { 137 mDeletedTypesCached = new ArraySet<>(Objects.requireNonNull(mDeletedTypes)); 138 } 139 return Collections.unmodifiableSet(mDeletedTypesCached); 140 } 141 142 /** 143 * Returns a {@link Set} of schema type that were migrated by the {@link 144 * AppSearchSession#setSchema} call. 145 * 146 * <p>A "migrated" type is a schema type that has triggered a {@link Migrator} instance to 147 * migrate documents of the schema type to another schema type, or to another version of the 148 * schema type. 149 * 150 * <p>If a document fails to be migrated, a {@link MigrationFailure} will be generated for that 151 * document. 152 * 153 * @see Migrator 154 */ 155 @NonNull getMigratedTypes()156 public Set<String> getMigratedTypes() { 157 if (mMigratedTypesCached == null) { 158 mMigratedTypesCached = new ArraySet<>(Objects.requireNonNull(mMigratedTypes)); 159 } 160 return Collections.unmodifiableSet(mMigratedTypesCached); 161 } 162 163 /** 164 * Returns a {@link Set} of schema type whose new definitions set in the {@link 165 * AppSearchSession#setSchema} call were incompatible with the pre-existing schema. 166 * 167 * <p>If a {@link Migrator} is provided for this type and the migration is success triggered. 168 * The type will also appear in {@link #getMigratedTypes()}. 169 * 170 * @see SetSchemaRequest 171 * @see AppSearchSession#setSchema 172 * @see SetSchemaRequest.Builder#setForceOverride 173 */ 174 @NonNull getIncompatibleTypes()175 public Set<String> getIncompatibleTypes() { 176 if (mIncompatibleTypesCached == null) { 177 mIncompatibleTypesCached = new ArraySet<>(Objects.requireNonNull(mIncompatibleTypes)); 178 } 179 return Collections.unmodifiableSet(mIncompatibleTypesCached); 180 } 181 182 /** Builder for {@link SetSchemaResponse} objects. */ 183 public static final class Builder { 184 private List<MigrationFailure> mMigrationFailures = new ArrayList<>(); 185 private ArrayList<String> mDeletedTypes = new ArrayList<>(); 186 private ArrayList<String> mMigratedTypes = new ArrayList<>(); 187 private ArrayList<String> mIncompatibleTypes = new ArrayList<>(); 188 private boolean mBuilt = false; 189 190 /** 191 * Creates a new {@link SetSchemaResponse.Builder} from the given SetSchemaResponse. 192 * 193 * @hide 194 */ Builder(@onNull SetSchemaResponse setSchemaResponse)195 public Builder(@NonNull SetSchemaResponse setSchemaResponse) { 196 Objects.requireNonNull(setSchemaResponse); 197 mDeletedTypes.addAll(setSchemaResponse.getDeletedTypes()); 198 mIncompatibleTypes.addAll(setSchemaResponse.getIncompatibleTypes()); 199 mMigratedTypes.addAll(setSchemaResponse.getMigratedTypes()); 200 mMigrationFailures.addAll(setSchemaResponse.getMigrationFailures()); 201 } 202 203 /** Create a {@link Builder} object} */ Builder()204 public Builder() {} 205 206 /** Adds {@link MigrationFailure}s to the list of migration failures. */ 207 @CanIgnoreReturnValue 208 @NonNull addMigrationFailures( @onNull Collection<MigrationFailure> migrationFailures)209 public Builder addMigrationFailures( 210 @NonNull Collection<MigrationFailure> migrationFailures) { 211 Objects.requireNonNull(migrationFailures); 212 resetIfBuilt(); 213 mMigrationFailures.addAll(migrationFailures); 214 return this; 215 } 216 217 /** Adds a {@link MigrationFailure} to the list of migration failures. */ 218 @CanIgnoreReturnValue 219 @NonNull addMigrationFailure(@onNull MigrationFailure migrationFailure)220 public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) { 221 Objects.requireNonNull(migrationFailure); 222 resetIfBuilt(); 223 mMigrationFailures.add(migrationFailure); 224 return this; 225 } 226 227 /** Adds {@code deletedTypes} to the list of deleted schema types. */ 228 @CanIgnoreReturnValue 229 @NonNull addDeletedTypes(@onNull Collection<String> deletedTypes)230 public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) { 231 Objects.requireNonNull(deletedTypes); 232 resetIfBuilt(); 233 mDeletedTypes.addAll(deletedTypes); 234 return this; 235 } 236 237 /** Adds one {@code deletedType} to the list of deleted schema types. */ 238 @CanIgnoreReturnValue 239 @NonNull addDeletedType(@onNull String deletedType)240 public Builder addDeletedType(@NonNull String deletedType) { 241 Objects.requireNonNull(deletedType); 242 resetIfBuilt(); 243 mDeletedTypes.add(deletedType); 244 return this; 245 } 246 247 /** Adds {@code incompatibleTypes} to the list of incompatible schema types. */ 248 @CanIgnoreReturnValue 249 @NonNull addIncompatibleTypes(@onNull Collection<String> incompatibleTypes)250 public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) { 251 Objects.requireNonNull(incompatibleTypes); 252 resetIfBuilt(); 253 mIncompatibleTypes.addAll(incompatibleTypes); 254 return this; 255 } 256 257 /** Adds one {@code incompatibleType} to the list of incompatible schema types. */ 258 @CanIgnoreReturnValue 259 @NonNull addIncompatibleType(@onNull String incompatibleType)260 public Builder addIncompatibleType(@NonNull String incompatibleType) { 261 Objects.requireNonNull(incompatibleType); 262 resetIfBuilt(); 263 mIncompatibleTypes.add(incompatibleType); 264 return this; 265 } 266 267 /** Adds {@code migratedTypes} to the list of migrated schema types. */ 268 @CanIgnoreReturnValue 269 @NonNull addMigratedTypes(@onNull Collection<String> migratedTypes)270 public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) { 271 Objects.requireNonNull(migratedTypes); 272 resetIfBuilt(); 273 mMigratedTypes.addAll(migratedTypes); 274 return this; 275 } 276 277 /** Adds one {@code migratedType} to the list of migrated schema types. */ 278 @CanIgnoreReturnValue 279 @NonNull addMigratedType(@onNull String migratedType)280 public Builder addMigratedType(@NonNull String migratedType) { 281 Objects.requireNonNull(migratedType); 282 resetIfBuilt(); 283 mMigratedTypes.add(migratedType); 284 return this; 285 } 286 287 /** Builds a {@link SetSchemaResponse} object. */ 288 @NonNull build()289 public SetSchemaResponse build() { 290 mBuilt = true; 291 // Avoid converting the potential thousands of MigrationFailures to Pracelable and 292 // back just for put in bundle. In platform, we should set MigrationFailures in 293 // AppSearchSession after we pass SetSchemaResponse via binder. 294 return new SetSchemaResponse( 295 mDeletedTypes, mIncompatibleTypes, mMigratedTypes, mMigrationFailures); 296 } 297 resetIfBuilt()298 private void resetIfBuilt() { 299 if (mBuilt) { 300 mMigrationFailures = new ArrayList<>(mMigrationFailures); 301 mDeletedTypes = new ArrayList<>(mDeletedTypes); 302 mMigratedTypes = new ArrayList<>(mMigratedTypes); 303 mIncompatibleTypes = new ArrayList<>(mIncompatibleTypes); 304 mBuilt = false; 305 } 306 } 307 } 308 309 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 310 @Override writeToParcel(@onNull Parcel dest, int flags)311 public void writeToParcel(@NonNull Parcel dest, int flags) { 312 SetSchemaResponseCreator.writeToParcel(this, dest, flags); 313 } 314 315 /** 316 * The class represents a post-migrated {@link GenericDocument} that failed to be saved by 317 * {@link AppSearchSession#setSchema}. 318 */ 319 @SafeParcelable.Class(creator = "MigrationFailureCreator") 320 @SuppressWarnings("HiddenSuperclass") 321 public static class MigrationFailure extends AbstractSafeParcelable { 322 323 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 324 @NonNull 325 public static final Parcelable.Creator<MigrationFailure> CREATOR = 326 new MigrationFailureCreator(); 327 328 @Field(id = 1, getter = "getNamespace") 329 private final String mNamespace; 330 331 @Field(id = 2, getter = "getDocumentId") 332 private final String mDocumentId; 333 334 @Field(id = 3, getter = "getSchemaType") 335 private final String mSchemaType; 336 337 @Field(id = 4) 338 @Nullable 339 final String mErrorMessage; 340 341 @Field(id = 5) 342 final int mResultCode; 343 344 @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)345 MigrationFailure( 346 @Param(id = 1) @NonNull String namespace, 347 @Param(id = 2) @NonNull String documentId, 348 @Param(id = 3) @NonNull String schemaType, 349 @Param(id = 4) @Nullable String errorMessage, 350 @Param(id = 5) int resultCode) { 351 mNamespace = namespace; 352 mDocumentId = documentId; 353 mSchemaType = schemaType; 354 mErrorMessage = errorMessage; 355 mResultCode = resultCode; 356 } 357 358 /** 359 * Constructs a new {@link MigrationFailure}. 360 * 361 * @param namespace The namespace of the document which failed to be migrated. 362 * @param documentId The id of the document which failed to be migrated. 363 * @param schemaType The type of the document which failed to be migrated. 364 * @param failedResult The reason why the document failed to be indexed. 365 * @throws IllegalArgumentException if the provided {@code failedResult} was not a failure. 366 */ MigrationFailure( @onNull String namespace, @NonNull String documentId, @NonNull String schemaType, @NonNull AppSearchResult<?> failedResult)367 public MigrationFailure( 368 @NonNull String namespace, 369 @NonNull String documentId, 370 @NonNull String schemaType, 371 @NonNull AppSearchResult<?> failedResult) { 372 mNamespace = namespace; 373 mDocumentId = documentId; 374 mSchemaType = schemaType; 375 376 Objects.requireNonNull(failedResult); 377 Preconditions.checkArgument( 378 !failedResult.isSuccess(), "failedResult was actually successful"); 379 mErrorMessage = failedResult.getErrorMessage(); 380 mResultCode = failedResult.getResultCode(); 381 } 382 383 /** Returns the namespace of the {@link GenericDocument} that failed to be migrated. */ 384 @NonNull getNamespace()385 public String getNamespace() { 386 return mNamespace; 387 } 388 389 /** Returns the id of the {@link GenericDocument} that failed to be migrated. */ 390 @NonNull getDocumentId()391 public String getDocumentId() { 392 return mDocumentId; 393 } 394 395 /** Returns the schema type of the {@link GenericDocument} that failed to be migrated. */ 396 @NonNull getSchemaType()397 public String getSchemaType() { 398 return mSchemaType; 399 } 400 401 /** 402 * Returns the {@link AppSearchResult} that indicates why the post-migration {@link 403 * GenericDocument} failed to be indexed. 404 */ 405 @NonNull getAppSearchResult()406 public AppSearchResult<Void> getAppSearchResult() { 407 return AppSearchResult.newFailedResult(mResultCode, mErrorMessage); 408 } 409 410 @NonNull 411 @Override toString()412 public String toString() { 413 return "MigrationFailure { schemaType: " 414 + getSchemaType() 415 + ", namespace: " 416 + getNamespace() 417 + ", documentId: " 418 + getDocumentId() 419 + ", appSearchResult: " 420 + getAppSearchResult().toString() 421 + "}"; 422 } 423 424 @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) 425 @Override writeToParcel(@onNull Parcel dest, int flags)426 public void writeToParcel(@NonNull Parcel dest, int flags) { 427 MigrationFailureCreator.writeToParcel(this, dest, flags); 428 } 429 } 430 } 431