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