diff --git a/src/main/java/org/carlspring/strongbox/janusgraph/domain/ChangeSet.java b/src/main/java/org/carlspring/strongbox/janusgraph/domain/ChangeSet.java new file mode 100644 index 0000000..cb8d551 --- /dev/null +++ b/src/main/java/org/carlspring/strongbox/janusgraph/domain/ChangeSet.java @@ -0,0 +1,55 @@ +package org.carlspring.strongbox.janusgraph.domain; + +import java.util.UUID; + +import org.neo4j.ogm.annotation.NodeEntity; + +/** + * @author Przemyslaw Fusik + */ +@NodeEntity +public class ChangeSet + extends DomainEntity + implements Comparable +{ + + private Integer order; + + private String name; + + public static ChangeSet build(final String name, + final Integer order) + { + ChangeSet changeSet = new ChangeSet(); + changeSet.setUuid(UUID.randomUUID().toString()); + changeSet.setName(name); + changeSet.setOrder(order); + return changeSet; + } + + public Integer getOrder() + { + return order; + } + + public void setOrder(final Integer order) + { + this.order = order; + } + + public String getName() + { + return name; + } + + public void setName(final String name) + { + this.name = name; + } + + @Override + public int compareTo(final ChangeSet o) + { + return Integer.compare(getOrder(), o.getOrder()); + } +} diff --git a/src/main/java/org/carlspring/strongbox/janusgraph/domain/DatabaseSchema.java b/src/main/java/org/carlspring/strongbox/janusgraph/domain/DatabaseSchema.java new file mode 100644 index 0000000..9260dc1 --- /dev/null +++ b/src/main/java/org/carlspring/strongbox/janusgraph/domain/DatabaseSchema.java @@ -0,0 +1,28 @@ +package org.carlspring.strongbox.janusgraph.domain; + +import java.util.NavigableSet; + +import org.neo4j.ogm.annotation.NodeEntity; +import org.neo4j.ogm.annotation.Relationship; + +/** + * @author Przemyslaw Fusik + */ +@NodeEntity +public class DatabaseSchema + extends DomainEntity +{ + + @Relationship(type = "DatabaseSchema_ChangeSet") + private NavigableSet changeSets; + + public NavigableSet getChangeSets() + { + return changeSets; + } + + public void setChangeSets(final NavigableSet changeSets) + { + this.changeSets = changeSets; + } +} diff --git a/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/StrongboxSchema.java b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/StrongboxSchema.java index c91d1fc..2b83268 100644 --- a/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/StrongboxSchema.java +++ b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/StrongboxSchema.java @@ -1,243 +1,177 @@ package org.carlspring.strongbox.janusgraph.graph.schema; -import java.util.HashSet; -import java.util.Optional; +import java.util.NavigableSet; +import java.util.Objects; import java.util.Set; -import java.util.concurrent.ExecutionException; - -import javax.inject.Inject; - -import org.apache.tinkerpop.gremlin.structure.Element; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.structure.Vertex; -import org.carlspring.strongbox.janusgraph.domain.ArtifactCoordinates; -import org.carlspring.strongbox.janusgraph.domain.ArtifactDependency; -import org.carlspring.strongbox.janusgraph.domain.ArtifactEntry; -import org.janusgraph.core.Cardinality; +import org.carlspring.strongbox.janusgraph.domain.DatabaseSchema; +import org.carlspring.strongbox.janusgraph.graph.schema.changesets.ChangeSet; +import org.carlspring.strongbox.janusgraph.reposiotries.DatabaseSchemaRepository; import org.janusgraph.core.JanusGraph; -import org.janusgraph.core.Multiplicity; -import org.janusgraph.core.PropertyKey; -import org.janusgraph.core.VertexLabel; -import org.janusgraph.core.schema.EdgeLabelMaker; -import org.janusgraph.core.schema.JanusGraphIndex; -import org.janusgraph.core.schema.JanusGraphManagement; -import org.janusgraph.core.schema.JanusGraphSchemaType; -import org.janusgraph.core.schema.PropertyKeyMaker; -import org.janusgraph.core.schema.SchemaAction; -import org.janusgraph.graphdb.database.management.ManagementSystem; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; +import org.springframework.util.Assert; +/** + * @author Przemyslaw Fusik + */ @Component public class StrongboxSchema + implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(StrongboxSchema.class); - @Inject - public void createSchema(JanusGraph jg) throws InterruptedException - { - JanusGraphManagement jgm = jg.openManagement(); - try - { - applySchemaChanges(jgm); - logger.info(String.format("Schema: %n%s", jgm.printSchema())); - jgm.commit(); - } - catch (Exception e) - { - logger.error("Failed to apply schema changes.", e); - jgm.rollback(); - throw new RuntimeException("Failed to apply schema changes.", e); - } - - jgm = jg.openManagement(); - Set indexes; - try - { - indexes = createIndexes(jg, jgm); - jgm.commit(); - } - catch (Exception e) - { - logger.error("Failed to create indexes.", e); - jgm.rollback(); - throw new RuntimeException("Failed to create indexes.", e); - } + private final DatabaseSchemaRepository databaseSchemaRepository; - for (String janusGraphIndex : indexes) - { - logger.info(String.format("Wait index [%s] to be registered.", janusGraphIndex)); - ManagementSystem.awaitGraphIndexStatus(jg, janusGraphIndex).call(); - } - - jgm = jg.openManagement(); - try - { - enableIndexes(jgm, indexes); - jgm.commit(); - } - catch (Exception e) - { - logger.error("Failed to enable indexes.", e); - jgm.rollback(); - throw new RuntimeException("Failed to enable indexes.", e); - } - } + private final NavigableSet changeSets; - protected void enableIndexes(JanusGraphManagement jgm, - Set indexes) - throws InterruptedException, - ExecutionException - { - for (String janusGraphIndex : indexes) - { - logger.info(String.format("Enabling index [%s].", janusGraphIndex)); - jgm.updateIndex(jgm.getGraphIndex(janusGraphIndex), SchemaAction.ENABLE_INDEX).get(); - } - } + private final JanusGraph janusGraph; - protected Set createIndexes(JanusGraph jg, JanusGraphManagement jgm) throws InterruptedException + public StrongboxSchema(final DatabaseSchemaRepository databaseSchemaRepository, + final NavigableSet changeSets, + final JanusGraph janusGraph) { - Set result = new HashSet<>(); - - PropertyKey propertyKey = jgm.getPropertyKey("uuid"); - VertexLabel vertexLabel = jgm.getVertexLabel(ArtifactEntry.class.getSimpleName()); - buildIndexIfNecessary(jgm, ArtifactEntry.class.getSimpleName() + ".uuid", Vertex.class, propertyKey, - vertexLabel, true).ifPresent(result::add); - - propertyKey = jgm.getPropertyKey("uuid"); - vertexLabel = jgm.getVertexLabel(ArtifactCoordinates.class.getSimpleName()); - buildIndexIfNecessary(jgm, ArtifactCoordinates.class.getSimpleName() + ".uuid", Vertex.class, propertyKey, - vertexLabel, true).ifPresent(result::add); - - propertyKey = jgm.getPropertyKey("path"); - vertexLabel = jgm.getVertexLabel(ArtifactCoordinates.class.getSimpleName()); - buildIndexIfNecessary(jgm, ArtifactCoordinates.class.getSimpleName() + ".path", Vertex.class, propertyKey, - vertexLabel).ifPresent(result::add); - -// EdgeLabel artifactEntryToArtifactCoordinates = jg.getEdgeLabel(ArtifactEntry.class.getSimpleName() + "_" -// + ArtifactCoordinates.class.getSimpleName()); -// jgm.buildEdgeIndex(artifactEntryToArtifactCoordinates, "battlesByTime", Direction.OUT); - - - return result; + this.databaseSchemaRepository = databaseSchemaRepository; + this.changeSets = changeSets; + this.janusGraph = janusGraph; } - private void applySchemaChanges(JanusGraphManagement jgm) + @Override + public void afterPropertiesSet() { - // Properties - makePropertyKeyIfDoesNotExist(jgm, "uuid", String.class); - makePropertyKeyIfDoesNotExist(jgm, "storageId", String.class); - makePropertyKeyIfDoesNotExist(jgm, "repositoryId", String.class); - makePropertyKeyIfDoesNotExist(jgm, "sizeInBytes", Long.class); - makePropertyKeyIfDoesNotExist(jgm, "created", String.class); - makePropertyKeyIfDoesNotExist(jgm, "tags", String[].class, Cardinality.SET); - - makePropertyKeyIfDoesNotExist(jgm, "path", String.class); - makePropertyKeyIfDoesNotExist(jgm, "version", String.class); - - // Vertices - makeVertexLabelIfDoesNotExist(jgm, ArtifactEntry.class.getSimpleName()); - makeVertexLabelIfDoesNotExist(jgm, ArtifactCoordinates.class.getSimpleName()); - - // Edges - makeEdgeLabelIfDoesNotExist(jgm, ArtifactEntry.class.getSimpleName() + "_" + - ArtifactCoordinates.class.getSimpleName(), Multiplicity.MANY2ONE); - makeEdgeLabelIfDoesNotExist(jgm, ArtifactDependency.class.getSimpleName(), Multiplicity.MULTI); + validateChangeSets(); + DatabaseSchema databaseSchema = getOrCreateDatabaseSchema(); + applyChangeSets(databaseSchema); } - private Optional buildIndexIfNecessary(final JanusGraphManagement jgm, - final String name, - final Class elementType, - final PropertyKey propertyPath, - final JanusGraphSchemaType schemaType) + void applyChangeSets(final DatabaseSchema databaseSchema) { - return buildIndexIfNecessary(jgm, name, elementType, propertyPath, schemaType, false); - } - - private Optional buildIndexIfNecessary(final JanusGraphManagement jgm, - final String name, - final Class elementType, - final PropertyKey propertyPath, - final JanusGraphSchemaType schemaType, - final boolean unique) - { - if (jgm.containsGraphIndex(name)) - { - return Optional.empty(); - } + final SortedSet appliedChangeSets = databaseSchema.getChangeSets(); + final NavigableSet changeSetsToApply = + CollectionUtils.isEmpty(appliedChangeSets) ? changeSets : filterNotApplied(appliedChangeSets.last()); + + logger.info(String.format("Changes to be applied: Size = [%d]. Values = [%s]", + changeSetsToApply.size(), + changeSetsToApply.stream() + .map(cs -> Pair.of(cs.name(), cs.getOrder())) + .collect(Collectors.toList()))); - JanusGraphManagement.IndexBuilder indexBuilder = jgm.buildIndex(name, elementType); - if (propertyPath != null) + if (changeSetsToApply.size() == 0) { - indexBuilder = indexBuilder.addKey(propertyPath); + return; } - if (schemaType != null) + + try { - indexBuilder = indexBuilder.indexOnly(schemaType); + changeSetsToApply.stream() + .forEach(cs -> cs.apply(janusGraph)); + appliedChangeSets.addAll(mapChangeSetsToNodes(changeSetsToApply)); + databaseSchemaRepository.save(databaseSchema); } - if (unique) + catch (Exception ex) { - indexBuilder = indexBuilder.unique(); + // TODO follow up task: ChangeSet should have rollback function + throw ex; } - - JanusGraphIndex janusGraphIndex = indexBuilder.buildCompositeIndex(); - return Optional.of(janusGraphIndex.name()); } - private void makeEdgeLabelIfDoesNotExist(final JanusGraphManagement jgm, - final String name, - final Multiplicity multiplicity) + private NavigableSet filterNotApplied(final org.carlspring.strongbox.janusgraph.domain.ChangeSet lastAppliedChangeSet) { - if (jgm.containsEdgeLabel(name)) - { - return; - } - EdgeLabelMaker edgeLabelMaker = jgm.makeEdgeLabel(name); - if (multiplicity != null) - { - edgeLabelMaker = edgeLabelMaker.multiplicity(multiplicity); - } + ChangeSet lastCounterpart = changeSets.stream() + .filter(cs -> cs.getOrder() == + lastAppliedChangeSet.getOrder() && + Objects.equals(cs.name(), + lastAppliedChangeSet.getName())) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + String.format( + "Unable to find applied changeSet with name = [%s] and order = [%d]", + lastAppliedChangeSet.getName(), + lastAppliedChangeSet.getOrder()))); + + return this.changeSets.tailSet(lastCounterpart, false); + } - edgeLabelMaker.make(); + DatabaseSchema getOrCreateDatabaseSchema() + { + DatabaseSchema databaseSchema; + long count = countDatabaseSchemas(); + + switch ((int) count) + { + case 0: + databaseSchema = new DatabaseSchema(); + databaseSchema.setUuid(UUID.randomUUID().toString()); + databaseSchema.setChangeSets(new TreeSet<>()); + databaseSchema = databaseSchemaRepository.save(databaseSchema); + Assert.isTrue(countDatabaseSchemas() == 1, "DatabaseSchema not persisted"); + break; + case 1: + databaseSchema = databaseSchemaRepository.findAll().iterator().next(); + break; + default: + throw new IllegalStateException( + String.format("Database schemas size = [%d]. Expected one database schema", count)); + } + + return databaseSchema; } - private void makeVertexLabelIfDoesNotExist(final JanusGraphManagement jgm, - final String name) + void validateChangeSets() { - if (jgm.containsVertexLabel(name)) - { - return; + int previousOrder = -1; + for (ChangeSet changeSet : changeSets) + { + int currentOrder = changeSet.getOrder(); + String changeSetName = changeSet.name(); + + Assert.state(currentOrder >= 0, + String.format("ChangeSet [%s] order should be greater or equal to 0 but was [%d]", + changeSetName, currentOrder)); + Assert.state(currentOrder != previousOrder, + String.format("Two ChangeSets should not have the same order value. ChangeSets [%s]", + changeSets.stream() + .map(cs -> Pair.of(cs.name(), cs.getOrder())) + .collect(Collectors.toList()))); + int expectedOrder = previousOrder + 1; + Assert.state(expectedOrder == currentOrder, + String.format( + "ChangeSets should be incremental by 1 but was: previousOrder [%d], currentOrder [%d], changeSet name [%s]", + previousOrder, currentOrder, changeSetName) + ); + previousOrder = currentOrder; } - jgm.makeVertexLabel(name).make(); } - private void makePropertyKeyIfDoesNotExist(final JanusGraphManagement jgm, - final String name, - final Class dataType) + private long countDatabaseSchemas() { - makePropertyKeyIfDoesNotExist(jgm, name, dataType, null); + GraphTraversalSource g = janusGraph.traversal(); + GraphTraversal databaseSchemas = g.V().hasLabel(DatabaseSchema.class.getSimpleName()).count(); + Long count = databaseSchemas.next(); + g.tx().commit(); + return count; } - private void makePropertyKeyIfDoesNotExist(final JanusGraphManagement jgm, - final String name, - final Class dataType, - final Cardinality cardinality) + private Set mapChangeSetsToNodes(final NavigableSet changeSets) { - if (jgm.containsPropertyKey(name)) - { - return; - } - - PropertyKeyMaker propertyKeyMaker = jgm.makePropertyKey(name).dataType(dataType); - if (cardinality != null) - { - propertyKeyMaker = propertyKeyMaker.cardinality(cardinality); - } - propertyKeyMaker.make(); - + return changeSets.stream().map( + cs -> org.carlspring.strongbox.janusgraph.domain.ChangeSet.build(cs.name(), cs.getOrder())).collect( + Collectors.toSet()); } } + + diff --git a/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/ArtifactEntryChangeSet.java b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/ArtifactEntryChangeSet.java new file mode 100644 index 0000000..5e0e3d7 --- /dev/null +++ b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/ArtifactEntryChangeSet.java @@ -0,0 +1,90 @@ +package org.carlspring.strongbox.janusgraph.graph.schema.changesets; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.carlspring.strongbox.janusgraph.domain.ArtifactCoordinates; +import org.carlspring.strongbox.janusgraph.domain.ArtifactDependency; +import org.carlspring.strongbox.janusgraph.domain.ArtifactEntry; +import org.janusgraph.core.Cardinality; +import org.janusgraph.core.Multiplicity; +import org.janusgraph.core.PropertyKey; +import org.janusgraph.core.VertexLabel; +import org.janusgraph.core.schema.JanusGraphManagement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +@Component +class ArtifactEntryChangeSet + implements ChangeSet +{ + + private static final Logger logger = LoggerFactory.getLogger(ArtifactEntryChangeSet.class); + + @Override + public int getOrder() + { + return 1; + } + + @Override + public Logger getLogger() + { + return logger; + } + + public void applySchemaChanges(JanusGraphManagement jgm) + { + // Properties + makePropertyKeyIfDoesNotExist(jgm, "uuid", String.class); + makePropertyKeyIfDoesNotExist(jgm, "storageId", String.class); + makePropertyKeyIfDoesNotExist(jgm, "repositoryId", String.class); + makePropertyKeyIfDoesNotExist(jgm, "sizeInBytes", Long.class); + makePropertyKeyIfDoesNotExist(jgm, "created", String.class); + makePropertyKeyIfDoesNotExist(jgm, "tags", String[].class, Cardinality.SET); + + makePropertyKeyIfDoesNotExist(jgm, "path", String.class); + makePropertyKeyIfDoesNotExist(jgm, "version", String.class); + + // Vertices + makeVertexLabelIfDoesNotExist(jgm, ArtifactEntry.class.getSimpleName()); + makeVertexLabelIfDoesNotExist(jgm, ArtifactCoordinates.class.getSimpleName()); + + // Edges + makeEdgeLabelIfDoesNotExist(jgm, ArtifactEntry.class.getSimpleName() + "_" + + ArtifactCoordinates.class.getSimpleName(), Multiplicity.MANY2ONE); + makeEdgeLabelIfDoesNotExist(jgm, ArtifactDependency.class.getSimpleName(), Multiplicity.MULTI); + } + + @Override + public Set createIndexes(JanusGraphManagement jgm) + { + Set result = new HashSet<>(); + + PropertyKey propertyKey = jgm.getPropertyKey("uuid"); + VertexLabel vertexLabel = jgm.getVertexLabel(ArtifactEntry.class.getSimpleName()); + buildIndexIfNecessary(jgm, ArtifactEntry.class.getSimpleName() + ".uuid", Vertex.class, propertyKey, + vertexLabel, true).ifPresent(result::add); + + propertyKey = jgm.getPropertyKey("uuid"); + vertexLabel = jgm.getVertexLabel(ArtifactCoordinates.class.getSimpleName()); + buildIndexIfNecessary(jgm, ArtifactCoordinates.class.getSimpleName() + ".uuid", Vertex.class, propertyKey, + vertexLabel, true).ifPresent(result::add); + + propertyKey = jgm.getPropertyKey("path"); + vertexLabel = jgm.getVertexLabel(ArtifactCoordinates.class.getSimpleName()); + buildIndexIfNecessary(jgm, ArtifactCoordinates.class.getSimpleName() + ".path", Vertex.class, propertyKey, + vertexLabel).ifPresent(result::add); + +// EdgeLabel artifactEntryToArtifactCoordinates = jg.getEdgeLabel(ArtifactEntry.class.getSimpleName() + "_" +// + ArtifactCoordinates.class.getSimpleName()); +// jgm.buildEdgeIndex(artifactEntryToArtifactCoordinates, "battlesByTime", Direction.OUT); + + + return result; + } + + +} diff --git a/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/ChangeSet.java b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/ChangeSet.java new file mode 100644 index 0000000..7f99177 --- /dev/null +++ b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/ChangeSet.java @@ -0,0 +1,227 @@ +package org.carlspring.strongbox.janusgraph.graph.schema.changesets; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.apache.tinkerpop.gremlin.structure.Element; +import org.janusgraph.core.Cardinality; +import org.janusgraph.core.JanusGraph; +import org.janusgraph.core.Multiplicity; +import org.janusgraph.core.PropertyKey; +import org.janusgraph.core.schema.EdgeLabelMaker; +import org.janusgraph.core.schema.JanusGraphIndex; +import org.janusgraph.core.schema.JanusGraphManagement; +import org.janusgraph.core.schema.JanusGraphSchemaType; +import org.janusgraph.core.schema.PropertyKeyMaker; +import org.janusgraph.core.schema.SchemaAction; +import org.janusgraph.graphdb.database.management.ManagementSystem; +import org.slf4j.Logger; +import org.springframework.core.Ordered; + +/** + * @author Przemyslaw Fusik + */ +public interface ChangeSet + extends Ordered, Comparable +{ + + Logger getLogger(); + + default String name() + { + return getClass().getName(); + } + + @Override + default int compareTo(ChangeSet o) + { + return Integer.compare(getOrder(), o.getOrder()); + } + + default void apply(JanusGraph jg) + { + applySchemaChanges(jg); + createAndEnableIndexes(jg); + } + + default void applySchemaChanges(JanusGraph jg) + { + JanusGraphManagement jgm = jg.openManagement(); + try + { + applySchemaChanges(jgm); + getLogger().info(String.format("Schema: %n%s", jgm.printSchema())); + jgm.commit(); + } + catch (Exception e) + { + getLogger().error("Failed to apply schema changes.", e); + jgm.rollback(); + throw new RuntimeException("Failed to apply schema changes.", e); + } + + } + + default void applySchemaChanges(JanusGraphManagement jgm) + { + // NOOP by default + } + + default void createAndEnableIndexes(JanusGraph jg) + { + JanusGraphManagement jgm = jg.openManagement(); + Set indexes; + + try + { + indexes = createIndexes(jgm); + jgm.commit(); + } + catch (Exception e) + { + getLogger().error("Failed to create indexes.", e); + jgm.rollback(); + throw new RuntimeException("Failed to create indexes.", e); + } + + for (String janusGraphIndex : indexes) + { + getLogger().info(String.format("Wait index [%s] to be registered.", janusGraphIndex)); + try + { + ManagementSystem.awaitGraphIndexStatus(jg, janusGraphIndex).call(); + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while registering indexes.", e); + } + } + + jgm = jg.openManagement(); + try + { + enableIndexes(jgm, indexes); + jgm.commit(); + } + catch (Exception e) + { + getLogger().error("Failed to enable indexes.", e); + jgm.rollback(); + throw new RuntimeException("Failed to enable indexes.", e); + } + } + + default Set createIndexes(JanusGraphManagement jgm) + { + return Collections.emptySet(); + } + + default void enableIndexes(JanusGraphManagement jgm, + Set indexes) + throws ExecutionException, InterruptedException + { + for (String janusGraphIndex : indexes) + { + getLogger().info(String.format("Enabling index [%s].", janusGraphIndex)); + jgm.updateIndex(jgm.getGraphIndex(janusGraphIndex), SchemaAction.ENABLE_INDEX).get(); + } + } + + default Optional buildIndexIfNecessary(final JanusGraphManagement jgm, + final String name, + final Class elementType, + final PropertyKey propertyPath, + final JanusGraphSchemaType schemaType) + { + return buildIndexIfNecessary(jgm, name, elementType, propertyPath, schemaType, false); + + } + + default Optional buildIndexIfNecessary(final JanusGraphManagement jgm, + final String name, + final Class elementType, + final PropertyKey propertyPath, + final JanusGraphSchemaType schemaType, + final boolean unique) + { + if (jgm.containsGraphIndex(name)) + { + return Optional.empty(); + } + + JanusGraphManagement.IndexBuilder indexBuilder = jgm.buildIndex(name, elementType); + if (propertyPath != null) + { + indexBuilder = indexBuilder.addKey(propertyPath); + } + if (schemaType != null) + { + indexBuilder = indexBuilder.indexOnly(schemaType); + } + if (unique) + { + indexBuilder = indexBuilder.unique(); + } + + JanusGraphIndex janusGraphIndex = indexBuilder.buildCompositeIndex(); + return Optional.of(janusGraphIndex.name()); + } + + default void makeEdgeLabelIfDoesNotExist(final JanusGraphManagement jgm, + final String name, + final Multiplicity multiplicity) + { + if (jgm.containsEdgeLabel(name)) + { + return; + } + EdgeLabelMaker edgeLabelMaker = jgm.makeEdgeLabel(name); + if (multiplicity != null) + { + edgeLabelMaker = edgeLabelMaker.multiplicity(multiplicity); + } + + edgeLabelMaker.make(); + } + + default void makeVertexLabelIfDoesNotExist(final JanusGraphManagement jgm, + final String name) + { + if (jgm.containsVertexLabel(name)) + { + return; + } + jgm.makeVertexLabel(name).make(); + } + + default void makePropertyKeyIfDoesNotExist(final JanusGraphManagement jgm, + final String name, + final Class dataType) + { + makePropertyKeyIfDoesNotExist(jgm, name, dataType, null); + } + + default void makePropertyKeyIfDoesNotExist(final JanusGraphManagement jgm, + final String name, + final Class dataType, + final Cardinality cardinality) + { + if (jgm.containsPropertyKey(name)) + { + return; + } + + PropertyKeyMaker propertyKeyMaker = jgm.makePropertyKey(name).dataType(dataType); + if (cardinality != null) + { + propertyKeyMaker = propertyKeyMaker.cardinality(cardinality); + } + propertyKeyMaker.make(); + + } + + +} diff --git a/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/DatabaseSchemaChangeSet.java b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/DatabaseSchemaChangeSet.java new file mode 100644 index 0000000..a113da7 --- /dev/null +++ b/src/main/java/org/carlspring/strongbox/janusgraph/graph/schema/changesets/DatabaseSchemaChangeSet.java @@ -0,0 +1,74 @@ +package org.carlspring.strongbox.janusgraph.graph.schema.changesets; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.carlspring.strongbox.janusgraph.domain.DatabaseSchema; +import org.janusgraph.core.Multiplicity; +import org.janusgraph.core.PropertyKey; +import org.janusgraph.core.VertexLabel; +import org.janusgraph.core.schema.JanusGraphManagement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * @author Przemyslaw Fusik + */ +@Component +public class DatabaseSchemaChangeSet + implements ChangeSet +{ + + private static final Logger logger = LoggerFactory.getLogger(DatabaseSchemaChangeSet.class); + + @Override + public int getOrder() + { + return 0; + } + + @Override + public Logger getLogger() + { + return logger; + } + + @Override + public void applySchemaChanges(JanusGraphManagement jgm) + { + // Properties + makePropertyKeyIfDoesNotExist(jgm, "order", Integer.class); + makePropertyKeyIfDoesNotExist(jgm, "name", String.class); + + // Vertices + makeVertexLabelIfDoesNotExist(jgm, DatabaseSchema.class.getSimpleName()); + makeVertexLabelIfDoesNotExist(jgm, org.carlspring.strongbox.janusgraph.domain.ChangeSet.class.getSimpleName()); + + // Edges + makeEdgeLabelIfDoesNotExist(jgm, DatabaseSchema.class.getSimpleName() + "_" + + org.carlspring.strongbox.janusgraph.domain.ChangeSet.class.getSimpleName(), + Multiplicity.ONE2MANY); + } + + /* + @Override + public Set createIndexes(JanusGraphManagement jgm) + { + Set result = new HashSet<>(); + + PropertyKey propertyKey = jgm.getPropertyKey("uuid"); + VertexLabel vertexLabel = jgm.getVertexLabel(DatabaseSchema.class.getSimpleName()); + buildIndexIfNecessary(jgm, DatabaseSchema.class.getSimpleName() + ".uuid", Vertex.class, propertyKey, + vertexLabel, true).ifPresent(result::add); + + propertyKey = jgm.getPropertyKey("name"); + vertexLabel = jgm.getVertexLabel(org.carlspring.strongbox.janusgraph.domain.ChangeSet.class.getSimpleName()); + buildIndexIfNecessary(jgm, org.carlspring.strongbox.janusgraph.domain.ChangeSet.class.getSimpleName() + ".name", + Vertex.class, propertyKey, vertexLabel, true).ifPresent(result::add); + + return result; + } + */ +} diff --git a/src/main/java/org/carlspring/strongbox/janusgraph/reposiotries/DatabaseSchemaRepository.java b/src/main/java/org/carlspring/strongbox/janusgraph/reposiotries/DatabaseSchemaRepository.java new file mode 100644 index 0000000..f063a67 --- /dev/null +++ b/src/main/java/org/carlspring/strongbox/janusgraph/reposiotries/DatabaseSchemaRepository.java @@ -0,0 +1,27 @@ +package org.carlspring.strongbox.janusgraph.reposiotries; + +import java.util.Optional; + +import org.carlspring.strongbox.janusgraph.domain.DatabaseSchema; +import org.springframework.data.neo4j.annotation.Query; +import org.springframework.data.repository.CrudRepository; + +/** + * @author Przemyslaw Fusik + */ +public interface DatabaseSchemaRepository + extends CrudRepository +{ + + @Query("MATCH (databaseSchema:DatabaseSchema {uuid:$uuid}) RETURN databaseSchema") + @Override + Optional findById(String uuid); + + @Query("MATCH (databaseSchema:DatabaseSchema)-[r:`DatabaseSchema_ChangeSet`]->(cs:ChangeSet) RETURN databaseSchema, r, cs") + @Override + Iterable findAll(); + + @Query("MATCH (databaseSchema:DatabaseSchema {uuid:$uuid}) DETACH DELETE databaseSchema") + @Override + void deleteById(String uuid); +} diff --git a/src/test/java/org/carlspring/strongbox/janusgraph/graph/schema/StrongboxSchemaTest.java b/src/test/java/org/carlspring/strongbox/janusgraph/graph/schema/StrongboxSchemaTest.java new file mode 100644 index 0000000..727c6db --- /dev/null +++ b/src/test/java/org/carlspring/strongbox/janusgraph/graph/schema/StrongboxSchemaTest.java @@ -0,0 +1,86 @@ +package org.carlspring.strongbox.janusgraph.graph.schema; + +import java.util.NavigableSet; + +import org.assertj.core.util.Sets; +import org.carlspring.strongbox.janusgraph.app.Application; +import org.carlspring.strongbox.janusgraph.graph.schema.changesets.ChangeSet; +import org.carlspring.strongbox.janusgraph.reposiotries.DatabaseSchemaRepository; +import org.janusgraph.core.JanusGraph; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * @author Przemyslaw Fusik + */ +@SpringBootTest(classes = Application.class) +class StrongboxSchemaTest +{ + + private static final Logger logger = LoggerFactory.getLogger(StrongboxSchemaTest.class); + + @Autowired + private DatabaseSchemaRepository databaseSchemaRepository; + + @Autowired + private JanusGraph janusGraph; + + @Autowired + private NavigableSet changeSets; + + @BeforeEach + public void beforeEach() + { + + ChangeSet changeSet1 = new ChangeSet() + { + + @Override + public int getOrder() + { + return 0; + } + + @Override + public Logger getLogger() + { + return logger; + } + }; + + ChangeSet changeSet2 = new ChangeSet() + { + + @Override + public int getOrder() + { + return 0; + } + + @Override + public Logger getLogger() + { + return logger; + } + }; + + strongboxSchema = new StrongboxSchema(databaseSchemaRepository, + Sets.newTreeSet(changeSet1, changeSet2), + janusGraph); + } + + private StrongboxSchema strongboxSchema = new StrongboxSchema(databaseSchemaRepository, null, janusGraph); + + @Test + void testGet() + { + // TODO + strongboxSchema.afterPropertiesSet(); + } + + +} \ No newline at end of file diff --git a/src/test/java/org/carlspring/strongbox/janusgraph/reposiotries/DatabaseSchemaRepositoryTest.java b/src/test/java/org/carlspring/strongbox/janusgraph/reposiotries/DatabaseSchemaRepositoryTest.java new file mode 100644 index 0000000..a111f69 --- /dev/null +++ b/src/test/java/org/carlspring/strongbox/janusgraph/reposiotries/DatabaseSchemaRepositoryTest.java @@ -0,0 +1,91 @@ +package org.carlspring.strongbox.janusgraph.reposiotries; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Iterator; +import java.util.Optional; +import java.util.UUID; + +import javax.inject.Inject; + +import org.assertj.core.util.Sets; +import org.carlspring.strongbox.janusgraph.app.Application; +import org.carlspring.strongbox.janusgraph.domain.ChangeSet; +import org.carlspring.strongbox.janusgraph.domain.DatabaseSchema; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author Przemyslaw Fusik + */ +@Transactional +@SpringBootTest(classes = Application.class) +class DatabaseSchemaRepositoryTest +{ + + @Inject + private DatabaseSchemaRepository databaseSchemaRepository; + + @BeforeEach + public void before() + { + databaseSchemaRepository.deleteAll(); + } + + @Test + public void crudShouldWork() + { + DatabaseSchema databaseSchema = new DatabaseSchema(); + String uuid = UUID.randomUUID().toString(); + databaseSchema.setUuid(uuid); + databaseSchemaRepository.save(databaseSchema); + + Optional byId = databaseSchemaRepository.findById(uuid); + assertThat(byId).isPresent(); + databaseSchema = byId.get(); + assertThat(databaseSchema.getUuid()).isEqualTo(uuid); + assertThat(databaseSchema.getChangeSets()).isNullOrEmpty(); + + ChangeSet changeSet = ChangeSet.build("first changeset", 0); + + databaseSchema.setChangeSets(Sets.newTreeSet(changeSet)); + databaseSchemaRepository.save(databaseSchema); + + byId = databaseSchemaRepository.findById(uuid); + assertThat(byId).isPresent(); + databaseSchema = byId.get(); + assertThat(databaseSchema.getUuid()).isEqualTo(uuid); + assertThat(databaseSchema.getChangeSets()).hasSize(1); + ChangeSet appliedChangeSet = databaseSchema.getChangeSets().iterator().next(); + assertThat(appliedChangeSet.getName()).isEqualTo("first changeset"); + assertThat(appliedChangeSet.getOrder()).isEqualTo(0); + + ChangeSet changeSetSecond = ChangeSet.build("second changeset", 1); + + databaseSchema.getChangeSets().add(changeSetSecond); + databaseSchemaRepository.save(databaseSchema); + + byId = databaseSchemaRepository.findById(uuid); + assertThat(byId).isPresent(); + databaseSchema = byId.get(); + assertThat(databaseSchema.getUuid()).isEqualTo(uuid); + assertThat(databaseSchema.getChangeSets()).hasSize(2); + + final Iterator iterator = databaseSchema.getChangeSets().iterator(); + + appliedChangeSet = iterator.next(); + assertThat(appliedChangeSet.getName()).isEqualTo("first changeset"); + assertThat(appliedChangeSet.getOrder()).isEqualTo(0); + + appliedChangeSet = iterator.next(); + assertThat(appliedChangeSet.getName()).isEqualTo("second changeset"); + assertThat(appliedChangeSet.getOrder()).isEqualTo(1); + + databaseSchemaRepository.deleteById(uuid); + byId = databaseSchemaRepository.findById(uuid); + assertThat(byId).isEmpty(); + } + +} \ No newline at end of file