// Copyright (C) 2016 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 com.google.gerrit.server; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.change.ChangeTriplet; import com.google.gerrit.server.index.IndexConfig; import com.google.gerrit.server.project.ChangeControl; import com.google.gerrit.server.project.NoSuchChangeException; import com.google.gerrit.server.query.change.ChangeData; import com.google.gerrit.server.query.change.InternalChangeQuery; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; @Singleton public class ChangeFinder { private final IndexConfig indexConfig; private final Provider<InternalChangeQuery> queryProvider; @Inject ChangeFinder(IndexConfig indexConfig, Provider<InternalChangeQuery> queryProvider) { this.indexConfig = indexConfig; this.queryProvider = queryProvider; } /** * Find changes matching the given identifier. * * @param id change identifier, either a numeric ID, a Change-Id, or project~branch~id triplet. * @param user user to wrap in controls. * @return possibly-empty list of controls for all matching changes, corresponding to the given * user; may or may not be visible. * @throws OrmException if an error occurred querying the database. */ public List<ChangeControl> find(String id, CurrentUser user) throws OrmException { // Use the index to search for changes, but don't return any stored fields, // to force rereading in case the index is stale. InternalChangeQuery query = queryProvider.get().noFields(); // Try legacy id if (!id.isEmpty() && id.charAt(0) != '0') { Integer n = Ints.tryParse(id); if (n != null) { return asChangeControls(query.byLegacyChangeId(new Change.Id(n)), user); } } // Try isolated changeId if (!id.contains("~")) { return asChangeControls(query.byKeyPrefix(id), user); } // Try change triplet Optional<ChangeTriplet> triplet = ChangeTriplet.parse(id); if (triplet.isPresent()) { return asChangeControls(query.byBranchKey(triplet.get().branch(), triplet.get().id()), user); } return Collections.emptyList(); } public ChangeControl findOne(Change.Id id, CurrentUser user) throws OrmException { List<ChangeControl> ctls = find(id, user); if (ctls.size() != 1) { throw new NoSuchChangeException(id); } return ctls.get(0); } public List<ChangeControl> find(Change.Id id, CurrentUser user) throws OrmException { // Use the index to search for changes, but don't return any stored fields, // to force rereading in case the index is stale. InternalChangeQuery query = queryProvider.get().noFields(); return asChangeControls(query.byLegacyChangeId(id), user); } private List<ChangeControl> asChangeControls(List<ChangeData> cds, CurrentUser user) throws OrmException { List<ChangeControl> ctls = new ArrayList<>(cds.size()); if (!indexConfig.separateChangeSubIndexes()) { for (ChangeData cd : cds) { ctls.add(cd.changeControl(user)); } return ctls; } // If an index implementation uses separate non-atomic subindexes, it's possible to temporarily // observe a change as present in both subindexes, if this search is concurrent with a write. // Dedup to avoid confusing the caller. We can choose an arbitrary ChangeData instance because // the index results have no stored fields, so the data is already reloaded. (It's also possible // that a change might appear in zero subindexes, but there's nothing we can do here to help // this case.) Set<Change.Id> seen = Sets.newHashSetWithExpectedSize(cds.size()); for (ChangeData cd : cds) { if (seen.add(cd.getId())) { ctls.add(cd.changeControl(user)); } } return ctls; } }