aboutsummaryrefslogtreecommitdiffstats
path: root/document/src/main/java/com/yahoo/document/datatypes/ReferenceFieldValue.java
blob: c03ba1feeedea0720d69d5ab49af77bc09f2237b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.document.datatypes;

import com.yahoo.document.DataType;
import com.yahoo.document.DocumentId;
import com.yahoo.document.Field;
import com.yahoo.document.ReferenceDataType;
import com.yahoo.document.serialization.FieldReader;
import com.yahoo.document.serialization.FieldWriter;
import com.yahoo.document.serialization.XmlStream;
import com.yahoo.vespa.objects.Ids;

import java.util.Objects;
import java.util.Optional;

/**
 * <p>A reference field value allows search queries to access fields in other document instances
 * as if they were fields natively stored within the searched document. This allows modelling
 * one-to-many relations such as a parent document with many children containing references
 * back to the parent.</p>
 *
 * <p>Each <code>ReferenceFieldValue</code> may contain a single document ID which specifies the
 * instance the field should refer to. This document ID must have a type matching that of the
 * reference data type of the field itself.</p>
 *
 * <p>Note that references are not polymorphic. This means that if you have a document type
 * "foo" inheriting "bar", you cannot have a <code>reference&lt;bar&gt;</code> field containing
 * a document ID for a "foo" document.</p>
 *
 * @author vekterli
 */
public class ReferenceFieldValue extends FieldValue {

    // Magic number for Identifiable, see document/util/identifiable.h
    public static final int classId = registerClass(Ids.document + 39, ReferenceFieldValue.class);

    private final ReferenceDataType referenceType;
    private Optional<DocumentId> documentId;

    /**
     * Creates an empty reference of the provided reference type.
     * @param referenceType reference target type
     */
    public ReferenceFieldValue(ReferenceDataType referenceType) {
        this.referenceType = referenceType;
        this.documentId = Optional.empty();
    }

    /**
     * Creates a reference pointing to a particular document instance in the document
     * type referenced by <code>referenceType</code>.
     *
     * @param referenceType reference target type
     * @param documentId document ID of the same document type as that given by <code>referenceType</code>
     * @throws IllegalArgumentException if documentId is not of the expected document type
     */
    public ReferenceFieldValue(ReferenceDataType referenceType, DocumentId documentId) {
        requireIdOfMatchingType(referenceType, documentId);
        this.referenceType = referenceType;
        this.documentId = Optional.of(documentId);
    }

    public static ReferenceFieldValue createEmptyWithType(ReferenceDataType referenceType) {
        return new ReferenceFieldValue(referenceType);
    }

    private static void requireIdOfMatchingType(ReferenceDataType referenceType, DocumentId id) {
        String expectedTypeName = referenceType.getTargetType().getName();
        if (!id.getDocType().equals(expectedTypeName)) {
            throw new IllegalArgumentException(String.format(
                    "Can't assign document ID '%s' (of type '%s') to reference of document type '%s'",
                    id, id.getDocType(), expectedTypeName));
        }
    }

    @Override
    public DataType getDataType() {
        return referenceType;
    }

    public Optional<DocumentId> getDocumentId() {
        return documentId;
    }

    public void setDocumentId(DocumentId documentId) {
        requireIdOfMatchingType(referenceType, documentId);
        this.documentId = Optional.of(documentId);
    }

    @Override
    @Deprecated
    public void printXml(XmlStream xml) { }

    @Override
    public void clear() {
        this.documentId = Optional.empty();
    }

    @Override
    public void assign(Object o) {
        if (o == null) {
            clear();
        } else if (o instanceof ReferenceFieldValue) {
            assignFromFieldValue((ReferenceFieldValue) o);
        } else if (o instanceof DocumentId) {
            setDocumentId((DocumentId) o);
        } else {
            throw new IllegalArgumentException(String.format(
                    "Can't assign value of type '%s' to field of type '%s'. Expected value of type '%s'",
                    o.getClass().getName(), getClass().getName(), DocumentId.class.getName()));
        }
    }

    private void assignFromFieldValue(ReferenceFieldValue rhs) {
        if (!getDataType().equals(rhs.getDataType())) {
            throw new IllegalArgumentException(String.format(
                    "Can't assign reference of type %s to reference of type %s",
                    rhs.getDataType().getName(), getDataType().getName()));
        }
        rhs.getDocumentId().ifPresent(this::setDocumentId);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        if (!super.equals(o)) {
            return false;
        }
        ReferenceFieldValue that = (ReferenceFieldValue) o;
        return Objects.equals(referenceType, that.referenceType) &&
                Objects.equals(documentId, that.documentId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), referenceType, documentId);
    }

    @Override
    public void serialize(Field field, FieldWriter writer) {
        writer.write(field, this);
    }

    @Override
    public void deserialize(Field field, FieldReader reader) {
        reader.read(field, this);
    }

    /**
     * Expose target document ID as this value's wrapped value. Primarily implemented to
     * allow for transparent interoperability when used in concrete document types.
     *
     * @return reference DocumentId, or null if none has been set
     */
    @Override
    public DocumentId getWrappedValue() {
        return documentId.orElse(null);
    }

    @Override
    public String toString() {
        return documentId.toString();
    }
}