ai.grakn.engine.SystemKeyspace.java Source code

Java tutorial

Introduction

Here is the source code for ai.grakn.engine.SystemKeyspace.java

Source

/*
 * Grakn - A Distributed Semantic Database
 * Copyright (C) 2016  Grakn Labs Limited
 *
 * Grakn 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.
 *
 * Grakn 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 Grakn. If not, see <http://www.gnu.org/licenses/gpl.txt>.
 */

package ai.grakn.engine;

import ai.grakn.GraknGraph;
import ai.grakn.GraknTxType;
import ai.grakn.concept.EntityType;
import ai.grakn.concept.Label;
import ai.grakn.concept.Resource;
import ai.grakn.concept.ResourceType;
import ai.grakn.concept.Thing;
import ai.grakn.engine.factory.EngineGraknGraphFactory;
import ai.grakn.exception.GraknBackendException;
import ai.grakn.exception.GraphOperationException;
import ai.grakn.exception.InvalidGraphException;
import ai.grakn.graph.admin.GraknAdmin;
import ai.grakn.util.GraknVersion;
import com.google.common.base.Stopwatch;
import java.util.concurrent.ConcurrentHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>
 * Manages the system keyspace.
 * </p>
 * 
 * <p>
 * Used to populate the system ontology the first time the system keyspace
 * is created.
 * </p>
 * 
 * <p>
 * Used to populate the system keyspace with all newly create keyspaces as a
 * user opens them. We have no way to determining whether a keyspace with a
 * given name already exists or not. We maintain the list in our Grakn system
 * keyspace. An element is added to that list when there is an attempt to create
 * a graph from a factory bound to the keyspace name. The list is simply the
 * instances of the system entity type 'keyspace'. Nothing is ever removed from
 * that list. The set of known keyspaces is maintained in a static map so we
 * don't connect to the system keyspace every time a factory produces a new
 * graph. That means that we can't have several different factories (e.g. Janus
 * and in-memory Tinkerpop) at the same time sharing keyspace names. We can't
 * identify the factory builder by engineUrl and config because we don't know
 * what's inside the config, which is residing remotely at the engine!
 * </p>
 * 
 * @author borislav, fppt
 *
 */
public class SystemKeyspace {
    // This will eventually be configurable and obtained the same way the factory is obtained
    // from engine. For now, we just make sure Engine and Core use the same system keyspace name.
    // If there is a more natural home for this constant, feel free to put it there! (Boris)
    public static final String SYSTEM_GRAPH_NAME = "graknSystem";
    private static final String SYSTEM_VERSION = "system-version";
    public static final Label KEYSPACE_ENTITY = Label.of("keyspace");
    public static final Label KEYSPACE_RESOURCE = Label.of("keyspace-name");

    private static final Logger LOG = LoggerFactory.getLogger(SystemKeyspace.class);
    private final ConcurrentHashMap<String, Boolean> openSpaces;
    private final EngineGraknGraphFactory factory;

    public SystemKeyspace(EngineGraknGraphFactory factory) {
        this(factory, true);
    }

    public SystemKeyspace(EngineGraknGraphFactory factory, boolean loadSystemOntology) {
        this.factory = factory;
        this.openSpaces = new ConcurrentHashMap<>();
        if (loadSystemOntology) {
            loadSystemOntology();
        }
    }

    /**
     * Notify that we just opened a keyspace with the same engineUrl & config.
     */
    public boolean ensureKeyspaceInitialised(String keyspace) {
        if (openSpaces.containsKey(keyspace)) {
            return true;
        }

        try (GraknGraph graph = factory.getGraph(SYSTEM_GRAPH_NAME, GraknTxType.WRITE)) {
            ResourceType<String> keyspaceName = graph.getOntologyConcept(KEYSPACE_RESOURCE);
            if (keyspaceName == null) {
                throw GraknBackendException.initializationException(keyspace);
            }
            Resource<String> resource = keyspaceName.putResource(keyspace);
            if (resource.owner() == null) {
                graph.<EntityType>getOntologyConcept(KEYSPACE_ENTITY).addEntity().resource(resource);
            }
            graph.admin().commitNoLogs();
        } catch (InvalidGraphException e) {
            throw new RuntimeException("Could not add keyspace [" + keyspace + "] to system graph", e);
        }

        return true;
    }

