com.google.cloud.firestore.DocumentSnapshot Maven / Gradle / Ivy
Show all versions of google-cloud-firestore Show documentation
/*
* Copyright 2017 Google LLC
*
* 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 com.google.cloud.firestore;
import com.google.api.core.InternalExtensionOnly;
import com.google.cloud.Timestamp;
import com.google.cloud.firestore.UserDataConverter.EncodingOptions;
import com.google.cloud.firestore.encoding.CustomClassMapper;
import com.google.common.base.Preconditions;
import com.google.firestore.v1.Document;
import com.google.firestore.v1.Value;
import com.google.firestore.v1.Write;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* A DocumentSnapshot contains data read from a document in a Firestore database. The data can be
* extracted with the {@link #getData()} or {@link #get(String)} methods.
*
* If the DocumentSnapshot points to a non-existing document, getData() and its corresponding
* methods will return null. You can always explicitly check for a document's existence by calling
* {@link #exists()}.
*
*
Subclassing Note: Firestore classes are not meant to be subclassed except for use in
* test mocks. Subclassing is not supported in production code and new SDK releases may break code
* that does so.
*/
@InternalExtensionOnly
public class DocumentSnapshot {
private final FirestoreRpcContext> rpcContext;
private final DocumentReference docRef;
@Nullable private final Map fields;
@Nullable private final Timestamp readTime;
@Nullable private final Timestamp updateTime;
@Nullable private final Timestamp createTime;
DocumentSnapshot(
FirestoreRpcContext> rpcContext,
DocumentReference docRef,
@Nullable Map fields,
@Nullable Timestamp readTime,
@Nullable Timestamp updateTime,
@Nullable Timestamp createTime) { // Elevated access level for mocking.
this.rpcContext = rpcContext;
this.docRef = docRef;
this.fields = fields;
this.readTime = readTime;
this.updateTime = updateTime;
this.createTime = createTime;
}
/**
* Returns the ID of the document contained in this snapshot.
*
* @return The id of the document.
*/
@Nonnull
public String getId() {
return docRef.getId();
}
static DocumentSnapshot fromObject(
FirestoreRpcContext> rpcContext,
DocumentReference docRef,
Map values,
EncodingOptions options) {
Map fields = new HashMap<>();
for (Map.Entry entry : values.entrySet()) {
Value encodedValue =
UserDataConverter.encodeValue(
FieldPath.of(entry.getKey()),
CustomClassMapper.convertToPlainJavaTypes(entry.getValue()),
options);
if (encodedValue != null) {
fields.put(entry.getKey(), encodedValue);
}
}
return new DocumentSnapshot(rpcContext, docRef, fields, null, null, null);
}
static DocumentSnapshot fromDocument(
FirestoreRpcContext> rpcContext, Timestamp readTime, Document document) {
return new DocumentSnapshot(
rpcContext,
new DocumentReference(rpcContext, ResourcePath.create(document.getName())),
document.getFieldsMap(),
readTime,
Timestamp.fromProto(document.getUpdateTime()),
Timestamp.fromProto(document.getCreateTime()));
}
static DocumentSnapshot fromMissing(
FirestoreRpcContext> rpcContext, DocumentReference documentReference, Timestamp readTime) {
return new DocumentSnapshot(rpcContext, documentReference, null, readTime, null, null);
}
/**
* Returns the time at which this snapshot was read.
*
* @return The read time of this snapshot.
*/
@Nullable
public Timestamp getReadTime() {
return readTime;
}
/**
* Returns the time at which this document was last updated. Returns null for non-existing
* documents.
*
* @return The last time the document in the snapshot was updated. Null if the document doesn't
* exist.
*/
@Nullable
public Timestamp getUpdateTime() {
return updateTime;
}
/**
* Returns the time at which this document was created. Returns null for non-existing documents.
*
* @return The last time the document in the snapshot was created. Null if the document doesn't
* exist.
*/
@Nullable
public Timestamp getCreateTime() {
return createTime;
}
/**
* Returns whether or not the field exists in the document. Returns false if the document does not
* exist.
*
* @return whether the document existed in this snapshot.
*/
public boolean exists() {
return fields != null;
}
/**
* Returns the fields of the document as a Map or null if the document doesn't exist. Field values
* will be converted to their native Java representation.
*
* @return The fields of the document as a Map or null if the document doesn't exist.
*/
@Nullable
public Map getData() {
if (fields == null) {
return null;
}
Map decodedFields = new HashMap<>();
for (Map.Entry entry : fields.entrySet()) {
Object decodedValue = UserDataConverter.decodeValue(rpcContext, entry.getValue());
decodedFields.put(entry.getKey(), decodedValue);
}
return decodedFields;
}
/**
* Returns the contents of the document converted to a POJO or null if the document doesn't exist.
*
* @param valueType The Java class to create
* @return The contents of the document in an object of type T or null if the document doesn't
* exist.
*/
@Nullable
public T toObject(@Nonnull Class valueType) {
Map data = getData();
return data == null ? null : CustomClassMapper.convertToCustomClass(data, valueType, docRef);
}
/**
* Returns whether or not the field exists in the document. Returns false if the document does not
* exist.
*
* @param field the path to the field.
* @return true iff the field exists.
*/
public boolean contains(@Nonnull String field) {
return contains(FieldPath.fromDotSeparatedString(field));
}
/**
* Returns whether or not the field exists in the document. Returns false if the document does not
* exist.
*
* @param fieldPath the path to the field.
* @return true iff the field exists.
*/
public boolean contains(@Nonnull FieldPath fieldPath) {
return this.extractField(fieldPath) != null;
}
/**
* Returns the value at the field or null if the field doesn't exist.
*
* @param field The path to the field.
* @return The value at the given field or null.
*/
@Nullable
public Object get(@Nonnull String field) {
return get(FieldPath.fromDotSeparatedString(field));
}
/**
* Returns the value at the field, converted to a POJO, or null if the field or document doesn't
* exist.
*
* @param field The path to the field
* @param valueType The Java class to convert the field value to.
* @return The value at the given field or null.
*/
@Nullable
public T get(@Nonnull String field, @Nonnull Class valueType) {
return get(FieldPath.fromDotSeparatedString(field), valueType);
}
/**
* Returns the value at the field or null if the field doesn't exist.
*
* @param fieldPath The path to the field.
* @return The value at the given field or null.
*/
@Nullable
public Object get(@Nonnull FieldPath fieldPath) {
Value value = extractField(fieldPath);
if (value == null) {
return null;
}
return UserDataConverter.decodeValue(rpcContext, value);
}
/**
* Returns the value at the field, converted to a POJO, or null if the field or document doesn't
* exist.
*
* @param fieldPath The path to the field
* @param valueType The Java class to convert the field value to.
* @return The value at the given field or null.
*/
@Nullable
public T get(@Nonnull FieldPath fieldPath, Class valueType) {
Object data = get(fieldPath);
return data == null ? null : CustomClassMapper.convertToCustomClass(data, valueType, docRef);
}
/** Returns the Value Proto at 'fieldPath'. Returns null if the field was not found. */
@Nullable
Value extractField(@Nonnull FieldPath fieldPath) {
Value value = null;
if (fields != null) {
Iterator components = fieldPath.getSegments().iterator();
value = fields.get(components.next());
while (value != null && components.hasNext()) {
if (value.getValueTypeCase() != Value.ValueTypeCase.MAP_VALUE) {
return null;
}
value = value.getMapValue().getFieldsOrDefault(components.next(), null);
}
}
return value;
}
/**
* Returns the value of the field as a boolean.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a Boolean.
* @return The value of the field.
*/
@Nullable
public Boolean getBoolean(@Nonnull String field) {
return (Boolean) get(field);
}
/**
* Returns the value of the field as a double.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a Number.
* @return The value of the field.
*/
@Nullable
public Double getDouble(@Nonnull String field) {
Number number = (Number) get(field);
return number == null ? null : number.doubleValue();
}
/**
* Returns the value of the field as a String.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a String.
* @return The value of the field.
*/
@Nullable
public String getString(@Nonnull String field) {
return (String) get(field);
}
/**
* Returns the value of the field as a long.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a Number.
* @return The value of the field.
*/
@Nullable
public Long getLong(@Nonnull String field) {
Number number = (Number) get(field);
return number == null ? null : number.longValue();
}
/**
* Returns the value of the field as a Date.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a Date.
* @return The value of the field.
*/
@Nullable
public Date getDate(@Nonnull String field) {
Timestamp timestamp = getTimestamp(field);
return timestamp == null ? null : timestamp.toDate();
}
/**
* Returns the value of the field as a {@link Timestamp}.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a Date.
* @return The value of the field.
*/
@Nullable
public Timestamp getTimestamp(@Nonnull String field) {
return (Timestamp) get(field);
}
/**
* Returns the value of the field as a Blob.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a Blob.
* @return The value of the field.
*/
@Nullable
public Blob getBlob(@Nonnull String field) {
return (Blob) get(field);
}
/**
* Returns the value of the field as a GeoPoint.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a GeoPoint.
* @return The value of the field.
*/
@Nullable
public GeoPoint getGeoPoint(@Nonnull String field) {
return (GeoPoint) get(field);
}
/**
* Returns the value of the field as a VectorValue.
*
* @param field The path to the field.
* @throws RuntimeException if the value is not a VectorValue.
* @return The value of the field.
*/
@Nullable
public VectorValue getVectorValue(@Nonnull String field) {
return (VectorValue) get(field);
}
/**
* Gets the reference to the document.
*
* @return The reference to the document.
*/
@Nonnull
public DocumentReference getReference() {
return docRef;
}
/** Checks whether this DocumentSnapshot contains any fields. */
boolean isEmpty() {
return fields == null || fields.isEmpty();
}
Map getProtoFields() {
return fields;
}
Write.Builder toPb() {
Preconditions.checkState(exists(), "Can't call toDocument() on a document that doesn't exist");
Write.Builder write = Write.newBuilder();
Document.Builder document = write.getUpdateBuilder();
document.setName(docRef.getName());
document.putAllFields(fields);
return write;
}
Document.Builder toDocumentPb() {
Preconditions.checkState(exists(), "Can't call toDocument() on a document that doesn't exist");
Document.Builder document = Document.newBuilder();
return document
.setName(docRef.getName())
.putAllFields(fields)
.setCreateTime(createTime.toProto())
.setUpdateTime(updateTime.toProto());
}
/**
* Returns true if the document's data and path in this DocumentSnapshot equals the provided
* snapshot.
*
* @param obj The object to compare against.
* @return Whether this DocumentSnapshot is equal to the provided object.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || !(obj instanceof DocumentSnapshot)) {
return false;
}
DocumentSnapshot that = (DocumentSnapshot) obj;
return Objects.equals(rpcContext, that.rpcContext)
&& Objects.equals(docRef, that.docRef)
&& Objects.equals(fields, that.fields);
}
@Override
public int hashCode() {
return Objects.hash(rpcContext, docRef, fields);
}
@Override
public String toString() {
return String.format(
"%s{doc=%s, fields=%s, readTime=%s, updateTime=%s, createTime=%s}",
getClass().getSimpleName(), docRef, fields, readTime, updateTime, createTime);
}
}