Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.index;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.common.base.Preconditions.checkState;
19 : import static java.util.Objects.requireNonNull;
20 :
21 : import com.google.common.base.CharMatcher;
22 : import com.google.gerrit.common.Nullable;
23 : import com.google.gerrit.exceptions.StorageException;
24 : import com.google.gerrit.index.SchemaFieldDefs.Getter;
25 : import com.google.gerrit.index.SchemaFieldDefs.SchemaField;
26 : import com.google.gerrit.index.SchemaFieldDefs.Setter;
27 : import java.io.IOException;
28 : import java.sql.Timestamp;
29 : import java.util.Optional;
30 :
31 : /**
32 : * Definition of a field stored in the secondary index.
33 : *
34 : * <p>{@link FieldDef}-s must not be changed once introduced to the codebase. Instead, a new
35 : * FieldDef must be added and the old one removed from the schema (in two upgrade steps, see {@code
36 : * com.google.gerrit.index.IndexUpgradeValidator}).
37 : *
38 : * <p>Note that {@link FieldDef} does not override {@link Object#equals(Object)}. It relies on
39 : * instances being singletons so that the default (i.e. reference) comparison works.
40 : *
41 : * @param <I> input type from which documents are created and search results are returned.
42 : * @param <T> type that should be extracted from the input object when converting to an index
43 : * document.
44 : */
45 : public final class FieldDef<I, T> implements SchemaField<I, T> {
46 : public static FieldDef.Builder<String> exact(String name) {
47 155 : return new FieldDef.Builder<>(FieldType.EXACT, name);
48 : }
49 :
50 : public static FieldDef.Builder<String> fullText(String name) {
51 155 : return new FieldDef.Builder<>(FieldType.FULL_TEXT, name);
52 : }
53 :
54 : public static FieldDef.Builder<Integer> intRange(String name) {
55 155 : return new FieldDef.Builder<>(FieldType.INTEGER_RANGE, name).stored();
56 : }
57 :
58 : public static FieldDef.Builder<Integer> integer(String name) {
59 155 : return new FieldDef.Builder<>(FieldType.INTEGER, name);
60 : }
61 :
62 : public static FieldDef.Builder<String> prefix(String name) {
63 155 : return new FieldDef.Builder<>(FieldType.PREFIX, name);
64 : }
65 :
66 : public static FieldDef.Builder<byte[]> storedOnly(String name) {
67 155 : return new FieldDef.Builder<>(FieldType.STORED_ONLY, name).stored();
68 : }
69 :
70 : public static FieldDef.Builder<Timestamp> timestamp(String name) {
71 155 : return new FieldDef.Builder<>(FieldType.TIMESTAMP, name);
72 : }
73 :
74 : public static class Builder<T> {
75 : private final FieldType<T> type;
76 : private final String name;
77 : private boolean stored;
78 :
79 155 : public Builder(FieldType<T> type, String name) {
80 155 : this.type = requireNonNull(type);
81 155 : this.name = requireNonNull(name);
82 155 : }
83 :
84 : public Builder<T> stored() {
85 155 : this.stored = true;
86 155 : return this;
87 : }
88 :
89 : public <I> FieldDef<I, T> build(Getter<I, T> getter) {
90 155 : return new FieldDef<>(name, type, stored, false, getter, null);
91 : }
92 :
93 : public <I> FieldDef<I, T> build(Getter<I, T> getter, Setter<I, T> setter) {
94 155 : return new FieldDef<>(name, type, stored, false, getter, setter);
95 : }
96 :
97 : public <I> FieldDef<I, Iterable<T>> buildRepeatable(Getter<I, Iterable<T>> getter) {
98 155 : return new FieldDef<>(name, type, stored, true, getter, null);
99 : }
100 :
101 : public <I> FieldDef<I, Iterable<T>> buildRepeatable(
102 : Getter<I, Iterable<T>> getter, Setter<I, Iterable<T>> setter) {
103 155 : return new FieldDef<>(name, type, stored, true, getter, setter);
104 : }
105 : }
106 :
107 : private final String name;
108 : private final FieldType<?> type;
109 : /** Allow reading the actual data from the index. */
110 : private final boolean stored;
111 :
112 : private final boolean repeatable;
113 : private final Getter<I, T> getter;
114 : private final Optional<Setter<I, T>> setter;
115 :
116 : private FieldDef(
117 : String name,
118 : FieldType<?> type,
119 : boolean stored,
120 : boolean repeatable,
121 : Getter<I, T> getter,
122 155 : @Nullable Setter<I, T> setter) {
123 155 : checkArgument(
124 : !(repeatable && type == FieldType.INTEGER_RANGE),
125 : "Range queries against repeated fields are unsupported");
126 155 : this.name = checkName(name);
127 155 : this.type = requireNonNull(type);
128 155 : this.stored = stored;
129 155 : this.repeatable = repeatable;
130 155 : this.getter = requireNonNull(getter);
131 155 : this.setter = Optional.ofNullable(setter);
132 155 : }
133 :
134 : private static String checkName(String name) {
135 155 : CharMatcher m = CharMatcher.anyOf("abcdefghijklmnopqrstuvwxyz0123456789_");
136 155 : checkArgument(name != null && m.matchesAllOf(name), "illegal field name: %s", name);
137 155 : return name;
138 : }
139 :
140 : /** Returns name of the field. */
141 : @Override
142 : public String getName() {
143 154 : return name;
144 : }
145 :
146 : /** Returns type of the field; for repeatable fields, the inner type, not the iterable type. */
147 : @Override
148 : public FieldType<?> getType() {
149 105 : return type;
150 : }
151 :
152 : /** Returns whether the field should be stored in the index. */
153 : @Override
154 : public boolean isStored() {
155 154 : return stored;
156 : }
157 :
158 : /**
159 : * Get the field contents from the input object.
160 : *
161 : * @param input input object.
162 : * @return the field value(s) to index.
163 : */
164 : @Override
165 : @Nullable
166 : public T get(I input) {
167 : try {
168 107 : return getter.get(input);
169 0 : } catch (IOException e) {
170 0 : throw new StorageException(e);
171 : }
172 : }
173 :
174 : /**
175 : * Set the field contents back to an object. Used to reconstruct fields from indexed values. No-op
176 : * if the field can't be reconstructed.
177 : *
178 : * @param object input object.
179 : * @param doc indexed document
180 : * @return {@code true} if the field was set, {@code false} otherwise
181 : */
182 : @SuppressWarnings("unchecked")
183 : @Override
184 : public boolean setIfPossible(I object, StoredValue doc) {
185 101 : if (!setter.isPresent()) {
186 101 : return false;
187 : }
188 :
189 100 : if (FieldType.STRING_TYPES.stream().anyMatch(t -> t.getName().equals(getType().getName()))) {
190 100 : setter.get().set(object, (T) (isRepeatable() ? doc.asStrings() : doc.asString()));
191 100 : return true;
192 100 : } else if (FieldType.INTEGER_TYPES.stream()
193 100 : .anyMatch(t -> t.getName().equals(getType().getName()))) {
194 100 : setter.get().set(object, (T) (isRepeatable() ? doc.asIntegers() : doc.asInteger()));
195 100 : return true;
196 100 : } else if (FieldType.LONG.getName().equals(getType().getName())) {
197 0 : setter.get().set(object, (T) (isRepeatable() ? doc.asLongs() : doc.asLong()));
198 0 : return true;
199 100 : } else if (FieldType.STORED_ONLY.getName().equals(getType().getName())) {
200 100 : setter.get().set(object, (T) (isRepeatable() ? doc.asByteArrays() : doc.asByteArray()));
201 100 : return true;
202 0 : } else if (FieldType.TIMESTAMP.getName().equals(getType().getName())) {
203 0 : checkState(!isRepeatable(), "can't repeat timestamp values");
204 0 : setter.get().set(object, (T) doc.asTimestamp());
205 0 : return true;
206 : }
207 0 : return false;
208 : }
209 :
210 : /** Returns whether the field is repeatable. */
211 : @Override
212 : public boolean isRepeatable() {
213 105 : return repeatable;
214 : }
215 : }
|