Line data Source code
1 : // Copyright (C) 2016 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.server.index.account; 16 : 17 : import static java.util.stream.Collectors.toSet; 18 : 19 : import com.google.common.collect.FluentIterable; 20 : import com.google.common.collect.ImmutableList; 21 : import com.google.common.collect.Iterables; 22 : import com.google.gerrit.common.data.GlobalCapability; 23 : import com.google.gerrit.entities.RefNames; 24 : import com.google.gerrit.index.IndexedField; 25 : import com.google.gerrit.index.RefState; 26 : import com.google.gerrit.index.SchemaUtil; 27 : import com.google.gerrit.server.account.AccountState; 28 : import com.google.gerrit.server.account.externalids.ExternalId; 29 : import com.google.gerrit.server.config.AllUsersName; 30 : import com.google.gerrit.server.config.AllUsersNameProvider; 31 : import java.sql.Timestamp; 32 : import java.util.Arrays; 33 : import java.util.Collections; 34 : import java.util.Locale; 35 : import java.util.Objects; 36 : import java.util.Set; 37 : import org.eclipse.jgit.lib.ObjectId; 38 : 39 : /** 40 : * Secondary index schemas for accounts. 41 : * 42 : * <p>Note that this class does not override {@link Object#equals(Object)}. It relies on instances 43 : * being singletons so that the default (i.e. reference) comparison works. 44 : */ 45 : public class AccountField { 46 : 47 153 : public static final IndexedField<AccountState, Integer> ID_FIELD = 48 153 : IndexedField.<AccountState>integerBuilder("Id") 49 153 : .stored() 50 153 : .required() 51 153 : .build(a -> a.account().id().get()); 52 : 53 153 : public static final IndexedField<AccountState, Integer>.SearchSpec ID_FIELD_SPEC = 54 153 : ID_FIELD.integer("id"); 55 : 56 153 : public static final IndexedField<AccountState, String> ID_STR_FIELD = 57 153 : IndexedField.<AccountState>stringBuilder("IdStr") 58 153 : .stored() 59 153 : .required() 60 153 : .build(a -> String.valueOf(a.account().id().get())); 61 : 62 153 : public static final IndexedField<AccountState, String>.SearchSpec ID_STR_FIELD_SPEC = 63 153 : ID_STR_FIELD.exact("id_str"); 64 : 65 : /** 66 : * External IDs. 67 : * 68 : * <p>This field includes secondary emails. Use this field only if the current user is allowed to 69 : * see secondary emails (requires the {@link GlobalCapability#MODIFY_ACCOUNT} capability). 70 : */ 71 153 : public static final IndexedField<AccountState, Iterable<String>> EXTERNAL_ID_FIELD = 72 153 : IndexedField.<AccountState>iterableStringBuilder("ExternalId") 73 153 : .required() 74 153 : .build(a -> Iterables.transform(a.externalIds(), id -> id.key().get())); 75 : 76 : public static final IndexedField<AccountState, Iterable<String>>.SearchSpec 77 153 : EXTERNAL_ID_FIELD_SPEC = EXTERNAL_ID_FIELD.exact("external_id"); 78 : 79 : /** 80 : * Fuzzy prefix match on name and email parts. 81 : * 82 : * <p>This field includes parts from the secondary emails. Use this field only if the current user 83 : * is allowed to see secondary emails (requires the {@link GlobalCapability#MODIFY_ACCOUNT} 84 : * capability). 85 : * 86 : * <p>Use the {@link AccountField#NAME_PART_NO_SECONDARY_EMAIL_SPEC} if the current user can't see 87 : * secondary emails. 88 : */ 89 153 : public static final IndexedField<AccountState, Iterable<String>> NAME_PART_FIELD = 90 153 : IndexedField.<AccountState>iterableStringBuilder("FullNameAndAllEmailsParts") 91 153 : .description("Full name, all linked emails and their parts (split at special characters)") 92 153 : .required() 93 153 : .build(a -> getNameParts(a, Iterables.transform(a.externalIds(), ExternalId::email))); 94 : 95 153 : public static final IndexedField<AccountState, Iterable<String>>.SearchSpec NAME_PART_SPEC = 96 153 : NAME_PART_FIELD.prefix("name"); 97 : 98 : /** 99 : * Fuzzy prefix match on name and preferred email parts. Parts of secondary emails are not 100 : * included. 101 : */ 102 : public static final IndexedField<AccountState, Iterable<String>> 103 153 : NAME_PART_NO_SECONDARY_EMAIL_FIELD = 104 153 : IndexedField.<AccountState>iterableStringBuilder("FullNameAndPreferredEmailParts") 105 153 : .description( 106 : "Full name, preferred emails and its parts (split at special characters)") 107 153 : .required() 108 153 : .build(a -> getNameParts(a, Arrays.asList(a.account().preferredEmail()))); 109 : 110 : public static final IndexedField<AccountState, Iterable<String>>.SearchSpec 111 153 : NAME_PART_NO_SECONDARY_EMAIL_SPEC = NAME_PART_NO_SECONDARY_EMAIL_FIELD.prefix("name2"); 112 : 113 153 : public static final IndexedField<AccountState, String> FULL_NAME_FIELD = 114 153 : IndexedField.<AccountState>stringBuilder("FullName").build(a -> a.account().fullName()); 115 : 116 153 : public static final IndexedField<AccountState, String>.SearchSpec FULL_NAME_SPEC = 117 153 : FULL_NAME_FIELD.exact("full_name"); 118 : 119 153 : public static final IndexedField<AccountState, String> ACTIVE_FIELD = 120 153 : IndexedField.<AccountState>stringBuilder("Active") 121 153 : .required() 122 153 : .build(a -> a.account().isActive() ? "1" : "0"); 123 : 124 153 : public static final IndexedField<AccountState, String>.SearchSpec ACTIVE_FIELD_SPEC = 125 153 : ACTIVE_FIELD.exact("inactive"); 126 : /** 127 : * All emails (preferred email + secondary emails). Use this field only if the current user is 128 : * allowed to see secondary emails (requires the 'Modify Account' capability). 129 : * 130 : * <p>Use the {@link AccountField#PREFERRED_EMAIL_LOWER_CASE_SPEC} if the current user can't see 131 : * secondary emails. 132 : */ 133 153 : public static final IndexedField<AccountState, Iterable<String>> EMAIL_FIELD = 134 153 : IndexedField.<AccountState>iterableStringBuilder("Email") 135 153 : .required() 136 153 : .build( 137 : a -> 138 10 : FluentIterable.from(a.externalIds()) 139 10 : .transform(ExternalId::email) 140 10 : .append(Collections.singleton(a.account().preferredEmail())) 141 10 : .filter(Objects::nonNull) 142 10 : .transform(String::toLowerCase) 143 10 : .toSet()); 144 : 145 153 : public static final IndexedField<AccountState, Iterable<String>>.SearchSpec EMAIL_SPEC = 146 153 : EMAIL_FIELD.prefix("email"); 147 : 148 153 : public static final IndexedField<AccountState, String> PREFERRED_EMAIL_LOWER_CASE_FIELD = 149 153 : IndexedField.<AccountState>stringBuilder("PreferredEmailLowerCase") 150 153 : .build( 151 : a -> { 152 10 : String preferredEmail = a.account().preferredEmail(); 153 10 : return preferredEmail != null ? preferredEmail.toLowerCase() : null; 154 : }); 155 : 156 : public static final IndexedField<AccountState, String>.SearchSpec 157 153 : PREFERRED_EMAIL_LOWER_CASE_SPEC = PREFERRED_EMAIL_LOWER_CASE_FIELD.prefix("preferredemail"); 158 : 159 153 : public static final IndexedField<AccountState, String> PREFERRED_EMAIL_EXACT_FIELD = 160 153 : IndexedField.<AccountState>stringBuilder("PreferredEmail") 161 153 : .build(a -> a.account().preferredEmail()); 162 : 163 153 : public static final IndexedField<AccountState, String>.SearchSpec PREFERRED_EMAIL_EXACT_SPEC = 164 153 : PREFERRED_EMAIL_EXACT_FIELD.exact("preferredemail_exact"); 165 : 166 : // TODO(issue-15518): Migrate type for timestamp index fields from Timestamp to Instant 167 153 : public static final IndexedField<AccountState, Timestamp> REGISTERED_FIELD = 168 153 : IndexedField.<AccountState>timestampBuilder("Registered") 169 153 : .required() 170 153 : .build(a -> Timestamp.from(a.account().registeredOn())); 171 : 172 153 : public static final IndexedField<AccountState, Timestamp>.SearchSpec REGISTERED_SPEC = 173 153 : REGISTERED_FIELD.timestamp("registered"); 174 : 175 153 : public static final IndexedField<AccountState, String> USERNAME_FIELD = 176 153 : IndexedField.<AccountState>stringBuilder("Username") 177 153 : .build(a -> a.userName().map(String::toLowerCase).orElse("")); 178 : 179 153 : public static final IndexedField<AccountState, String>.SearchSpec USERNAME_SPEC = 180 153 : USERNAME_FIELD.exact("username"); 181 : 182 153 : public static final IndexedField<AccountState, Iterable<String>> WATCHED_PROJECT_FIELD = 183 153 : IndexedField.<AccountState>iterableStringBuilder("WatchedProject") 184 153 : .build( 185 : a -> 186 105 : FluentIterable.from(a.projectWatches().keySet()) 187 105 : .transform(k -> k.project().get()) 188 105 : .toSet()); 189 : 190 153 : public static final IndexedField<AccountState, Iterable<String>>.SearchSpec WATCHED_PROJECT_SPEC = 191 153 : WATCHED_PROJECT_FIELD.exact("watchedproject"); 192 : 193 : /** 194 : * All values of all refs that were used in the course of indexing this document, except the 195 : * refs/meta/external-ids notes branch which is handled specially (see {@link 196 : * #EXTERNAL_ID_STATE_SPEC}). 197 : * 198 : * <p>Emitted as UTF-8 encoded strings of the form {@code project:ref/name:[hex sha]}. 199 : */ 200 153 : public static final IndexedField<AccountState, Iterable<byte[]>> REF_STATE_FIELD = 201 153 : IndexedField.<AccountState>iterableByteArrayBuilder("RefState") 202 153 : .stored() 203 153 : .required() 204 153 : .build( 205 : a -> { 206 11 : if (a.account().metaId() == null) { 207 0 : return ImmutableList.of(); 208 : } 209 : 210 11 : return ImmutableList.of( 211 11 : RefState.create( 212 11 : RefNames.refsUsers(a.account().id()), 213 11 : ObjectId.fromString(a.account().metaId())) 214 : // We use the default AllUsers name to avoid having to pass around that 215 : // variable just for indexing. 216 : // This field is only used for staleness detection which will discover the 217 : // default name and replace it with the actually configured name. 218 11 : .toByteArray(new AllUsersName(AllUsersNameProvider.DEFAULT))); 219 : }); 220 : 221 153 : public static final IndexedField<AccountState, Iterable<byte[]>>.SearchSpec REF_STATE_SPEC = 222 153 : REF_STATE_FIELD.storedOnly("ref_state"); 223 : 224 : /** 225 : * All note values of all external IDs that were used in the course of indexing this document. 226 : * 227 : * <p>Emitted as UTF-8 encoded strings of the form {@code [hex sha of external ID]:[hex sha of 228 : * note blob]}, or with other words {@code [note ID]:[note data ID]}. 229 : */ 230 153 : public static final IndexedField<AccountState, Iterable<byte[]>> EXTERNAL_ID_STATE_FIELD = 231 153 : IndexedField.<AccountState>iterableByteArrayBuilder("ExternalIdState") 232 153 : .stored() 233 153 : .required() 234 153 : .build( 235 : a -> 236 11 : a.externalIds().stream() 237 11 : .filter(e -> e.blobId() != null) 238 11 : .map(ExternalId::toByteArray) 239 11 : .collect(toSet())); 240 : 241 : public static final IndexedField<AccountState, Iterable<byte[]>>.SearchSpec 242 153 : EXTERNAL_ID_STATE_SPEC = EXTERNAL_ID_STATE_FIELD.storedOnly("external_id_state"); 243 : 244 : private static final Set<String> getNameParts(AccountState a, Iterable<String> emails) { 245 35 : String fullName = a.account().fullName(); 246 35 : Set<String> parts = SchemaUtil.getNameParts(fullName, emails); 247 : 248 : // Additional values not currently added by getPersonParts. 249 : // TODO(dborowitz): Move to getPersonParts and remove this hack. 250 35 : if (fullName != null) { 251 35 : parts.add(fullName.toLowerCase(Locale.US)); 252 : } 253 35 : return parts; 254 : } 255 : 256 : private AccountField() {} 257 : }