    /**
     * Checks if the keyspace exists in the system. The persisted graph is checked each time because the graph
     * may have been deleted in another JVM.
     *
     * @param keyspace The keyspace which might be in the system
     * @return true if the keyspace is in the system
     */
    public boolean containsKeyspace(String keyspace) {
        try (GraknGraph graph = factory.getGraph(SYSTEM_GRAPH_NAME, GraknTxType.READ)) {
            return graph.getResourceType(KEYSPACE_RESOURCE.getValue()).getResource(keyspace) != null;
        }
    }

    /**
     * This is called when a graph is deleted via {@link GraknAdmin#delete()}.
     * This removes the keyspace of the deleted graph from the system graph
     *
     * @param keyspace the keyspace to be removed from the system graph
     */
    public boolean deleteKeyspace(String keyspace) {
        if (keyspace.equals(SYSTEM_GRAPH_NAME)) {
            return false;
        }

        try (GraknGraph graph = factory.getGraph(SYSTEM_GRAPH_NAME, GraknTxType.WRITE)) {
            ResourceType<String> keyspaceName = graph.getOntologyConcept(KEYSPACE_RESOURCE);
            Resource<String> resource = keyspaceName.getResource(keyspace);

            if (resource == null)
                return false;
            Thing thing = resource.owner();
            if (thing != null)
                thing.delete();
            resource.delete();

            openSpaces.remove(keyspace);

            graph.admin().commitNoLogs();
        }

        return true;
    }

    /**
     * Load the system ontology into a newly created system keyspace. Because the ontology
     * only consists of types, the inserts are idempotent and it is safe to load it
     * multiple times.
     */
    public void loadSystemOntology() {
        Stopwatch timer = Stopwatch.createStarted();
        try (GraknGraph graph = factory.getGraph(SYSTEM_GRAPH_NAME, GraknTxType.WRITE)) {
            if (graph.getOntologyConcept(KEYSPACE_ENTITY) != null) {
                checkVersion(graph);
                return;
            }
            LOG.info("No other version found, loading ontology for version {}", GraknVersion.VERSION);
            loadSystemOntology(graph);
            graph.getResourceType(SYSTEM_VERSION).putResource(GraknVersion.VERSION);
            graph.admin().commitNoLogs();
            LOG.info("Loaded system ontology to system keyspace. Took: {}", timer.stop());
        } catch (Exception e) {
            LOG.error("Error while loading system ontology in {}. The error was: {}", timer.stop(), e.getMessage(),
                    e);
            throw e;
        }
    }

    /**
     * Helper method which checks the version persisted in the system keyspace with the version of the running grakn
     * instance
     *
     * @throws ai.grakn.exception.GraphOperationException when the versions do not match
     */
    private void checkVersion(GraknGraph graph) {
        Resource existingVersion = graph.getResourceType(SYSTEM_VERSION).instances().iterator().next();
        if (!GraknVersion.VERSION.equals(existingVersion.getValue())) {
            throw GraphOperationException.versionMistmatch(existingVersion);
        } else {
            LOG.info("Found version {}", existingVersion.getValue());
        }
    }

    /**
     * Loads the system ontology inside the provided grakn graph.
     *
     * @param graph The graph to contain the system ontology
     */
    private void loadSystemOntology(GraknGraph graph) {
        //Keyspace data
        ResourceType<String> keyspaceName = graph.putResourceType("keyspace-name", ResourceType.DataType.STRING);
        graph.putEntityType("keyspace").key(keyspaceName);

        //User Data
        ResourceType<String> userName = graph.putResourceType("user-name", ResourceType.DataType.STRING);
        ResourceType<String> userPassword = graph.putResourceType("user-password", ResourceType.DataType.STRING);
        ResourceType<String> userPasswordSalt = graph.putResourceType("user-password-salt",
                ResourceType.DataType.STRING);
        ResourceType<String> userFirstName = graph.putResourceType("user-first-name", ResourceType.DataType.STRING);
        ResourceType<String> userLastName = graph.putResourceType("user-last-name", ResourceType.DataType.STRING);
        ResourceType<String> userEmail = graph.putResourceType("user-email", ResourceType.DataType.STRING);
        ResourceType<Boolean> userIsAdmin = graph.putResourceType("user-is-admin", ResourceType.DataType.BOOLEAN);

        graph.putEntityType("user").key(userName).resource(userPassword).resource(userPasswordSalt)
                .resource(userFirstName).resource(userLastName).resource(userEmail).resource(userIsAdmin);

        //System Version
        graph.putResourceType("system-version", ResourceType.DataType.STRING);
    }
}