/*
* 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.
*
* Contributions from 2013-2017 where performed either by US government
* employees, or under US Veterans Health Administration contracts.
*
* US Veterans Health Administration contributions by government employees
* are work of the U.S. Government and are not subject to copyright
* protection in the United States. Portions contributed by government
* employees are USGovWork (17USC ยง105). Not subject to copyright.
*
* Contribution by contractors to the US Veterans Health Administration
* during this period are contractually contributed under the
* Apache License, Version 2.0.
*
* See: https://www.usa.gov/government-works
*
* Contributions prior to 2013:
*
* Copyright (C) International Health Terminology Standards Development Organisation.
* Licensed under the Apache License, Version 2.0.
*
*/
package sh.isaac.provider.sync.git.service;
//~--- JDK imports ------------------------------------------------------------
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.glassfish.hk2.runlevel.RunLevel;
import org.jvnet.hk2.annotations.Service;
import sh.isaac.api.ChangeSetLoadService;
import sh.isaac.api.Get;
import sh.isaac.api.LookupService;
import sh.isaac.api.RemoteServiceInfo;
import sh.isaac.api.commit.ChangeSetWriterService;
import sh.isaac.api.sync.MergeFailOption;
import sh.isaac.provider.sync.git.SyncServiceGIT;
import sh.isaac.provider.sync.git.gitblit.GitBlitUtils;
//~--- classes ----------------------------------------------------------------
/**
*
* {@link ChangesetSyncService}
* This service will periodically check and see if there have been changeset files written that have not yet been synced to git, and as necessary,
* pause the changeset writers, commit and push any new files to git, and then resume the changeset writers.
*
* Upon Sync, if any incoming changeset files are found, then the changeset load provider will be triggered to read in any incoming changes.
*
*
* @author <a href="mailto:daniel.armbrust.list@gmail.com">Dan Armbrust</a>
*/
@Service
@RunLevel(value = 5)
public class ChangesetSyncService {
/** The Constant LOG. */
private static final Logger LOG = LogManager.getLogger();
/** The sync JSON files. */
public static boolean syncJSONFiles = true; // TODO we can turn this off later
//~--- fields --------------------------------------------------------------
/** The scheduled check. */
private ScheduledFuture<?> scheduledCheck;
/** The ssg. */
private SyncServiceGIT ssg;
//~--- constructors --------------------------------------------------------
/**
* Instantiates a new changeset sync service.
*/
// For HK2
private ChangesetSyncService() {}
//~--- methods -------------------------------------------------------------
/**
* Start me.
*/
@PostConstruct
private void startMe() {
final Optional<RemoteServiceInfo> gitConfig = Get.configurationService()
.getGitConfiguration();
if (!gitConfig.isPresent() || org.apache.commons.lang3.StringUtils.isBlank(gitConfig.get().getURL())) {
LOG.info("No git configuration is available - Changeset sync service will not be started.");
return;
}
LOG.info("Background threading initial repository sync");
Get.workExecutors().getExecutor().execute(() -> {
try {
LOG.debug("Reading repositories from {} as user {}", gitConfig.get()
.getURL(), gitConfig.get()
.getUsername());
final Set<String> remoteRepos = GitBlitUtils.readRepositories(gitConfig.get()
.getURL(),
gitConfig.get()
.getUsername(),
gitConfig.get()
.getPassword());
LOG.debug("Read {} repositories", remoteRepos.size());
final String changeSetRepo = "db-changesets-" +
Get.conceptService().getDataStoreId().toString() + ".git";
if (!remoteRepos.contains(changeSetRepo)) {
LOG.debug("Creating remote repository {}", changeSetRepo);
GitBlitUtils.createRepository(gitConfig.get()
.getURL(),
changeSetRepo,
"Storage for database changesets",
gitConfig.get()
.getUsername(),
gitConfig.get()
.getPassword(),
false);
}
this.ssg = new SyncServiceGIT();
this.ssg.setReadmeFileContent("ISAAC Changeset Storage \r" + "=== \r" +
"This is a repository for storing ISAAC changesets.\r" +
"It is highly recommended that you do not make changes to this repository manually - ISAAC interfaces with this.");
this.ssg.setGitIgnoreContent(syncJSONFiles ? ""
: "*.json");
final ChangeSetWriterService csw = LookupService.get()
.getService(ChangeSetWriterService.class);
this.ssg.setRootLocation(csw.getWriteFolder()
.toFile());
csw.pause();
LOG.debug("Attempting to link and fetch from remote GIT repository");
final String targetUrl = GitBlitUtils.adjustBareUrlForGitBlit(gitConfig.get()
.getURL()) + "r/" +
changeSetRepo;
this.ssg.linkAndFetchFromRemote(targetUrl,
gitConfig.get()
.getUsername(),
gitConfig.get()
.getPassword());
LOG.debug("Reading any newly arrived changeset files");
int loaded = LookupService.get()
.getService(ChangeSetLoadService.class)
.readChangesetFiles();
LOG.debug("Read {} files", loaded);
LOG.debug("Adding untracked local files");
this.ssg.addUntrackedFiles();
LOG.debug("Committing and Pushing");
final Set<String> changedFiles = this.ssg.updateCommitAndPush("Synchronizing changesets",
gitConfig.get()
.getUsername(),
gitConfig.get()
.getPassword(),
MergeFailOption.FAIL,
(String[]) null);
if (changedFiles.size() != 0) {
LOG.debug("Commit pulled {} more files - reading newly arrived files", changedFiles.size());
loaded = LookupService.get()
.getService(ChangeSetLoadService.class)
.readChangesetFiles();
LOG.debug("Read {} files", loaded);
}
LOG.info(
"Initial sync with remote repository successful. Scheduling remote and local checks.");
this.scheduledCheck = Get.workExecutors()
.getScheduledThreadPoolExecutor()
.scheduleAtFixedRate(() -> syncCheck(), 5, 5, TimeUnit.MINUTES);
} catch (final Exception e) {
LOG.error(
"Unexpected error initializing remote repository sync. Automated repository sync will not execute.",
e);
} finally {
try {
LookupService.get()
.getService(ChangeSetWriterService.class)
.resume();
} catch (final Exception e) {
LOG.warn("Unexpected", e);
}
}
LOG.info("Finished ChangesetSyncService Provider postConstruct.");
});
}
/**
* Stop me.
*/
@PreDestroy
private void stopMe() {
if (this.scheduledCheck != null) {
this.scheduledCheck.cancel(true);
}
this.ssg = null;
LOG.info("Finished ChangesetSyncService Provider preDestroy.");
}
/**
* Sync check.
*/
private void syncCheck() {
LOG.info("Launching sync check in background thread");
Get.workExecutors().getExecutor().execute(() -> {
final Optional<RemoteServiceInfo> gitConfig = Get.configurationService()
.getGitConfiguration();
if (!gitConfig.isPresent()) {
LOG.info("No git configuration is available - Changeset sync service cannot execute.");
return;
}
try {
LookupService.get()
.getService(ChangeSetWriterService.class)
.pause();
LOG.debug("Adding untracked local files");
this.ssg.addUntrackedFiles();
LOG.debug("Committing and Syncing");
final Set<String> changedFiles = this.ssg.updateCommitAndPush("Synchronizing changesets",
gitConfig.get()
.getUsername(),
gitConfig.get()
.getPassword(),
MergeFailOption.FAIL,
(String[]) null);
if (changedFiles.size() != 0) {
LOG.debug("Commit pulled {} more files - reading newly arrived files", changedFiles.size());
final int loaded = LookupService.get()
.getService(ChangeSetLoadService.class)
.readChangesetFiles();
LOG.debug("Read {} files", loaded);
}
LOG.info("Sync with remote successful.");
} catch (final Exception e) {
LOG.error("Unexpected error while doing remote sync.", e);
} finally {
try {
LookupService.get()
.getService(ChangeSetWriterService.class)
.resume();
} catch (final Exception e) {
LOG.warn("Unexpected", e);
}
}
});
}
}