org.hawkular.inventory.impl.tinkerpop.sql.SqlGraphProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.hawkular.inventory.impl.tinkerpop.sql.SqlGraphProvider.java

Source

/*
 * Copyright 2015-2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.hawkular.inventory.impl.tinkerpop.sql;

import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import javax.naming.InitialContext;
import javax.sql.DataSource;

import org.apache.commons.configuration.MapConfiguration;
import org.hawkular.inventory.api.Configuration;
import org.hawkular.inventory.api.EntityAlreadyExistsException;
import org.hawkular.inventory.api.RelationAlreadyExistsException;
import org.hawkular.inventory.api.filters.RelationFilter;
import org.hawkular.inventory.api.model.Relationship;
import org.hawkular.inventory.impl.tinkerpop.spi.GraphProvider;
import org.hawkular.inventory.impl.tinkerpop.spi.IndexSpec;
import org.hawkular.inventory.impl.tinkerpop.sql.impl.InsertException;
import org.hawkular.inventory.impl.tinkerpop.sql.impl.SqlGraph;
import org.hawkular.inventory.paths.CanonicalPath;

import com.tinkerpop.blueprints.Edge;
import com.tinkerpop.blueprints.Parameter;
import com.tinkerpop.blueprints.TransactionalGraph;
import com.tinkerpop.blueprints.Vertex;

/**
 * This is a "toy" provider for Hawkular that uses the primitive Blueprints implementation for an RDBMS. It only
 * supports H2 and Postgres and should not be used for anything but playful experiments.
 * <p>
 * That said, its main use is for checking the correct transactional behavior of Hawkular, because especially H2
 * seems to be quite sensitive about accessing ResultSets of closed transactions etc, which is a great testbed for
 * Hawkular's manual transaction handling.
 *
 * @author Lukas Krejci
 * @since 0.13.0
 */
public class SqlGraphProvider implements GraphProvider {

    @Override
    public boolean isPreferringBigTransactions() {
        return false;
    }

    @Override
    public boolean isUniqueIndexSupported() {
        return true;
    }

    @Override
    public boolean needsDraining() {
        return true;
    }

    @Override
    public SqlGraph instantiateGraph(Configuration configuration) {
        try {
            Map<String, String> conf = configuration.prefixedWith("sql.")
                    .getImplementationConfiguration(sysPropsAsProperties());

            String jndi = conf.get("sql.datasource.jndi");
            if (jndi == null || jndi.isEmpty()) {
                Log.LOG.iUsingJdbcUrl(conf.get("sql.datasource.url"));
                return new SqlGraph(new MapConfiguration(conf));
            } else {
                InitialContext ctx = new InitialContext();
                DataSource ds = (DataSource) ctx.lookup(jndi);
                Log.LOG.iUsingDatasource(jndi);
                return new SqlGraph(ds, new MapConfiguration(conf));
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Could not instantiate the SQL graph.", e);
        }
    }

    @Override
    public void ensureIndices(TransactionalGraph graph, IndexSpec... indexSpecs) {
        try {
            SqlGraph sqlg = (SqlGraph) graph;

            sqlg.createSchemaIfNeeded();

            Set<String> vertexIndices = sqlg.getIndexedKeys(Vertex.class);
            Set<String> edgeIndices = sqlg.getIndexedKeys(Edge.class);
            ArrayList<IndexSpec> specs = new ArrayList<>(Arrays.asList(indexSpecs));

            BiConsumer<IndexSpec, Consumer<IndexSpec.Property>> indexChecker = (is, indexMutator) -> {
                IndexSpec.Property prop = is.getProperties().iterator().next();
                if (!prop.isUnique()) {
                    return;
                }

                Set<String> indices = is.getElementType().equals(Edge.class) ? edgeIndices : vertexIndices;

                if (indices.contains(prop.getName())) {
                    return;
                }

                indexMutator.accept(prop);
            };

            Iterator<IndexSpec> it = specs.iterator();
            while (it.hasNext()) {
                IndexSpec is = it.next();

                if (is.getProperties().stream().filter(IndexSpec.Property::isUnique).count() > 1) {
                    throw new IllegalArgumentException(
                            "SQL Graph doesn't support unique indices over multiple " + "properties");
                }

                it.remove();

                indexChecker.accept(is,
                        prop -> sqlg.createKeyIndex(prop.getName(), is.getElementType(), (Parameter[]) null));
            }

            //now remove those that are defined but no longer needed
            specs.forEach(
                    is -> indexChecker.accept(is, prop -> sqlg.dropKeyIndex(prop.getName(), is.getElementType())));

            sqlg.commit();
        } catch (SQLException | IOException e) {
            throw new IllegalStateException("Could not create the database schema and indices.", e);
        }
    }

    @Override
    public RuntimeException translateException(RuntimeException inputException, CanonicalPath affectedPath) {
        if (inputException instanceof InsertException) {
            if (Relationship.class.equals(affectedPath.getSegment().getElementType())) {
                throw new RelationAlreadyExistsException(inputException, null, RelationFilter.pathTo(affectedPath));
            } else {
                return new EntityAlreadyExistsException(inputException, affectedPath);
            }
        } else {
            return inputException;
        }
    }

    private static Set<Configuration.Property> sysPropsAsProperties() {
        return System.getProperties().entrySet().stream().map(e -> new Configuration.Property() {
            @Override
            public String getPropertyName() {
                return (String) e.getKey();
            }

            @Override
            public List<String> getSystemPropertyNames() {
                return Collections.singletonList((String) e.getKey());
            }
        }).collect(Collectors.toSet());
    }
}