Java tutorial
// Copyright (C) 2015 Advanced Micro Devices, Inc. All rights reserved. // // 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.amd.gerrit.plugins.manifestsubscription; import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG; import com.amd.gerrit.plugins.manifestsubscription.manifest.Manifest; import com.google.common.collect.*; import com.google.gerrit.common.ChangeHooks; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.MetaDataUpdate; import com.google.gerrit.server.git.ProjectConfig; import com.google.gerrit.server.project.ProjectCache; import com.google.inject.Inject; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.xml.bind.JAXBException; import java.io.IOException; import java.util.*; public class ManifestSubscription implements GitReferenceUpdatedListener, LifecycleListener { private static final Logger log = LoggerFactory.getLogger(ManifestSubscription.class); private static final String KEY_BRANCH = "branch"; private static final String KEY_STORE = "store"; static final String STORE_BRANCH_PREFIX = "refs/heads/m/"; private final String pluginName; private final MetaDataUpdate.Server metaDataUpdateFactory; private final GitRepositoryManager gitRepoManager; private final ProjectCache projectCache; private final ChangeHooks changeHooks; /** * source project lookup * manifest source project name, plugin config **/ private Map<String, PluginProjectConfig> enabledManifestSource = Maps.newHashMap(); /** * manifest store lookup * repo name, branch (branchPath), Manifest */ private Table<String, String, Manifest> manifestStores = HashBasedTable.create(); /** * manifest source lookup * repo name, branch (branchPath), manifest source repo */ private Table<String, String, String> manifestSource = HashBasedTable.create(); /** * lookup * subscribed project name and branch, manifest dest store, <manifest dest branch Project> **/ private Table<ProjectBranchKey, String, Map<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>> subscribedRepos = HashBasedTable .create(); public Set<String> getEnabledManifestSource() { return ImmutableSet.copyOf(enabledManifestSource.keySet()); } public Set<ProjectBranchKey> getSubscribedProjects() { return ImmutableSet.copyOf(subscribedRepos.rowKeySet()); } @Override public void start() { ProjectConfig config; for (Project.NameKey p : projectCache.all()) { //TODO parallelize parsing/load up? try { config = ProjectConfig.read(metaDataUpdateFactory.create(p)); loadStoreFromProjectConfig(p.toString(), config); } catch (IOException | ConfigInvalidException | JAXBException e) { log.error(e.toString()); e.printStackTrace(); } } } @Override public void stop() { } @Inject ManifestSubscription(MetaDataUpdate.Server metaDataUpdateFactory, GitRepositoryManager gitRepoManager, @PluginName String pluginName, ProjectCache projectCache, ChangeHooks changeHooks) { this.metaDataUpdateFactory = metaDataUpdateFactory; this.gitRepoManager = gitRepoManager; this.pluginName = pluginName; this.projectCache = projectCache; this.changeHooks = changeHooks; } @Override public void onGitReferenceUpdated(Event event) { String projectName = event.getProjectName(); String refName = event.getRefName(); String branchName = refName.startsWith("refs/heads/") ? refName.substring(11) : ""; ProjectBranchKey pbKey = new ProjectBranchKey(projectName, branchName); if (event.getNewObjectId().equals(ObjectId.zeroId().toString())) { // This happens when there's a branch deletion and possibly other events log.info("Project: " + projectName + "\nrefName: " + refName); } else if (REFS_CONFIG.equals(refName)) { // possible change in enabled repos processProjectConfigChange(event); } else if (enabledManifestSource.containsKey(projectName) && enabledManifestSource.get(projectName).getBranches().contains(branchName)) { processManifestChange(event, projectName, branchName); } else if (subscribedRepos.containsRow(pbKey)) { //updates in subscribed repos // Manifest store and branch Map<String, Map<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>> destinations = subscribedRepos .row(pbKey); for (String store : destinations.keySet()) { for (String storeBranch : destinations.get(store).keySet()) { Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> ps = destinations.get(store) .get(storeBranch); Manifest manifest = manifestStores.get(store, storeBranch); String manifestSrc = manifestSource.get(store, storeBranch); StringBuilder extraCommitMsg = new StringBuilder(); Project.NameKey p = new Project.NameKey(projectName); try (Repository r = gitRepoManager.openRepository(p); RevWalk walk = new RevWalk(r)) { RevCommit c = walk.parseCommit(ObjectId.fromString(event.getNewObjectId())); extraCommitMsg.append(event.getNewObjectId().substring(0, 7)); extraCommitMsg.append(" "); extraCommitMsg.append(projectName); extraCommitMsg.append(" "); extraCommitMsg.append(c.getShortMessage()); } catch (IOException e) { e.printStackTrace(); } // these are project from the above manifest previously // cached in the lookup table for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project updateProject : ps) { updateProject.setRevision(event.getNewObjectId()); } try { Utilities.updateManifest(gitRepoManager, metaDataUpdateFactory, changeHooks, store, STORE_BRANCH_PREFIX + storeBranch, manifest, manifestSrc, extraCommitMsg.toString(), null); } catch (JAXBException | IOException e) { e.printStackTrace(); } } } } } private void updateProjectRev(String projectName, String branch, String rev, List<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> projects) { //TODO optimize to not have to iterate through manifest? for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project project : projects) { if (Objects.equals(projectName, project.getName()) && Objects.equals(branch, project.getUpstream())) { project.setRevision(rev); } if (project.getProject().size() > 0) { updateProjectRev(projectName, branch, rev, project.getProject()); } } } private void processManifestChange(Event event, String projectName, String branchName) { try { VersionedManifests versionedManifests = parseManifests(event); processManifestChange(versionedManifests, projectName, branchName); } catch (JAXBException | IOException | ConfigInvalidException e) { e.printStackTrace(); } } private void processManifestChange(VersionedManifests versionedManifests, String projectName, String branchName) { //possible manifest update in subscribing repos //TODO Fix, right now update all manifest every time //TODO even when only one of the manifest has changed try { if (versionedManifests != null) { CanonicalManifest cManifest = new CanonicalManifest(versionedManifests); Set<String> manifests = versionedManifests.getManifestPaths(); Manifest manifest; String store = enabledManifestSource.get(projectName).getStore(); Table<String, String, String> lookup = HashBasedTable.create(); // Remove old manifest from subscription if destination store and branch // matches manifest source being updated // TODO again, this assume 1-1 map between store and manifest store Map<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>> branchPaths; for (Table.Cell<ProjectBranchKey, String, Map<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>> cell : subscribedRepos .cellSet()) { if (store.equals(cell.getColumnKey())) { branchPaths = cell.getValue(); Iterator<String> iter = branchPaths.keySet().iterator(); String branchPath; while (iter.hasNext()) { branchPath = iter.next(); if (branchPath.startsWith(branchName)) { iter.remove(); manifestStores.remove(store, branchPath); manifestSource.remove(store, branchPath); } } } } //TODO need to make sure remote is pointing to this server? //TODO this may be impossible //TODO only monitor projects without 'remote' attribute / only using default? for (String path : manifests) { String bp = branchName + "/" + path; try { manifest = cManifest.getCanonicalManifest(path); VersionedManifests.affixManifest(gitRepoManager, manifest, lookup); watchCanonicalManifest(manifest, store, bp, projectName); //save manifest //TODO added the m/ to the ref to to work around LOCK_FAILURE error of creating master/bla/bla //TODO (because default master ref already exists) better solution? updateManifest(store, STORE_BRANCH_PREFIX + bp, manifest, projectName); } catch (ManifestReadException | GitAPIException e) { e.printStackTrace(); } } } } catch (JAXBException | IOException e) { e.printStackTrace(); } } private void watchCanonicalManifest(Manifest manifest, String store, String branchPath, String manifestSrc) { String defaultBranch; if (manifest.getDefault() != null && manifest.getDefault().getRevision() != null) { defaultBranch = manifest.getDefault().getRevision(); } else { defaultBranch = ""; } manifestStores.put(store, branchPath, manifest); manifestSource.put(store, branchPath, manifestSrc); List<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> projects = manifest.getProject(); watchProjectsInCanonicalManifest(store, branchPath, defaultBranch, projects); } private void watchProjectsInCanonicalManifest(String store, String branchPath, String defaultBranch, List<com.amd.gerrit.plugins.manifestsubscription.manifest.Project> projects) { ProjectBranchKey pbKey; for (com.amd.gerrit.plugins.manifestsubscription.manifest.Project project : projects) { if (manifestStores.containsRow(project.getName()) && !manifestStores.row(project.getName()).isEmpty()) { // Skip if it's one of the repo for storing // manifest to avoid infinite loop // This is a bit too general, but it's done to avoid the complexity // of actually tracing out the loop // i.e. manifest1->store2 --> manifest2->store1 continue; } // Make sure revision is branch ref w/o refs/heads String branch = project.getRevision() == null ? defaultBranch : (project.getUpstream() != null ? project.getUpstream() : project.getRevision()); pbKey = new ProjectBranchKey(project.getName(), Repository.shortenRefName(branch)); //TODO only update manifests that changed if (!subscribedRepos.contains(pbKey, store)) { subscribedRepos.put(pbKey, store, Maps.<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>newHashMap()); } Map<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>> ps; ps = subscribedRepos.get(pbKey, store); if (!ps.containsKey(branchPath)) { ps.put(branchPath, Sets.<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>newHashSet()); } ps.get(branchPath).add(project); if (project.getProject().size() > 0) { watchProjectsInCanonicalManifest(store, branchPath, defaultBranch, project.getProject()); } } } private void processProjectConfigChange(Event event) { Project.NameKey p = new Project.NameKey(event.getProjectName()); //TODO test two separate project configured to the same store try { ProjectConfig oldCfg = parseConfig(p, event.getOldObjectId()); ProjectConfig newCfg = parseConfig(p, event.getNewObjectId()); //TODO selectively update changes instead of complete reset if (oldCfg != null) { String oldStore = oldCfg.getPluginConfig(pluginName).getString(KEY_STORE); if (oldStore != null && !oldStore.isEmpty()) { //TODO FIX assume unique store for each manifest source (1-1 map) manifestStores.row(oldStore).clear(); manifestSource.row(oldStore).clear(); enabledManifestSource.remove(event.getProjectName()); Iterator<Table.Cell<ProjectBranchKey, String, Map<String, Set<com.amd.gerrit.plugins.manifestsubscription.manifest.Project>>>> iter = subscribedRepos .cellSet().iterator(); while (iter.hasNext()) { if (oldStore.equals(iter.next().getColumnKey())) { iter.remove(); } } } } if (newCfg != null) { loadStoreFromProjectConfig(event.getProjectName(), newCfg); } } catch (IOException | ConfigInvalidException | JAXBException e) { e.printStackTrace(); } } private void loadStoreFromProjectConfig(String projectName, ProjectConfig config) throws JAXBException, IOException, ConfigInvalidException { String newStore = config.getPluginConfig(pluginName).getString(KEY_STORE); if (newStore != null) { newStore = newStore.trim(); if (!newStore.isEmpty()) { Set<String> branches = Sets .newHashSet(config.getPluginConfig(pluginName).getStringList(KEY_BRANCH)); if (branches.size() > 0) { PluginProjectConfig ppc = new PluginProjectConfig(newStore, branches); enabledManifestSource.put(projectName, ppc); Project.NameKey nameKey = new Project.NameKey(projectName); VersionedManifests versionedManifests; for (String branch : branches) { versionedManifests = parseManifests(nameKey, branch); processManifestChange(versionedManifests, projectName, branch); } } } } } private ProjectConfig parseConfig(Project.NameKey p, String idStr) throws IOException, ConfigInvalidException { ObjectId id = ObjectId.fromString(idStr); if (ObjectId.zeroId().equals(id)) { return null; } return ProjectConfig.read(metaDataUpdateFactory.create(p), id); } private VersionedManifests parseManifests(Event event) throws JAXBException, IOException, ConfigInvalidException { Project.NameKey p = new Project.NameKey(event.getProjectName()); return parseManifests(p, event.getRefName()); } private VersionedManifests parseManifests(Project.NameKey p, String refName) throws IOException, JAXBException, ConfigInvalidException { Repository repo = gitRepoManager.openRepository(p); ObjectId commitId = repo.resolve(refName); MetaDataUpdate update = metaDataUpdateFactory.create(p); VersionedManifests vManifests = new VersionedManifests(refName); vManifests.load(update, commitId); return vManifests; } private void updateManifest(String projectName, String refName, Manifest manifest, String manifestSrc) throws JAXBException, IOException { Utilities.updateManifest(gitRepoManager, metaDataUpdateFactory, changeHooks, projectName, refName, manifest, manifestSrc, "", null); } }