diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..f023adc
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,17 @@
+name: Java CI
+
+on: [push]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ - name: Build with Maven
+ run: mvn --batch-mode --update-snapshots package
\ No newline at end of file
diff --git a/.github/workflows/pub-docker-hub.yaml b/.github/workflows/pub-docker-hub.yaml
new file mode 100644
index 0000000..3095f57
--- /dev/null
+++ b/.github/workflows/pub-docker-hub.yaml
@@ -0,0 +1,29 @@
+name: Publish Docker image to Docker Hub
+on:
+ release:
+ types:
+ - published
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Login to Docker Hub
+ uses: docker/login-action@v2
+ with:
+ username: ${{secrets.DOCKER_USERNAME}}
+ password: ${{secrets.DOCKER_PASSWORD}}
+ - uses: actions/checkout@v3
+ - name: Set up Maven Central Repository
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+ server-id: docker.io
+ server-username: DOCKER_USERNAME
+ server-password: DOCKER_PASSWORD
+ - name: Publish package
+ run: mvn --batch-mode -Prelease package dockerfile:push
+env:
+ DOCKER_USERNAME: ${{secrets.DOCKER_USERNAME}}
+ DOCKER_TOKEN: ${{secrets.DOCKER_PASSWORD}}
\ No newline at end of file
diff --git a/.github/workflows/pub.yaml b/.github/workflows/pub.yaml
new file mode 100644
index 0000000..15a617a
--- /dev/null
+++ b/.github/workflows/pub.yaml
@@ -0,0 +1,26 @@
+name: Publish package to the Maven Central Repository
+on:
+ release:
+ types: [released]
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Maven Central Repository
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'adopt'
+ server-id: ossrh
+ server-username: OSSRH_USERNAME
+ server-password: OSSRH_TOKEN
+ gpg-private-key: ${{secrets.GPG_PRIVATE_KEY}}
+ gpg-passphrase: GPG_PASSPHRASE
+ - name: Publish package
+ run: mvn --batch-mode -Prelease deploy
+env:
+ GPG_PASSPHRASE: ${{secrets.GPG_PASSPHRASE}}
+ OSSRH_USERNAME: ${{secrets.OSSRH_USERNAME}}
+ OSSRH_TOKEN: ${{secrets.OSSRH_TOKEN}}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..549e00a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..20d4dc6
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,6 @@
+FROM openjdk:17
+MAINTAINER protege.stanford.edu
+
+ARG JAR_FILE
+COPY target/${JAR_FILE} webprotege-initial-revision-history-service.jar
+ENTRYPOINT ["java","-jar","/webprotege-initial-revision-history-service.jar"]
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4652147
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,214 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.5
+
+
+ edu.stanford.protege
+ webprotege-initial-revision-history-service
+ 0.0.1
+ webprotege-initial-revision-history-service
+ This service reads Binary OWL ontology documents from cloud storage (using MinIO) and, from these, generates WebProtégé
+ revision history document that contains a single revision to create these ontologies.
+
+ 17
+
+
+
+
+
+ ossrh
+ https://oss.sonatype.org/service/local/staging/deploy/maven2/
+
+
+ nexus-snapshots
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ edu.stanford.protege
+ webprotege-ipc
+ 1.0.1
+
+
+
+ edu.stanford.protege
+ webprotege-jackson
+ 0.9.2
+
+
+
+ io.minio
+ minio
+ 8.5.10
+
+
+
+ edu.stanford.protege
+ webprotege-revision-manager
+ 0.9.2
+
+
+
+ org.testcontainers
+ minio
+ 1.19.7
+ test
+
+
+
+ com.github.dasniko
+ testcontainers-keycloak
+ 3.3.0
+ test
+
+
+
+
+ net.sourceforge.owlapi
+ binaryowl
+ 2.0.1
+
+
+
+ org.testcontainers
+ rabbitmq
+ 1.19.7
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ com.spotify
+ dockerfile-maven-plugin
+ 1.4.13
+
+
+ default
+
+ build
+
+
+
+
+ protegeproject/${project.artifactId}
+ ${project.version}
+
+ ${project.build.finalName}.jar
+
+
+
+
+
+
+
+
+ release
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ 3.2.1
+
+
+ attach-sources
+
+ jar-no-fork
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ 3.2.0
+
+ none
+ 16
+
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-gpg-plugin
+ 1.6
+
+
+ sign-artifacts
+ verify
+
+ sign
+
+
+
+ --pinentry-mode
+ loopback
+
+
+
+
+
+
+
+ org.sonatype.plugins
+ nexus-staging-maven-plugin
+ 1.6.7
+ true
+
+ ossrh
+ https://oss.sonatype.org
+ true
+
+
+
+
+ com.thoughtworks.xstream
+ xstream
+ 1.4.15
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryCommandHandler.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryCommandHandler.java
new file mode 100644
index 0000000..2830172
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryCommandHandler.java
@@ -0,0 +1,42 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import edu.stanford.protege.webprotege.ipc.CommandHandler;
+import edu.stanford.protege.webprotege.ipc.ExecutionContext;
+import edu.stanford.protege.webprotege.ipc.WebProtegeHandler;
+import org.jetbrains.annotations.NotNull;
+import reactor.core.publisher.Mono;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@WebProtegeHandler
+public class CreateInitialRevisionHistoryCommandHandler implements CommandHandler {
+
+ private final InitialRevisionGenerator initialRevisionGenerator;
+
+ public CreateInitialRevisionHistoryCommandHandler(InitialRevisionGenerator initialRevisionGenerator) {
+ this.initialRevisionGenerator = initialRevisionGenerator;
+ }
+
+ @NotNull
+ @Override
+ public String getChannelName() {
+ return CreateInitialRevisionHistoryRequest.CHANNEL;
+ }
+
+ @Override
+ public Class getRequestClass() {
+ return CreateInitialRevisionHistoryRequest.class;
+ }
+
+ @Override
+ public Mono handleRequest(CreateInitialRevisionHistoryRequest request,
+ ExecutionContext executionContext) {
+ var changeDocumentLocation = initialRevisionGenerator.writeRevisionHistoryFromOntologies(executionContext.userId(),
+ request.documentLocations(),
+ "Initial import");
+ return Mono.just(new CreateInitialRevisionHistoryResponse(changeDocumentLocation));
+ }
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryRequest.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryRequest.java
new file mode 100644
index 0000000..a8ebc1b
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryRequest.java
@@ -0,0 +1,26 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import edu.stanford.protege.webprotege.common.Request;
+
+import java.util.List;
+
+import static edu.stanford.protege.webprotege.initialrevisionhistoryservice.CreateInitialRevisionHistoryRequest.CHANNEL;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@JsonTypeName(CHANNEL)
+public record CreateInitialRevisionHistoryRequest(@JsonProperty("documentLocations") List documentLocations) implements Request {
+
+ public static final String CHANNEL = "webprotege.revisions.CreateInitialRevisionHistory";
+
+ @Override
+ public String getChannel() {
+ return CHANNEL;
+ }
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryResponse.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryResponse.java
new file mode 100644
index 0000000..e7a79fb
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryResponse.java
@@ -0,0 +1,19 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeName;
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import edu.stanford.protege.webprotege.common.Response;
+
+import static edu.stanford.protege.webprotege.initialrevisionhistoryservice.CreateInitialRevisionHistoryRequest.CHANNEL;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@JsonTypeName(CHANNEL)
+public record CreateInitialRevisionHistoryResponse(@JsonProperty("documentLocation") BlobLocation revisionHistoryLocation) implements Response {
+
+
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/InitialRevisionGenerator.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/InitialRevisionGenerator.java
new file mode 100644
index 0000000..35036ef
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/InitialRevisionGenerator.java
@@ -0,0 +1,53 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import com.google.common.collect.ImmutableList;
+import edu.stanford.protege.webprotege.change.OntologyChange;
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import edu.stanford.protege.webprotege.common.UserId;
+import edu.stanford.protege.webprotege.revision.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@Component
+public class InitialRevisionGenerator {
+
+ private final Logger logger = LoggerFactory.getLogger(InitialRevisionGenerator.class);
+
+ private final OntologyChangesLoader ontologyChangesLoader;
+
+ private final RevisionHistoryStorer revisionHistoryStorer;
+
+ public InitialRevisionGenerator(OntologyChangesLoader ontologyChangesLoader,
+ RevisionHistoryStorer revisionHistoryStorer) {
+ this.ontologyChangesLoader = ontologyChangesLoader;
+ this.revisionHistoryStorer = revisionHistoryStorer;
+ }
+
+ public BlobLocation writeRevisionHistoryFromOntologies(UserId userId,
+ List ontologyDocumentLocations,
+ String initialChangeDescription) {
+ logger.info("Creating initial revision history document from ontologies");
+ var ontologyChanges = new ArrayList();
+ ontologyDocumentLocations.forEach(ontologyDocumentLocation -> {
+ ontologyChangesLoader.loadOntologyAndPopulateChanges(ontologyDocumentLocation, userId, ontologyChanges);
+ logger.info("Processed ontology at {}. Cumulative number of changes: {}", ontologyDocumentLocation, ontologyChanges.size());
+ });
+ var initialRevisionNumber = RevisionNumber.getRevisionNumber(1);
+ var timestamp = System.currentTimeMillis();
+ var initialRevision = new Revision(userId, initialRevisionNumber, ImmutableList.copyOf(ontologyChanges), timestamp, initialChangeDescription);
+ var revisionLocation = revisionHistoryStorer.storeRevision(initialRevision);
+ logger.info("Stored revision history document at {}", revisionLocation);
+ return revisionLocation;
+ }
+
+
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioOntologyDocumentLoader.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioOntologyDocumentLoader.java
new file mode 100644
index 0000000..002f67f
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioOntologyDocumentLoader.java
@@ -0,0 +1,44 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import io.minio.GetObjectArgs;
+import io.minio.MinioClient;
+import io.minio.errors.*;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@Component
+public class MinioOntologyDocumentLoader {
+
+ private final MinioClient minioClient;
+
+ private final MinioProperties minioProperties;
+
+ public MinioOntologyDocumentLoader(MinioClient minioClient, MinioProperties minioProperties) {
+ this.minioClient = minioClient;
+ this.minioProperties = minioProperties;
+ }
+
+ public byte [] loadOntologyDocument(@Nonnull BlobLocation location) throws StorageException {
+ try {
+ var object = minioClient.getObject(GetObjectArgs.builder()
+ .bucket(minioProperties.getOntologyDocumentsBucketName())
+ .object(location.name())
+ .build());
+ return object.readAllBytes();
+ } catch (ErrorResponseException | XmlParserException | ServerException | NoSuchAlgorithmException |
+ IOException | InvalidResponseException | InvalidKeyException | InternalException |
+ InsufficientDataException e) {
+ throw new StorageException("Problem reading ontology document object from storage", e);
+ }
+ }
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioProperties.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioProperties.java
new file mode 100644
index 0000000..6384ba6
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioProperties.java
@@ -0,0 +1,64 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@Component
+@ConfigurationProperties(prefix = "webprotege.minio")
+public class MinioProperties {
+
+ private String accessKey;
+
+ private String secretKey;
+
+ private String endPoint;
+
+ private String ontologyDocumentsBucketName;
+
+ private String revisionHistoryDocumentsBucketName;
+
+ public void setAccessKey(String accessKey) {
+ this.accessKey = accessKey;
+ }
+
+ public void setSecretKey(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ public void setEndPoint(String endPoint) {
+ this.endPoint = endPoint;
+ }
+
+ public String getAccessKey() {
+ return accessKey;
+ }
+
+ public String getSecretKey() {
+ return secretKey;
+ }
+
+ public String getEndPoint() {
+ return endPoint;
+ }
+
+ public String getOntologyDocumentsBucketName() {
+ return ontologyDocumentsBucketName;
+ }
+
+ public void setOntologyDocumentsBucketName(String ontologyDocumentsBucketName) {
+ this.ontologyDocumentsBucketName = ontologyDocumentsBucketName;
+ }
+
+ public String getRevisionHistoryDocumentsBucketName() {
+ return revisionHistoryDocumentsBucketName;
+ }
+
+ public void setRevisionHistoryDocumentsBucketName(String revisionHistoryDocumentsBucketName) {
+ this.revisionHistoryDocumentsBucketName = revisionHistoryDocumentsBucketName;
+ }
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioRevisionHistoryDocumentStorer.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioRevisionHistoryDocumentStorer.java
new file mode 100644
index 0000000..0b942d2
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/MinioRevisionHistoryDocumentStorer.java
@@ -0,0 +1,67 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import io.minio.BucketExistsArgs;
+import io.minio.MakeBucketArgs;
+import io.minio.MinioClient;
+import io.minio.UploadObjectArgs;
+import io.minio.errors.*;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.UUID;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@Component
+public class MinioRevisionHistoryDocumentStorer {
+
+ private final MinioClient minioClient;
+
+ private final MinioProperties minioProperties;
+
+ public MinioRevisionHistoryDocumentStorer(MinioClient minioClient, MinioProperties minioProperties) {
+ this.minioClient = minioClient;
+ this.minioProperties = minioProperties;
+ }
+
+ public BlobLocation storeDocument(Path documentPath) {
+ try {
+ var location = generateBlobLocation();
+ // Create bucket if necessary
+ createBucketIfNecessary(location);
+ minioClient.uploadObject(UploadObjectArgs.builder()
+ .filename(documentPath.toString())
+ .bucket(location.bucket())
+ .object(location.name())
+ .contentType("application/octet-stream")
+ .build());
+ return location;
+ } catch (ErrorResponseException | XmlParserException | ServerException | NoSuchAlgorithmException |
+ IOException | InvalidResponseException | InvalidKeyException | InternalException |
+ InsufficientDataException e) {
+ throw new StorageException("Problem writing revision history document to storage", e);
+ }
+ }
+
+ private void createBucketIfNecessary(BlobLocation location) throws ErrorResponseException, InsufficientDataException, InternalException, InvalidKeyException, InvalidResponseException, IOException, NoSuchAlgorithmException, ServerException, XmlParserException {
+ if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(location.bucket()).build())) {
+ minioClient.makeBucket(MakeBucketArgs.builder().bucket(location.bucket()).build());
+ }
+ }
+
+ private BlobLocation generateBlobLocation() {
+ return new BlobLocation(minioProperties.getRevisionHistoryDocumentsBucketName(), generateObjectName());
+ }
+
+ private static String generateObjectName() {
+ return "revision-history-" + UUID.randomUUID() + ".bin";
+ }
+
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/OntologyChangesLoader.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/OntologyChangesLoader.java
new file mode 100644
index 0000000..b06e5f4
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/OntologyChangesLoader.java
@@ -0,0 +1,77 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import edu.stanford.protege.webprotege.change.AddAxiomChange;
+import edu.stanford.protege.webprotege.change.AddOntologyAnnotationChange;
+import edu.stanford.protege.webprotege.change.OntologyChange;
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import edu.stanford.protege.webprotege.common.UserId;
+import io.minio.GetObjectArgs;
+import io.minio.MinioClient;
+import io.minio.errors.*;
+import org.semanticweb.binaryowl.BinaryOWLOntologyDocumentHandlerAdapter;
+import org.semanticweb.binaryowl.BinaryOWLOntologyDocumentSerializer;
+import org.semanticweb.owlapi.model.OWLAnnotation;
+import org.semanticweb.owlapi.model.OWLAxiom;
+import org.semanticweb.owlapi.model.OWLOntologyID;
+import org.springframework.stereotype.Component;
+import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@Component
+public class OntologyChangesLoader {
+
+ private final MinioOntologyDocumentLoader ontologyDocumentLoader;
+
+ public OntologyChangesLoader(MinioOntologyDocumentLoader ontologyDocumentLoader) {
+ this.ontologyDocumentLoader = ontologyDocumentLoader;
+ }
+
+ public void loadOntologyAndPopulateChanges(BlobLocation blobLocation,
+ UserId userId,
+ List ontologyChanges) throws UncheckedIOException {
+
+ var serializer = new BinaryOWLOntologyDocumentSerializer();
+ var ontologyIdRef = new AtomicReference();
+ var ontologyDocumentBytes = ontologyDocumentLoader.loadOntologyDocument(blobLocation);
+ try {
+ serializer.read(new ByteArrayInputStream(ontologyDocumentBytes),
+ new BinaryOWLOntologyDocumentHandlerAdapter<>() {
+ @Override
+ public void handleOntologyID(OWLOntologyID ontologyID) throws RuntimeException {
+ ontologyIdRef.set(ontologyID);
+ }
+
+ @Override
+ public void handleOntologyAnnotations(Set annotations) throws RuntimeException {
+ annotations.forEach(annotation -> {
+ ontologyChanges.add(new AddOntologyAnnotationChange(ontologyIdRef.get(),
+ annotation));
+ });
+ }
+
+ @Override
+ public void handleAxioms(Set axioms) throws RuntimeException {
+ axioms.forEach(axiom -> {
+ ontologyChanges.add(new AddAxiomChange(ontologyIdRef.get(), axiom));
+ });
+ }
+ },
+ new OWLDataFactoryImpl());
+ } catch (IOException e) {
+ throw new UncheckedIOException("Problem deserailizing ontology document", e);
+ }
+ }
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/RevisionHistoryStorer.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/RevisionHistoryStorer.java
new file mode 100644
index 0000000..76d7ebc
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/RevisionHistoryStorer.java
@@ -0,0 +1,54 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import edu.stanford.protege.webprotege.common.ProjectId;
+import edu.stanford.protege.webprotege.revision.Revision;
+import edu.stanford.protege.webprotege.revision.RevisionSerializationTask;
+import io.minio.BucketExistsArgs;
+import io.minio.MakeBucketArgs;
+import io.minio.MinioClient;
+import io.minio.UploadObjectArgs;
+import io.minio.errors.*;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.UUID;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+@Component
+public class RevisionHistoryStorer {
+
+ private final MinioRevisionHistoryDocumentStorer documentStorer;
+
+ public RevisionHistoryStorer(MinioRevisionHistoryDocumentStorer documentStorer) {
+ this.documentStorer = documentStorer;
+ }
+
+ public BlobLocation storeRevision(Revision revision) {
+ try {
+ var tempFile = Files.createTempFile("webprotege-", "-revision-history.bin");
+ var revisionSerializationTask = new RevisionSerializationTask(tempFile.toFile(), revision);
+ revisionSerializationTask.call();
+ var location = documentStorer.storeDocument(tempFile);
+ Files.delete(tempFile);
+ return location;
+ } catch (IOException e) {
+ throw new UncheckedIOException("Problem storing revision history", e);
+ }
+ }
+
+
+
+ private static Path createTempFile() throws IOException {
+ return Files.createTempFile("webprotege-", null);
+ }
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/StorageException.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/StorageException.java
new file mode 100644
index 0000000..bac3ab4
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/StorageException.java
@@ -0,0 +1,13 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+/**
+ * Matthew Horridge
+ * Stanford Center for Biomedical Informatics Research
+ * 2024-05-03
+ */
+public class StorageException extends RuntimeException {
+
+ public StorageException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/WebprotegeInitialRevisionHistoryServiceApplication.java b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/WebprotegeInitialRevisionHistoryServiceApplication.java
new file mode 100644
index 0000000..afe9aeb
--- /dev/null
+++ b/src/main/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/WebprotegeInitialRevisionHistoryServiceApplication.java
@@ -0,0 +1,32 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import edu.stanford.protege.webprotege.common.ProjectId;
+import edu.stanford.protege.webprotege.common.WebProtegeCommonConfiguration;
+import edu.stanford.protege.webprotege.ipc.WebProtegeIpcApplication;
+import edu.stanford.protege.webprotege.jackson.WebProtegeJacksonApplication;
+import edu.stanford.protege.webprotege.revision.*;
+import io.minio.MinioClient;
+import org.semanticweb.owlapi.model.OWLDataFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Import;
+import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;
+
+@SpringBootApplication
+@Import({WebProtegeIpcApplication.class})
+public class WebprotegeInitialRevisionHistoryServiceApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(WebprotegeInitialRevisionHistoryServiceApplication.class, args);
+ }
+
+
+ @Bean
+ MinioClient minioClient(MinioProperties properties) {
+ return MinioClient.builder()
+ .credentials(properties.getAccessKey(), properties.getSecretKey())
+ .endpoint(properties.getEndPoint())
+ .build();
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..e08457d
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,19 @@
+spring:
+ application:
+ name: webprotege-initial-revision-history-service
+ rabbitmq:
+ host: rabbitmq
+ port: 5672
+ password: guest
+ username: guest
+webprotege:
+ minio:
+ access-key: webprotege
+ end-point: http://localhost:9000
+ secret-key: webprotege
+ ontology-documents-bucket-name: webprotege-processed-ontologies
+ revision-history-documents-bucket-name: webprotege-revision-history-documents
+ rabbitmq:
+ requestqueue: webprotege-initial-revision-history-service-queue
+ responsequeue: webprotege-initial-revision-history-service-response-queue
+ timeout: 60000
diff --git a/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryRequestTest.java b/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryRequestTest.java
new file mode 100644
index 0000000..6fec746
--- /dev/null
+++ b/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryRequestTest.java
@@ -0,0 +1,68 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.json.JacksonTester;
+
+import java.io.StringReader;
+import java.util.List;
+
+public class CreateInitialRevisionHistoryRequestTest {
+
+ protected static final String BUCKET_NAME = "bucket";
+
+ protected static final String OBJECT_NAME = "name";
+
+ private JacksonTester json;
+
+ private CreateInitialRevisionHistoryRequest request;
+
+ @BeforeEach
+ public void setup() {
+ var objectMapper = new ObjectMapper()
+ .registerModule(new ParameterNamesModule());
+ JacksonTester.initFields(this, objectMapper);
+
+ var location = new BlobLocation(BUCKET_NAME, OBJECT_NAME);
+ var locations = List.of(location);
+ request = new CreateInitialRevisionHistoryRequest(locations);
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ var written = json.write(request);
+ assertThat(written).hasJsonPathStringValue("$.documentLocations[0].bucket", BUCKET_NAME);
+ assertThat(written).hasJsonPathStringValue("$.documentLocations[0].name", OBJECT_NAME);
+ }
+
+ @Test
+ public void testDeserialization() throws Exception {
+ var read = json.read(new StringReader("""
+ {
+ "documentLocations" : [
+ {
+ "bucket" : "bucket",
+ "name" : "name"
+ }
+ ]
+ }
+ """));
+ assertThat(read.getObject()).isEqualTo(request);
+ }
+
+ @Test
+ public void testGetChannel() {
+ var location = new BlobLocation(BUCKET_NAME, OBJECT_NAME);
+ var locations = List.of(location);
+ var request = new CreateInitialRevisionHistoryRequest(locations);
+
+ assertThat(request.getChannel()).isEqualTo(CreateInitialRevisionHistoryRequest.CHANNEL);
+ }
+}
diff --git a/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryResponseTest.java b/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryResponseTest.java
new file mode 100644
index 0000000..88f0c3e
--- /dev/null
+++ b/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/CreateInitialRevisionHistoryResponseTest.java
@@ -0,0 +1,64 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
+import edu.stanford.protege.webprotege.common.BlobLocation;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.json.JacksonTester;
+
+import java.io.StringReader;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class CreateInitialRevisionHistoryResponseTest {
+
+ protected static final String BUCKET_NAME = "bucket";
+
+ protected static final String OBJECT_NAME = "name";
+
+ private JacksonTester json;
+
+ private CreateInitialRevisionHistoryResponse response;
+
+ @BeforeEach
+ public void setup() {
+ var objectMapper = new ObjectMapper()
+ .registerModule(new ParameterNamesModule());
+ JacksonTester.initFields(this, objectMapper);
+
+ var location = new BlobLocation(BUCKET_NAME, OBJECT_NAME);
+ response = new CreateInitialRevisionHistoryResponse(new BlobLocation(BUCKET_NAME, OBJECT_NAME));
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ var written = json.write(response);
+ assertThat(written).hasJsonPathStringValue("$.documentLocation.bucket", BUCKET_NAME);
+ assertThat(written).hasJsonPathStringValue("$.documentLocation.name", OBJECT_NAME);
+ }
+
+ @Test
+ public void testDeserialization() throws Exception {
+ var read = json.read(new StringReader("""
+ {
+ "documentLocation" :
+ {
+ "bucket" : "bucket",
+ "name" : "name"
+ }
+ }
+ """));
+ assertThat(read.getObject()).isEqualTo(response);
+ }
+
+ @Test
+ public void testGetChannel() {
+ var location = new BlobLocation(BUCKET_NAME, OBJECT_NAME);
+ var locations = List.of(location);
+ var request = new CreateInitialRevisionHistoryRequest(locations);
+
+ assertThat(request.getChannel()).isEqualTo(CreateInitialRevisionHistoryRequest.CHANNEL);
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/WebprotegeInitialRevisionHistoryServiceApplicationTests.java b/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/WebprotegeInitialRevisionHistoryServiceApplicationTests.java
new file mode 100644
index 0000000..177d573
--- /dev/null
+++ b/src/test/java/edu/stanford/protege/webprotege/initialrevisionhistoryservice/WebprotegeInitialRevisionHistoryServiceApplicationTests.java
@@ -0,0 +1,13 @@
+package edu.stanford.protege.webprotege.initialrevisionhistoryservice;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class WebprotegeInitialRevisionHistoryServiceApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties
new file mode 100644
index 0000000..b815f82
--- /dev/null
+++ b/src/test/resources/application.properties
@@ -0,0 +1,4 @@
+webprotege.rabbitmq.commands-subscribe=false
+webprotege.minio.access-key=webprotege
+webprotege.minio.secret-key=webprotege
+webprotege.minio.end-point=http://localhost:9000