/* * 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. * */ /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package sh.isaac.provider.commit; //~--- JDK imports ------------------------------------------------------------ import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Stream; //~--- non-JDK imports -------------------------------------------------------- import javafx.collections.ObservableList; import javafx.concurrent.Task; //~--- JDK imports ------------------------------------------------------------ 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.apache.mahout.math.map.OpenIntIntHashMap; import org.glassfish.hk2.runlevel.RunLevel; import org.jvnet.hk2.annotations.Service; import sh.isaac.api.ConfigurationService; import sh.isaac.api.Get; import sh.isaac.api.LookupService; import sh.isaac.api.SystemStatusService; import sh.isaac.api.chronicle.ObjectChronology; import sh.isaac.api.chronicle.ObjectChronologyType; import sh.isaac.api.collections.ConceptSequenceSet; import sh.isaac.api.collections.SememeSequenceSet; import sh.isaac.api.collections.StampSequenceSet; import sh.isaac.api.collections.UuidIntMapMap; import sh.isaac.api.commit.Alert; import sh.isaac.api.commit.AlertType; import sh.isaac.api.commit.ChangeChecker; import sh.isaac.api.commit.CheckPhase; import sh.isaac.api.commit.ChronologyChangeListener; import sh.isaac.api.commit.CommitRecord; import sh.isaac.api.commit.CommitService; import sh.isaac.api.commit.UncommittedStamp; import sh.isaac.api.component.concept.ConceptChronology; import sh.isaac.api.component.sememe.SememeChronology; import sh.isaac.api.component.sememe.SememeType; import sh.isaac.api.coordinate.EditCoordinate; import sh.isaac.api.externalizable.OchreExternalizable; import sh.isaac.api.externalizable.StampAlias; import sh.isaac.api.externalizable.StampComment; import sh.isaac.api.task.SequentialAggregateTask; import sh.isaac.api.task.TimedTask; import sh.isaac.model.ObjectChronologyImpl; import sh.isaac.model.ObjectVersionImpl; //~--- classes ---------------------------------------------------------------- /** * The Class CommitProvider. * * @author kec */ @Service(name = "Commit Provider") @RunLevel(value = 1) public class CommitProvider implements CommitService { /** The Constant LOG. */ private static final Logger LOG = LogManager.getLogger(); /** The Constant DEFAULT_CRADLE_COMMIT_MANAGER_FOLDER. */ public static final String DEFAULT_CRADLE_COMMIT_MANAGER_FOLDER = "commit-manager"; /** The Constant COMMIT_MANAGER_DATA_FILENAME. */ private static final String COMMIT_MANAGER_DATA_FILENAME = "commit-manager.data"; /** The Constant STAMP_ALIAS_MAP_FILENAME. */ private static final String STAMP_ALIAS_MAP_FILENAME = "stamp-alias.map"; /** The Constant STAMP_COMMENT_MAP_FILENAME. */ private static final String STAMP_COMMENT_MAP_FILENAME = "stamp-comment.map"; /** The Constant WRITE_POOL_SIZE. */ private static final int WRITE_POOL_SIZE = 40; //~--- fields -------------------------------------------------------------- /** The uncommitted sequence lock. */ private final ReentrantLock uncommittedSequenceLock = new ReentrantLock(); /** The write permit reference. */ private final AtomicReference<Semaphore> writePermitReference = new AtomicReference<>(new Semaphore(WRITE_POOL_SIZE)); /** The write completion service. */ private final WriteCompletionService writeCompletionService = new WriteCompletionService(); /** The change listeners. */ ConcurrentSkipListSet<WeakReference<ChronologyChangeListener>> changeListeners = new ConcurrentSkipListSet<>(); /** The checkers. */ private final ConcurrentSkipListSet<ChangeChecker> checkers = new ConcurrentSkipListSet<>(); /** The last commit. */ private long lastCommit = Long.MIN_VALUE; /** The load required. */ private final AtomicBoolean loadRequired = new AtomicBoolean(); /** The deferred import no check nids. */ ThreadLocal<Set<Integer>> deferredImportNoCheckNids = new ThreadLocal<>(); /** * TODO recreate alert collection at restart for uncommitted components. */ private final ConcurrentSkipListSet<Alert> alertCollection = new ConcurrentSkipListSet<>(); /** Persistent map of a stamp aliases to a sequence. */ private final StampAliasMap stampAliasMap = new StampAliasMap(); /** * Persistent map of comments to a stamp. */ private final StampCommentMap stampCommentMap = new StampCommentMap(); /** Persistent sequence of database commit actions. */ private final AtomicLong databaseSequence = new AtomicLong(); /** Persistent stamp sequence. */ private final ConceptSequenceSet uncommittedConceptsWithChecksSequenceSet = ConceptSequenceSet.concurrent(); /** The uncommitted concepts no checks sequence set. */ private final ConceptSequenceSet uncommittedConceptsNoChecksSequenceSet = ConceptSequenceSet.concurrent(); /** The uncommitted sememes with checks sequence set. */ private final SememeSequenceSet uncommittedSememesWithChecksSequenceSet = SememeSequenceSet.concurrent(); /** The uncommitted sememes no checks sequence set. */ private final SememeSequenceSet uncommittedSememesNoChecksSequenceSet = SememeSequenceSet.concurrent(); /** The database validity. */ private DatabaseValidity databaseValidity = DatabaseValidity.NOT_SET; /** The db folder path. */ private final Path dbFolderPath; /** The commit manager folder. */ private final Path commitManagerFolder; //~--- constructors -------------------------------------------------------- /** * Instantiates a new commit provider. * * @throws IOException Signals that an I/O exception has occurred. */ private CommitProvider() throws IOException { try { this.dbFolderPath = LookupService.getService(ConfigurationService.class) .getChronicleFolderPath() .resolve("commit-provider"); this.loadRequired.set(Files.exists(this.dbFolderPath)); Files.createDirectories(this.dbFolderPath); this.commitManagerFolder = this.dbFolderPath.resolve(DEFAULT_CRADLE_COMMIT_MANAGER_FOLDER); if (!Files.exists(this.commitManagerFolder)) { this.databaseValidity = DatabaseValidity.MISSING_DIRECTORY; } Files.createDirectories(this.commitManagerFolder); } catch (final Exception e) { LookupService.getService(SystemStatusService.class) .notifyServiceConfigurationFailure("Cradle Commit Provider", e); throw e; } } //~--- methods ------------------------------------------------------------- /** * Adds the alias. * * @param stampSequence the stamp sequence * @param stampAlias the stamp alias * @param aliasCommitComment the alias commit comment */ @Override public void addAlias(int stampSequence, int stampAlias, String aliasCommitComment) { this.stampAliasMap.addAlias(stampSequence, stampAlias); if (aliasCommitComment != null) { this.stampCommentMap.addComment(stampAlias, aliasCommitComment); } } /** * Adds the change checker. * * @param checker the checker */ @Override public void addChangeChecker(ChangeChecker checker) { this.checkers.add(checker); } /** * Due to the use of Weak References in the implementation, you MUST maintain a reference to the change listener that is passed in here, * otherwise, it will be rapidly garbage collected, and you will randomly stop getting change notifications!. * * @param changeListener the change listener * @see sh.isaac.api.commit.CommitService#addChangeListener(sh.isaac.api.commit.ChronologyChangeListener) */ @Override public void addChangeListener(ChronologyChangeListener changeListener) { this.changeListeners.add(new ChangeListenerReference(changeListener)); } /** * Adds the uncommitted. * * @param cc the cc * @return the task */ @Override public Task<Void> addUncommitted(ConceptChronology cc) { return checkAndWrite(cc, this.checkers, this.alertCollection, this.writePermitReference.get(), this.changeListeners); } /** * Adds the uncommitted. * * @param sc the sc * @return the task */ @Override public Task<Void> addUncommitted(SememeChronology sc) { return checkAndWrite(sc, this.checkers, this.alertCollection, this.writePermitReference.get(), this.changeListeners); } /** * Adds the uncommitted no checks. * * @param cc the cc * @return the task */ @Override public Task<Void> addUncommittedNoChecks(ConceptChronology cc) { return write(cc, this.writePermitReference.get(), this.changeListeners); } /** * Adds the uncommitted no checks. * * @param sc the sc * @return the task */ @Override public Task<Void> addUncommittedNoChecks(SememeChronology sc) { return write(sc, this.writePermitReference.get(), this.changeListeners); } /** * Cancel. * * @return the task */ @Override public Task<Void> cancel() { return cancel(Get.configurationService() .getDefaultEditCoordinate()); } /** * Cancel. * * @param cc the cc * @return the task */ @Override public Task<Void> cancel(ConceptChronology cc) { return cancel(cc, Get.configurationService() .getDefaultEditCoordinate()); } /** * Cancel. * * @param editCoordinate the edit coordinate * @return the task */ @Override public Task<Void> cancel(EditCoordinate editCoordinate) { return Get.stampService() .cancel(editCoordinate.getAuthorSequence()); } /** * Cancel. * * @param sememeChronicle the sememe chronicle * @return the task */ @Override public Task<Void> cancel(SememeChronology sememeChronicle) { return cancel(sememeChronicle, Get.configurationService() .getDefaultEditCoordinate()); } /** * Cancel. * * @param chronicle the chronicle * @param editCoordinate the edit coordinate * @return the task */ @Override public Task<Void> cancel(ObjectChronology<?> chronicle, EditCoordinate editCoordinate) { final ObjectChronologyImpl chronicleImpl = (ObjectChronologyImpl) chronicle; final List<ObjectVersionImpl> versionList = chronicleImpl.getVersionList(); for (final ObjectVersionImpl version: versionList) { if (version.isUncommitted()) { if (version.getAuthorSequence() == editCoordinate.getAuthorSequence()) { version.cancel(); } } } chronicleImpl.setVersions(versionList); // see if id is in uncommitted with checks, and without checks... final Collection<Task<?>> subTasks = new ArrayList<>(); if (chronicle instanceof ConceptChronology) { final ConceptChronology conceptChronology = (ConceptChronology) chronicle; if (this.uncommittedConceptsNoChecksSequenceSet.contains(conceptChronology.getConceptSequence())) { subTasks.add(addUncommittedNoChecks(conceptChronology)); } if (this.uncommittedConceptsWithChecksSequenceSet.contains(conceptChronology.getConceptSequence())) { subTasks.add(addUncommitted(conceptChronology)); } } else if (chronicle instanceof SememeChronology) { final SememeChronology sememeChronology = (SememeChronology) chronicle; if (this.uncommittedSememesNoChecksSequenceSet.contains(sememeChronology.getSememeSequence())) { subTasks.add(addUncommittedNoChecks(sememeChronology)); } if (this.uncommittedSememesWithChecksSequenceSet.contains(sememeChronology.getSememeSequence())) { subTasks.add(addUncommitted(sememeChronology)); } } else { throw new RuntimeException("Unsupported chronology type: " + chronicle); } return new SequentialAggregateTask<>("Canceling change", subTasks); } /** * Clear database validity value. */ @Override public void clearDatabaseValidityValue() { // Reset to enforce analysis this.databaseValidity = DatabaseValidity.NOT_SET; } /** * Perform a global commit. The caller may chose to block on the returned * task if synchronous operation is desired. * * @param commitComment the commit comment * @return a task that is already submitted to an executor. */ @Override public synchronized Task<Optional<CommitRecord>> commit(String commitComment) { // return commit(Get.configurationService().getDefaultEditCoordinate(), commitComment); final Semaphore pendingWrites = this.writePermitReference.getAndSet(new Semaphore(WRITE_POOL_SIZE)); pendingWrites.acquireUninterruptibly(WRITE_POOL_SIZE); this.alertCollection.clear(); this.lastCommit = this.databaseSequence.incrementAndGet(); final Map<UncommittedStamp, Integer> pendingStampsForCommit = Get.stampService() .getPendingStampsForCommit(); try { this.uncommittedSequenceLock.lock(); final CommitTask task = CommitTask.get(commitComment, this.uncommittedConceptsWithChecksSequenceSet, this.uncommittedConceptsNoChecksSequenceSet, this.uncommittedSememesWithChecksSequenceSet, this.uncommittedSememesNoChecksSequenceSet, this.lastCommit, this.checkers, this.alertCollection, pendingStampsForCommit, this); return task; } finally { this.uncommittedSequenceLock.unlock(); } } /** * Commit. * * @param cc the cc * @param commitComment the commit comment * @return the task */ @Override public Task<Optional<CommitRecord>> commit(ConceptChronology cc, String commitComment) { return commit(cc, Get.configurationService() .getDefaultEditCoordinate(), commitComment); } /** * Commit. * * @param editCoordinate the edit coordinate * @param commitComment the commit comment * @return the task */ @Override public Task<Optional<CommitRecord>> commit(EditCoordinate editCoordinate, String commitComment) { // TODO, make this only commit those components with changes from the provided edit coordinate. throw new UnsupportedOperationException("This implementation is broken"); // TODO this needs repair... pendingStampsForCommit, for example, is never populated. // Also do we need to lock around the CommitTask creation to makre sure the uncommitted lists are consistent during copy? // Semaphore pendingWrites = writePermitReference.getAndSet(new Semaphore(WRITE_POOL_SIZE)); // pendingWrites.acquireUninterruptibly(WRITE_POOL_SIZE); // alertCollection.clear(); // lastCommit = databaseSequence.incrementAndGet(); // // Map<UncommittedStamp, Integer> pendingStampsForCommit = new HashMap<>(); // UNCOMMITTED_STAMP_TO_STAMP_SEQUENCE_MAP.forEach((uncommittedStamp, stampSequence) -> { // if (uncommittedStamp.authorSequence == editCoordinate.getAuthorSequence()) { // Stamp stamp = new Stamp(Status.getStatusFromState(uncommittedStamp.status), // Long.MIN_VALUE, // Get.identifierService().getConceptNid(uncommittedStamp.authorSequence), // Get.identifierService().getConceptNid(uncommittedStamp.moduleSequence), // Get.identifierService().getConceptNid(uncommittedStamp.pathSequence)); // addStamp(stamp, stampSequence); // UNCOMMITTED_STAMP_TO_STAMP_SEQUENCE_MAP.remove(uncommittedStamp); // } // }); // UNCOMMITTED_STAMP_TO_STAMP_SEQUENCE_MAP.clear(); // // CommitTask task = CommitTask.get(commitComment, // uncommittedConceptsWithChecksSequenceSet, // uncommittedConceptsNoChecksSequenceSet, // uncommittedSememesWithChecksSequenceSet, // uncommittedSememesNoChecksSequenceSet, // lastCommit, // checkers, // alertCollection, // pendingStampsForCommit, // this); // return task; } /** * Commit. * * @param cc the cc * @param commitComment the commit comment * @return the task */ @Override public Task<Optional<CommitRecord>> commit(SememeChronology cc, String commitComment) { return commit(cc, Get.configurationService() .getDefaultEditCoordinate(), commitComment); } /** * Commit. * * @param chronicle the chronicle * @param editCoordinate the edit coordinate * @param commitComment the commit comment * @return the task */ @Override public synchronized Task<Optional<CommitRecord>> commit(ObjectChronology<?> chronicle, EditCoordinate editCoordinate, String commitComment) { // TODO make asynchronous with a actual task. // TODO there are numerous inconsistencies with this impl, and the global commit. Need to understand: // global seq number, should a write be done on the provider? // This also doesn't safely copy the uncommitted lists before using them. // TODO I think this needs to be rewritten to use the CommitTask - but need to understand these issues first. // This also doesn't update the stamp provider... CommitRecord commitRecord = null; final Set<Alert> alerts = new HashSet<>(); if (chronicle instanceof ConceptChronology) { final ConceptChronology conceptChronology = (ConceptChronology) chronicle; if (this.uncommittedConceptsWithChecksSequenceSet.contains(conceptChronology.getConceptSequence())) { this.checkers.stream().forEach((check) -> { check.check(conceptChronology, alerts, CheckPhase.COMMIT); }); } } else if (chronicle instanceof SememeChronology) { final SememeChronology sememeChronology = (SememeChronology) chronicle; if (this.uncommittedSememesWithChecksSequenceSet.contains(sememeChronology.getSememeSequence())) { this.checkers.stream().forEach((check) -> { check.check(sememeChronology, alerts, CheckPhase.COMMIT); }); } } else { throw new RuntimeException("Unsupported chronology type: " + chronicle); } if (alerts.stream() .anyMatch((alert) -> (alert.getAlertType() == AlertType.ERROR))) { this.alertCollection.addAll(alerts); } else { final long commitTime = System.currentTimeMillis(); // TODO have it only commit the versions on the sememe consistent with the edit coordinate. // successful check, commit and remove uncommitted sequences... final StampSequenceSet stampsInCommit = new StampSequenceSet(); final OpenIntIntHashMap stampAliases = new OpenIntIntHashMap(); final ConceptSequenceSet conceptsInCommit = new ConceptSequenceSet(); final SememeSequenceSet sememesInCommit = new SememeSequenceSet(); chronicle.getVersionList().forEach((version) -> { if (((ObjectVersionImpl) version).isUncommitted() && ((ObjectVersionImpl) version).getAuthorSequence() == editCoordinate.getAuthorSequence()) { ((ObjectVersionImpl) version).setTime(commitTime); stampsInCommit.add(((ObjectVersionImpl) version).getStampSequence()); } }); if (chronicle instanceof ConceptChronology) { final ConceptChronology conceptChronology = (ConceptChronology) chronicle; conceptsInCommit.add(conceptChronology.getConceptSequence()); this.uncommittedConceptsWithChecksSequenceSet.remove(conceptChronology.getConceptSequence()); this.uncommittedConceptsNoChecksSequenceSet.remove(conceptChronology.getConceptSequence()); Get.conceptService() .writeConcept(conceptChronology); } else { final SememeChronology sememeChronology = (SememeChronology) chronicle; sememesInCommit.add(sememeChronology.getSememeSequence()); this.uncommittedSememesWithChecksSequenceSet.remove(sememeChronology.getSememeSequence()); this.uncommittedSememesNoChecksSequenceSet.remove(sememeChronology.getSememeSequence()); Get.sememeService() .writeSememe(sememeChronology); } commitRecord = new CommitRecord(Instant.ofEpochMilli(commitTime), stampsInCommit, stampAliases, conceptsInCommit, sememesInCommit, commitComment); } CommitProvider.this.handleCommitNotification(commitRecord); final Optional<CommitRecord> optionalRecord = Optional.ofNullable(commitRecord); final Task<Optional<CommitRecord>> task = new TimedTask() { @Override protected Optional<CommitRecord> call() throws Exception { Get.activeTasks() .remove(this); return optionalRecord; } }; Get.activeTasks() .add(task); Get.workExecutors() .getExecutor() .execute(task); return task; } /** * Import no checks. * * @param ochreExternalizable the ochre externalizable */ @Override public void importNoChecks(OchreExternalizable ochreExternalizable) { switch (ochreExternalizable.getOchreObjectType()) { case CONCEPT: final ConceptChronology conceptChronology = (ConceptChronology) ochreExternalizable; Get.conceptService() .writeConcept(conceptChronology); break; case SEMEME: final SememeChronology sememeChronology = (SememeChronology) ochreExternalizable; Get.sememeService() .writeSememe(sememeChronology); if (sememeChronology.getSememeType() == SememeType.LOGIC_GRAPH) { deferNidAction(sememeChronology.getNid()); } break; case STAMP_ALIAS: final StampAlias stampAlias = (StampAlias) ochreExternalizable; this.stampAliasMap.addAlias(stampAlias.getStampSequence(), stampAlias.getStampAlias()); break; case STAMP_COMMENT: final StampComment stampComment = (StampComment) ochreExternalizable; this.stampCommentMap.addComment(stampComment.getStampSequence(), stampComment.getComment()); break; default: throw new UnsupportedOperationException("Can't handle: " + ochreExternalizable); } } /** * Increment and get sequence. * * @return the long */ @Override public long incrementAndGetSequence() { return this.databaseSequence.incrementAndGet(); } /** * Post process import no checks. */ @Override public void postProcessImportNoChecks() { final Set<Integer> nids = this.deferredImportNoCheckNids.get(); this.deferredImportNoCheckNids.remove(); if (nids != null) { for (final int nid: nids) { if (ObjectChronologyType.SEMEME.equals(Get.identifierService() .getChronologyTypeForNid(nid))) { final SememeChronology sc = Get.sememeService() .getSememe(nid); if (sc.getSememeType() == SememeType.LOGIC_GRAPH) { Get.taxonomyService() .updateTaxonomy(sc); } else { throw new UnsupportedOperationException("Unexpected nid in deferred set: " + nid); } } else { throw new UnsupportedOperationException("Unexpected nid in deferred set: " + nid); } } } } /** * Removes the change checker. * * @param checker the checker */ @Override public void removeChangeChecker(ChangeChecker checker) { this.checkers.remove(checker); } /** * Removes the change listener. * * @param changeListener the change listener */ @Override public void removeChangeListener(ChronologyChangeListener changeListener) { this.changeListeners.remove(new ChangeListenerReference(changeListener)); } /** * Adds the comment. * * @param stamp the stamp * @param commitComment the commit comment */ protected void addComment(int stamp, String commitComment) { this.stampCommentMap.addComment(stamp, commitComment); } /** * Handle commit notification. * * @param commitRecord the commit record */ protected void handleCommitNotification(CommitRecord commitRecord) { this.changeListeners.forEach((listenerRef) -> { final ChronologyChangeListener listener = listenerRef.get(); if (listener == null) { this.changeListeners.remove(listenerRef); } else { listener.handleCommit(commitRecord); } }); } /** * Revert commit. * * @param conceptsToCommit the concepts to commit * @param conceptsToCheck the concepts to check * @param sememesToCommit the sememes to commit * @param sememesToCheck the sememes to check * @param pendingStampsForCommit the pending stamps for commit */ protected void revertCommit(ConceptSequenceSet conceptsToCommit, ConceptSequenceSet conceptsToCheck, SememeSequenceSet sememesToCommit, SememeSequenceSet sememesToCheck, Map<UncommittedStamp, Integer> pendingStampsForCommit) { Get.stampService() .setPendingStampsForCommit(pendingStampsForCommit); this.uncommittedSequenceLock.lock(); try { this.uncommittedConceptsWithChecksSequenceSet.or(conceptsToCheck); this.uncommittedConceptsNoChecksSequenceSet.or(conceptsToCommit); this.uncommittedConceptsNoChecksSequenceSet.andNot(conceptsToCheck); this.uncommittedSememesWithChecksSequenceSet.or(sememesToCheck); this.uncommittedSememesNoChecksSequenceSet.or(sememesToCommit); this.uncommittedSememesNoChecksSequenceSet.andNot(sememesToCheck); } finally { this.uncommittedSequenceLock.unlock(); } } /** * Check and write. * * @param cc the cc * @param checkers the checkers * @param alertCollection the alert collection * @param writeSemaphore the write semaphore * @param changeListeners the change listeners * @return the task */ private Task<Void> checkAndWrite(ConceptChronology cc, ConcurrentSkipListSet<ChangeChecker> checkers, ConcurrentSkipListSet<Alert> alertCollection, Semaphore writeSemaphore, ConcurrentSkipListSet<WeakReference<ChronologyChangeListener>> changeListeners) { writeSemaphore.acquireUninterruptibly(); final WriteAndCheckConceptChronicle task = new WriteAndCheckConceptChronicle(cc, checkers, alertCollection, writeSemaphore, changeListeners, (sememeOrConceptChronicle, changeCheckerActive) -> handleUncommittedSequenceSet( sememeOrConceptChronicle, changeCheckerActive)); this.writeCompletionService.submit(task); return task; } /** * Check and write. * * @param sc the sc * @param checkers the checkers * @param alertCollection the alert collection * @param writeSemaphore the write semaphore * @param changeListeners the change listeners * @return the task */ private Task<Void> checkAndWrite(SememeChronology sc, ConcurrentSkipListSet<ChangeChecker> checkers, ConcurrentSkipListSet<Alert> alertCollection, Semaphore writeSemaphore, ConcurrentSkipListSet<WeakReference<ChronologyChangeListener>> changeListeners) { writeSemaphore.acquireUninterruptibly(); final WriteAndCheckSememeChronicle task = new WriteAndCheckSememeChronicle(sc, checkers, alertCollection, writeSemaphore, changeListeners, (sememeOrConceptChronicle, changeCheckerActive) -> handleUncommittedSequenceSet( sememeOrConceptChronicle, changeCheckerActive)); this.writeCompletionService.submit(task); return task; } /** * Defer nid action. * * @param nid the nid */ private void deferNidAction(int nid) { Set<Integer> nids = this.deferredImportNoCheckNids.get(); if (nids == null) { nids = new HashSet<>(); this.deferredImportNoCheckNids.set(nids); } nids.add(nid); } /** * Handle uncommitted sequence set. * * @param sememeOrConceptChronicle the sememe or concept chronicle * @param changeCheckerActive the change checker active */ private void handleUncommittedSequenceSet(ObjectChronology sememeOrConceptChronicle, boolean changeCheckerActive) { try { this.uncommittedSequenceLock.lock(); switch (sememeOrConceptChronicle.getOchreObjectType()) { case CONCEPT: { final int sequence = Get.identifierService() .getConceptSequence(sememeOrConceptChronicle.getNid()); final ConceptSequenceSet set = changeCheckerActive ? this.uncommittedConceptsWithChecksSequenceSet : this.uncommittedConceptsNoChecksSequenceSet; if (sememeOrConceptChronicle.isUncommitted()) { set.add(sequence); } else { set.remove(sequence); } break; } case SEMEME: { final int sequence = Get.identifierService() .getSememeSequence(sememeOrConceptChronicle.getNid()); final SememeSequenceSet set = changeCheckerActive ? this.uncommittedSememesWithChecksSequenceSet : this.uncommittedSememesNoChecksSequenceSet; if (sememeOrConceptChronicle.isUncommitted()) { set.add(sequence); } else { set.remove(sequence); } break; } default: throw new RuntimeException("Only Concepts or Sememes should be passed"); } } finally { this.uncommittedSequenceLock.unlock(); } } /** * Start me. */ @PostConstruct private void startMe() { try { LOG.info("Starting CommitProvider post-construct"); this.writeCompletionService.start(); if (this.loadRequired.get()) { LOG.info("Reading existing commit manager data. "); LOG.info("Reading " + COMMIT_MANAGER_DATA_FILENAME); try (DataInputStream in = new DataInputStream(new FileInputStream(new File(this.commitManagerFolder.toFile(), COMMIT_MANAGER_DATA_FILENAME)))) { this.databaseSequence.set(in.readLong()); UuidIntMapMap.getNextNidProvider() .set(in.readInt()); this.uncommittedConceptsWithChecksSequenceSet.read(in); this.uncommittedConceptsNoChecksSequenceSet.read(in); this.uncommittedSememesWithChecksSequenceSet.read(in); this.uncommittedSememesNoChecksSequenceSet.read(in); } LOG.info("Reading: " + STAMP_ALIAS_MAP_FILENAME); this.stampAliasMap.read(new File(this.commitManagerFolder.toFile(), STAMP_ALIAS_MAP_FILENAME)); LOG.info("Reading: " + STAMP_COMMENT_MAP_FILENAME); this.stampCommentMap.read(new File(this.commitManagerFolder.toFile(), STAMP_COMMENT_MAP_FILENAME)); this.databaseValidity = DatabaseValidity.POPULATED_DIRECTORY; } } catch (final Exception e) { LookupService.getService(SystemStatusService.class) .notifyServiceConfigurationFailure("Cradle Commit Provider", e); throw new RuntimeException(e); } } /** * Stop me. */ @PreDestroy private void stopMe() { LOG.info("Stopping CommitProvider pre-destroy. "); try { this.writeCompletionService.stop(); this.stampAliasMap.write(new File(this.commitManagerFolder.toFile(), STAMP_ALIAS_MAP_FILENAME)); this.stampCommentMap.write(new File(this.commitManagerFolder.toFile(), STAMP_COMMENT_MAP_FILENAME)); try (DataOutputStream out = new DataOutputStream(new FileOutputStream(new File(this.commitManagerFolder.toFile(), COMMIT_MANAGER_DATA_FILENAME)))) { out.writeLong(this.databaseSequence.get()); out.writeInt(UuidIntMapMap.getNextNidProvider() .get()); this.uncommittedConceptsWithChecksSequenceSet.write(out); this.uncommittedConceptsNoChecksSequenceSet.write(out); this.uncommittedSememesWithChecksSequenceSet.write(out); this.uncommittedSememesNoChecksSequenceSet.write(out); } } catch (final IOException e) { throw new RuntimeException(e); } } /** * Write. * * @param cc the cc * @param writeSemaphore the write semaphore * @param changeListeners the change listeners * @return the task */ private Task<Void> write(ConceptChronology cc, Semaphore writeSemaphore, ConcurrentSkipListSet<WeakReference<ChronologyChangeListener>> changeListeners) { writeSemaphore.acquireUninterruptibly(); final WriteConceptChronicle task = new WriteConceptChronicle(cc, writeSemaphore, changeListeners, (sememeOrConceptChronicle, changeCheckerActive) -> handleUncommittedSequenceSet( sememeOrConceptChronicle, changeCheckerActive)); this.writeCompletionService.submit(task); return task; } /** * Write. * * @param sc the sc * @param writeSemaphore the write semaphore * @param changeListeners the change listeners * @return the task */ private Task<Void> write(SememeChronology sc, Semaphore writeSemaphore, ConcurrentSkipListSet<WeakReference<ChronologyChangeListener>> changeListeners) { writeSemaphore.acquireUninterruptibly(); final WriteSememeChronicle task = new WriteSememeChronicle(sc, writeSemaphore, changeListeners, (sememeOrConceptChronicle, changeCheckerActive) -> handleUncommittedSequenceSet( sememeOrConceptChronicle, changeCheckerActive)); this.writeCompletionService.submit(task); return task; } //~--- get methods --------------------------------------------------------- /** * Gets the alert list. * * @return the alert list */ @Override public ObservableList<Alert> getAlertList() { throw new UnsupportedOperationException( "Not supported yet."); // To change body of generated methods, choose Tools | Templates. } /** * Gets the aliases. * * @param stampSequence the stamp sequence * @return the aliases */ @Override public int[] getAliases(int stampSequence) { return this.stampAliasMap.getAliases(stampSequence); } /** * Gets the comment. * * @param stampSequence the stamp sequence * @return the comment */ @Override public Optional<String> getComment(int stampSequence) { return this.stampCommentMap.getComment(stampSequence); } //~--- set methods --------------------------------------------------------- /** * Set comment. * * @param stampSequence the stamp sequence * @param comment the comment */ @Override public void setComment(int stampSequence, String comment) { this.stampCommentMap.addComment(stampSequence, comment); } //~--- get methods --------------------------------------------------------- /** * Gets the commit manager sequence. * * @return the commit manager sequence */ @Override public long getCommitManagerSequence() { return this.databaseSequence.get(); } /** * Gets the database folder. * * @return the database folder */ @Override public Path getDatabaseFolder() { return this.commitManagerFolder; } /** * Gets the database validity status. * * @return the database validity status */ @Override public DatabaseValidity getDatabaseValidityStatus() { return this.databaseValidity; } /** * Gets the stamp alias stream. * * @return the stamp alias stream */ @Override public Stream<StampAlias> getStampAliasStream() { return this.stampAliasMap.getStampAliasStream(); } /** * Gets the stamp comment stream. * * @return the stamp comment stream */ @Override public Stream<StampComment> getStampCommentStream() { return this.stampCommentMap.getStampCommentStream(); } /** * Gets the uncommitted component text summary. * * @return the uncommitted component text summary */ @Override public String getUncommittedComponentTextSummary() { final StringBuilder builder = new StringBuilder("CommitProvider summary: "); builder.append("\nuncommitted concepts with checks: ") .append(this.uncommittedConceptsWithChecksSequenceSet); builder.append("\nuncommitted concepts no checks: ") .append(this.uncommittedConceptsNoChecksSequenceSet); builder.append("\nuncommitted sememes with checks: ") .append(this.uncommittedSememesWithChecksSequenceSet); builder.append("\nuncommitted sememes no checks: ") .append(this.uncommittedSememesNoChecksSequenceSet); return builder.toString(); } /** * Gets the uncommitted concept nids. * * @return the uncommitted concept nids */ @Override public ObservableList<Integer> getUncommittedConceptNids() { // need to create a list that can be backed with a set... throw new UnsupportedOperationException( "Not supported yet."); // To change body of generated methods, choose Tools | Templates. } //~--- inner classes ------------------------------------------------------- /** * The Class ChangeListenerReference. */ private static class ChangeListenerReference extends WeakReference<ChronologyChangeListener> implements Comparable<ChangeListenerReference> { /** The listener uuid. */ UUID listenerUuid; //~--- constructors ----------------------------------------------------- /** * Instantiates a new change listener reference. * * @param referent the referent */ public ChangeListenerReference(ChronologyChangeListener referent) { super(referent); this.listenerUuid = referent.getListenerUuid(); } /** * Instantiates a new change listener reference. * * @param referent the referent * @param q the q */ public ChangeListenerReference(ChronologyChangeListener referent, ReferenceQueue<? super ChronologyChangeListener> q) { super(referent, q); this.listenerUuid = referent.getListenerUuid(); } //~--- methods ---------------------------------------------------------- /** * Compare to. * * @param o the o * @return the int */ @Override public int compareTo(ChangeListenerReference o) { return this.listenerUuid.compareTo(o.listenerUuid); } /** * Equals. * * @param obj the obj * @return true, if successful */ @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final ChangeListenerReference other = (ChangeListenerReference) obj; return Objects.equals(this.listenerUuid, other.listenerUuid); } /** * Hash code. * * @return the int */ @Override public int hashCode() { int hash = 3; hash = 67 * hash + Objects.hashCode(this.listenerUuid); return hash; } } }