package nl.knaw.huygens.alexandria.service; import static java.util.stream.Collectors.toList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; /* * #%L * alexandria-service-tinkerpop-titan * ======= * Copyright (C) 2015 - 2017 Huygens ING (KNAW) * ======= * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ import javax.inject.Inject; import javax.inject.Singleton; import org.apache.commons.lang3.text.WordUtils; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Element; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.thinkaurelius.titan.core.PropertyKey; import com.thinkaurelius.titan.core.TitanFactory; import com.thinkaurelius.titan.core.TitanGraph; import com.thinkaurelius.titan.core.VertexLabel; import com.thinkaurelius.titan.core.schema.ConsistencyModifier; import com.thinkaurelius.titan.core.schema.SchemaAction; import com.thinkaurelius.titan.core.schema.SchemaStatus; import com.thinkaurelius.titan.core.schema.TitanGraphIndex; import com.thinkaurelius.titan.core.schema.TitanManagement; import com.thinkaurelius.titan.core.schema.TitanManagement.IndexBuilder; import com.thinkaurelius.titan.core.util.TitanCleanup; import com.thinkaurelius.titan.graphdb.database.management.GraphIndexStatusReport; import com.thinkaurelius.titan.graphdb.database.management.ManagementSystem; import nl.knaw.huygens.alexandria.config.AlexandriaConfiguration; import nl.knaw.huygens.alexandria.endpoint.LocationBuilder; import nl.knaw.huygens.alexandria.storage.Storage; import nl.knaw.huygens.alexandria.util.StreamUtil; @Singleton public class TitanService extends TinkerPopService { private static final Logger LOG = LoggerFactory.getLogger(TitanService.class); private static final boolean UNIQUE = true; private static final String PROP_TYPE = "type"; private static final String PROP_WHO = "who"; private static final String PROP_STATE = "state"; enum VertexCompositeIndex { IDX_ANY_STATE(null, PROP_STATE, !UNIQUE), // IDX_RESOURCE_UUID("Resource", Storage.IDENTIFIER_PROPERTY, UNIQUE), // // IDX_RESOURCE_CARGO("Resource", PROP_CARGO, !UNIQUE), // IDX_ANNOTATION_UUID("Annotation", Storage.IDENTIFIER_PROPERTY, UNIQUE), // IDX_ANNOTATION_WHO("Annotation", PROP_WHO, !UNIQUE), // IDX_ANNOTATIONBODY_UUID("AnnotationBody", Storage.IDENTIFIER_PROPERTY, UNIQUE), // IDX_ANNOTATIONBODY_TYPE("AnnotationBody", PROP_TYPE, !UNIQUE); public String label; public String property; public boolean unique; public String name; VertexCompositeIndex(String label, String property, boolean unique) { this.label = label; this.property = property; this.unique = unique; this.name = (label == null ? "" : label) + "By" + WordUtils.capitalize(property); } } private static TitanGraph titanGraph; private AlexandriaConfiguration configuration; static class IndexInfo { private String name; private String backingIndex; private Class<? extends Element> indexedElement; private SchemaStatus indexStatus; public IndexInfo(TitanGraphIndex index) { name = index.name(); backingIndex = index.getBackingIndex(); indexedElement = index.getIndexedElement(); PropertyKey[] fieldKeys = index.getFieldKeys(); indexStatus = index.getIndexStatus(fieldKeys[0]); } public String getBackingIndex() { return backingIndex; } public Class<? extends Element> getIndexedElement() { return indexedElement; } public SchemaStatus getIndexStatus() { return indexStatus; } public String getName() { return name; } } @Inject public TitanService(LocationBuilder locationBuilder, AlexandriaConfiguration configuration) { super(getStorage(configuration), locationBuilder); this.configuration = configuration; } @Override public Map<String, Object> getMetadata() { TitanManagement mgmt = titanGraph.openManagement(); Map<String, Object> metadata = super.getMetadata(); @SuppressWarnings("unchecked") Map<String, Object> storageMap = (Map<String, Object>) metadata.get("storage"); storageMap.put("vertexIndexes", indexInfo(mgmt, Vertex.class)); storageMap.put("edgeIndexes", indexInfo(mgmt, Edge.class)); return metadata; } private static Storage getStorage(AlexandriaConfiguration configuration) { titanGraph = TitanFactory.open(String.join(":", "berkeleyje", configuration.getStorageDirectory())); setIndexes(); return new Storage(titanGraph); } private List<IndexInfo> indexInfo(TitanManagement mgmt, Class<? extends Element> elementClass) { return StreamUtil.stream(mgmt.getGraphIndexes(elementClass))// .map(IndexInfo::new)// .collect(toList()); } private static void setIndexes() { List<String> reindex = Lists.newArrayList(); TitanManagement mgmt = titanGraph.openManagement(); for (VertexCompositeIndex compositeIndex : VertexCompositeIndex.values()) { boolean created = createIndexWhenAbsent(mgmt, compositeIndex); if (created) { reindex.add(compositeIndex.name); } } LOG.info("saving indexes"); mgmt.commit(); mgmt = titanGraph.openManagement(); LOG.info("wait for completion"); for (VertexCompositeIndex compositeIndex : VertexCompositeIndex.values()) { waitForCompletion(mgmt, compositeIndex); } mgmt.commit(); mgmt = titanGraph.openManagement(); for (String newIndex : reindex) { LOG.info("reindexing {}", newIndex); try { mgmt.updateIndex(mgmt.getGraphIndex(newIndex), SchemaAction.REINDEX).get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); throw new RuntimeException(e); } } mgmt.commit(); titanGraph.tx().rollback(); } private static boolean createIndexWhenAbsent(TitanManagement mgmt, VertexCompositeIndex compositeIndex) { String name = compositeIndex.name; if (!mgmt.containsGraphIndex(name)) { String property = compositeIndex.property; String label = compositeIndex.label; boolean unique = compositeIndex.unique; LOG.info("building {} index '{}' for label '{}' + property '{}'", unique ? "unique" : "non-unique", name, label, property); PropertyKey uuidKey = mgmt.containsPropertyKey(property)// ? mgmt.getPropertyKey(property)// : mgmt.makePropertyKey(property).dataType(String.class).make(); IndexBuilder indexBuilder = mgmt.buildIndex(name, Vertex.class)// .addKey(uuidKey); if (label != null) { VertexLabel vertexLabel = mgmt.containsVertexLabel(label)// ? mgmt.getVertexLabel(label)// : mgmt.makeVertexLabel(label).make(); indexBuilder = indexBuilder.indexOnly(vertexLabel); } if (unique) { indexBuilder = indexBuilder.unique(); } TitanGraphIndex index = indexBuilder.buildCompositeIndex(); mgmt.setConsistency(index, ConsistencyModifier.LOCK); return true; } return false; } private static void waitForCompletion(TitanManagement mgmt, VertexCompositeIndex compositeIndex) { String name = compositeIndex.name; TitanGraphIndex graphIndex = mgmt.getGraphIndex(name); PropertyKey propertyKey = graphIndex.getFieldKeys()[0]; // For composite indexes, the propertyKey is ignored and the status of the index as a whole is returned if (!SchemaStatus.ENABLED.equals(graphIndex.getIndexStatus(propertyKey))) { try { GraphIndexStatusReport report = ManagementSystem.awaitGraphIndexStatus(titanGraph, name).call(); LOG.info("report={}", report); } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } } } @Override public void destroy() { // Log.info("destroy called"); super.destroy(); if (titanGraph.isOpen()) { LOG.info("closing {}:", titanGraph); titanGraph.close(); } // Log.info("destroy finished"); } @Override Storage clearGraph() { titanGraph.close(); TitanCleanup.clear(titanGraph); return getStorage(configuration); } }