/* * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.app.appsearch; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appsearch.annotation.CanIgnoreReturnValue; import android.app.appsearch.flags.Flags; import android.app.appsearch.safeparcel.AbstractSafeParcelable; import android.app.appsearch.safeparcel.SafeParcelable; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; /** The response class of {@link AppSearchSession#setSchema} */ @SafeParcelable.Class(creator = "SetSchemaResponseCreator") @SuppressWarnings("HiddenSuperclass") public final class SetSchemaResponse extends AbstractSafeParcelable { @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) @NonNull public static final Parcelable.Creator CREATOR = new SetSchemaResponseCreator(); @Field(id = 1) final List mDeletedTypes; @Field(id = 2) final List mIncompatibleTypes; @Field(id = 3) final List mMigratedTypes; /** * The migrationFailures won't be saved as a SafeParcelable field. Since: * *
    *
  • {@link MigrationFailure} is generated in {@link AppSearchSession} which will be the SDK * side in platform. We don't need to pass it from service side via binder as a part of * {@link SetSchemaResponse}. *
  • Writing multiple {@link MigrationFailure}s to SafeParcelable in {@link Builder} and * then back in constructor will be a huge waste. *
*/ private final List mMigrationFailures; /** * Cache of the inflated deleted schema types. Comes from inflating mDeletedTypes at first use */ @Nullable private Set mDeletedTypesCached; /** * Cache of the inflated migrated schema types. Comes from inflating mMigratedTypes at first * use. */ @Nullable private Set mMigratedTypesCached; /** * Cache of the inflated incompatible schema types. Comes from inflating mIncompatibleTypes at * first use. */ @Nullable private Set mIncompatibleTypesCached; @Constructor SetSchemaResponse( @Param(id = 1) @NonNull List deletedTypes, @Param(id = 2) @NonNull List incompatibleTypes, @Param(id = 3) @NonNull List migratedTypes) { mDeletedTypes = deletedTypes; mIncompatibleTypes = incompatibleTypes; mMigratedTypes = migratedTypes; mMigrationFailures = Collections.emptyList(); } SetSchemaResponse( @NonNull List deletedTypes, @NonNull List incompatibleTypes, @NonNull List migratedTypes, @NonNull List migrationFailures) { mDeletedTypes = deletedTypes; mIncompatibleTypes = incompatibleTypes; mMigratedTypes = migratedTypes; mMigrationFailures = Objects.requireNonNull(migrationFailures); } /** * Returns a {@link List} of all failed {@link MigrationFailure}. * *

A {@link MigrationFailure} will be generated if the system trying to save a post-migrated * {@link GenericDocument} but fail. * *

{@link MigrationFailure} contains the namespace, id and schemaType of the post-migrated * {@link GenericDocument} and the error reason. Mostly it will be mismatch the schema it * migrated to. */ @NonNull public List getMigrationFailures() { return Collections.unmodifiableList(mMigrationFailures); } /** * Returns a {@link Set} of deleted schema types. * *

A "deleted" type is a schema type that was previously a part of the database schema but * was not present in the {@link SetSchemaRequest} object provided in the {@link * AppSearchSession#setSchema} call. * *

Documents for a deleted type are removed from the database. */ @NonNull public Set getDeletedTypes() { if (mDeletedTypesCached == null) { mDeletedTypesCached = new ArraySet<>(Objects.requireNonNull(mDeletedTypes)); } return Collections.unmodifiableSet(mDeletedTypesCached); } /** * Returns a {@link Set} of schema type that were migrated by the {@link * AppSearchSession#setSchema} call. * *

A "migrated" type is a schema type that has triggered a {@link Migrator} instance to * migrate documents of the schema type to another schema type, or to another version of the * schema type. * *

If a document fails to be migrated, a {@link MigrationFailure} will be generated for that * document. * * @see Migrator */ @NonNull public Set getMigratedTypes() { if (mMigratedTypesCached == null) { mMigratedTypesCached = new ArraySet<>(Objects.requireNonNull(mMigratedTypes)); } return Collections.unmodifiableSet(mMigratedTypesCached); } /** * Returns a {@link Set} of schema type whose new definitions set in the {@link * AppSearchSession#setSchema} call were incompatible with the pre-existing schema. * *

If a {@link Migrator} is provided for this type and the migration is success triggered. * The type will also appear in {@link #getMigratedTypes()}. * * @see SetSchemaRequest * @see AppSearchSession#setSchema * @see SetSchemaRequest.Builder#setForceOverride */ @NonNull public Set getIncompatibleTypes() { if (mIncompatibleTypesCached == null) { mIncompatibleTypesCached = new ArraySet<>(Objects.requireNonNull(mIncompatibleTypes)); } return Collections.unmodifiableSet(mIncompatibleTypesCached); } /** Builder for {@link SetSchemaResponse} objects. */ public static final class Builder { private List mMigrationFailures = new ArrayList<>(); private ArrayList mDeletedTypes = new ArrayList<>(); private ArrayList mMigratedTypes = new ArrayList<>(); private ArrayList mIncompatibleTypes = new ArrayList<>(); private boolean mBuilt = false; /** * Creates a new {@link SetSchemaResponse.Builder} from the given SetSchemaResponse. * * @hide */ public Builder(@NonNull SetSchemaResponse setSchemaResponse) { Objects.requireNonNull(setSchemaResponse); mDeletedTypes.addAll(setSchemaResponse.getDeletedTypes()); mIncompatibleTypes.addAll(setSchemaResponse.getIncompatibleTypes()); mMigratedTypes.addAll(setSchemaResponse.getMigratedTypes()); mMigrationFailures.addAll(setSchemaResponse.getMigrationFailures()); } /** Create a {@link Builder} object} */ public Builder() {} /** Adds {@link MigrationFailure}s to the list of migration failures. */ @CanIgnoreReturnValue @NonNull public Builder addMigrationFailures( @NonNull Collection migrationFailures) { Objects.requireNonNull(migrationFailures); resetIfBuilt(); mMigrationFailures.addAll(migrationFailures); return this; } /** Adds a {@link MigrationFailure} to the list of migration failures. */ @CanIgnoreReturnValue @NonNull public Builder addMigrationFailure(@NonNull MigrationFailure migrationFailure) { Objects.requireNonNull(migrationFailure); resetIfBuilt(); mMigrationFailures.add(migrationFailure); return this; } /** Adds {@code deletedTypes} to the list of deleted schema types. */ @CanIgnoreReturnValue @NonNull public Builder addDeletedTypes(@NonNull Collection deletedTypes) { Objects.requireNonNull(deletedTypes); resetIfBuilt(); mDeletedTypes.addAll(deletedTypes); return this; } /** Adds one {@code deletedType} to the list of deleted schema types. */ @CanIgnoreReturnValue @NonNull public Builder addDeletedType(@NonNull String deletedType) { Objects.requireNonNull(deletedType); resetIfBuilt(); mDeletedTypes.add(deletedType); return this; } /** Adds {@code incompatibleTypes} to the list of incompatible schema types. */ @CanIgnoreReturnValue @NonNull public Builder addIncompatibleTypes(@NonNull Collection incompatibleTypes) { Objects.requireNonNull(incompatibleTypes); resetIfBuilt(); mIncompatibleTypes.addAll(incompatibleTypes); return this; } /** Adds one {@code incompatibleType} to the list of incompatible schema types. */ @CanIgnoreReturnValue @NonNull public Builder addIncompatibleType(@NonNull String incompatibleType) { Objects.requireNonNull(incompatibleType); resetIfBuilt(); mIncompatibleTypes.add(incompatibleType); return this; } /** Adds {@code migratedTypes} to the list of migrated schema types. */ @CanIgnoreReturnValue @NonNull public Builder addMigratedTypes(@NonNull Collection migratedTypes) { Objects.requireNonNull(migratedTypes); resetIfBuilt(); mMigratedTypes.addAll(migratedTypes); return this; } /** Adds one {@code migratedType} to the list of migrated schema types. */ @CanIgnoreReturnValue @NonNull public Builder addMigratedType(@NonNull String migratedType) { Objects.requireNonNull(migratedType); resetIfBuilt(); mMigratedTypes.add(migratedType); return this; } /** Builds a {@link SetSchemaResponse} object. */ @NonNull public SetSchemaResponse build() { mBuilt = true; // Avoid converting the potential thousands of MigrationFailures to Pracelable and // back just for put in bundle. In platform, we should set MigrationFailures in // AppSearchSession after we pass SetSchemaResponse via binder. return new SetSchemaResponse( mDeletedTypes, mIncompatibleTypes, mMigratedTypes, mMigrationFailures); } private void resetIfBuilt() { if (mBuilt) { mMigrationFailures = new ArrayList<>(mMigrationFailures); mDeletedTypes = new ArrayList<>(mDeletedTypes); mMigratedTypes = new ArrayList<>(mMigratedTypes); mIncompatibleTypes = new ArrayList<>(mIncompatibleTypes); mBuilt = false; } } } @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) @Override public void writeToParcel(@NonNull Parcel dest, int flags) { SetSchemaResponseCreator.writeToParcel(this, dest, flags); } /** * The class represents a post-migrated {@link GenericDocument} that failed to be saved by * {@link AppSearchSession#setSchema}. */ @SafeParcelable.Class(creator = "MigrationFailureCreator") @SuppressWarnings("HiddenSuperclass") public static class MigrationFailure extends AbstractSafeParcelable { @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) @NonNull public static final Parcelable.Creator CREATOR = new MigrationFailureCreator(); @Field(id = 1, getter = "getNamespace") private final String mNamespace; @Field(id = 2, getter = "getDocumentId") private final String mDocumentId; @Field(id = 3, getter = "getSchemaType") private final String mSchemaType; @Field(id = 4) @Nullable final String mErrorMessage; @Field(id = 5) final int mResultCode; @Constructor MigrationFailure( @Param(id = 1) @NonNull 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) { mNamespace = namespace; mDocumentId = documentId; mSchemaType = schemaType; mErrorMessage = errorMessage; mResultCode = resultCode; } /** * Constructs a new {@link MigrationFailure}. * * @param namespace The namespace of the document which failed to be migrated. * @param documentId The id of the document which failed to be migrated. * @param schemaType The type of the document which failed to be migrated. * @param failedResult The reason why the document failed to be indexed. * @throws IllegalArgumentException if the provided {@code failedResult} was not a failure. */ public MigrationFailure( @NonNull String namespace, @NonNull String documentId, @NonNull String schemaType, @NonNull AppSearchResult failedResult) { mNamespace = namespace; mDocumentId = documentId; mSchemaType = schemaType; Objects.requireNonNull(failedResult); Preconditions.checkArgument( !failedResult.isSuccess(), "failedResult was actually successful"); mErrorMessage = failedResult.getErrorMessage(); mResultCode = failedResult.getResultCode(); } /** Returns the namespace of the {@link GenericDocument} that failed to be migrated. */ @NonNull public String getNamespace() { return mNamespace; } /** Returns the id of the {@link GenericDocument} that failed to be migrated. */ @NonNull public String getDocumentId() { return mDocumentId; } /** Returns the schema type of the {@link GenericDocument} that failed to be migrated. */ @NonNull public String getSchemaType() { return mSchemaType; } /** * Returns the {@link AppSearchResult} that indicates why the post-migration {@link * GenericDocument} failed to be indexed. */ @NonNull public AppSearchResult getAppSearchResult() { return AppSearchResult.newFailedResult(mResultCode, mErrorMessage); } @NonNull @Override public String toString() { return "MigrationFailure { schemaType: " + getSchemaType() + ", namespace: " + getNamespace() + ", documentId: " + getDocumentId() + ", appSearchResult: " + getAppSearchResult().toString() + "}"; } @FlaggedApi(Flags.FLAG_ENABLE_SAFE_PARCELABLE_2) @Override public void writeToParcel(@NonNull Parcel dest, int flags) { MigrationFailureCreator.writeToParcel(this, dest, flags); } } }