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