org.grails.datastore.gorm.neo4j.Neo4jDatastore.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.datastore.gorm.neo4j.Neo4jDatastore.java

Source

/* Copyright (C) 2010 SpringSource
 *
 * 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.grails.datastore.gorm.neo4j;

import groovy.lang.Closure;
import org.grails.datastore.gorm.GormEnhancer;
import org.grails.datastore.gorm.GormInstanceApi;
import org.grails.datastore.gorm.GormStaticApi;
import org.grails.datastore.gorm.events.AutoTimestampEventListener;
import org.grails.datastore.gorm.events.ConfigurableApplicationEventPublisher;
import org.grails.datastore.gorm.events.DefaultApplicationEventPublisher;
import org.grails.datastore.gorm.events.DomainEventListener;
import org.grails.datastore.gorm.neo4j.connections.Neo4jConnectionSourceFactory;
import org.grails.datastore.gorm.neo4j.connections.Neo4jConnectionSourceSettings;
import org.grails.datastore.gorm.neo4j.connections.Neo4jConnectionSourceSettingsBuilder;
import org.grails.datastore.gorm.validation.constraints.MappingContextAwareConstraintFactory;
import org.grails.datastore.gorm.validation.constraints.builtin.UniqueConstraint;
import org.grails.datastore.gorm.validation.constraints.registry.DefaultValidatorRegistry;
import org.grails.datastore.mapping.config.Property;
import org.grails.datastore.mapping.config.Settings;
import org.grails.datastore.mapping.core.AbstractDatastore;
import org.grails.datastore.mapping.core.Datastore;
import org.grails.datastore.mapping.core.DatastoreUtils;
import org.grails.datastore.mapping.core.StatelessDatastore;
import org.grails.datastore.mapping.core.connections.*;
import org.grails.datastore.mapping.core.exceptions.ConfigurationException;
import org.grails.datastore.mapping.graph.GraphDatastore;
import org.grails.datastore.mapping.model.DatastoreConfigurationException;
import org.grails.datastore.mapping.model.MappingContext;
import org.grails.datastore.mapping.model.PersistentEntity;
import org.grails.datastore.mapping.model.PersistentProperty;
import org.grails.datastore.mapping.model.types.Simple;
import org.grails.datastore.mapping.multitenancy.MultiTenancySettings;
import org.grails.datastore.mapping.multitenancy.MultiTenantCapableDatastore;
import org.grails.datastore.mapping.multitenancy.TenantResolver;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.Transaction;
import org.neo4j.driver.v1.exceptions.Neo4jException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.StandardEnvironment;

import javax.annotation.PreDestroy;
import javax.persistence.FlushModeType;
import java.io.Closeable;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;

/**
 * Datastore implementation for Neo4j backend
 *
 * @author Stefan Armbruster (stefan@armbruster-it.de)
 * @author Graeme Rocher
 *
 * @since 1.0
 */
public class Neo4jDatastore extends AbstractDatastore implements Closeable, StatelessDatastore, GraphDatastore,
        Settings, MultiTenantCapableDatastore<Driver, Neo4jConnectionSourceSettings> {

    private static Logger log = LoggerFactory.getLogger(Neo4jDatastore.class);

    protected boolean skipIndexSetup = false;
    protected final Driver boltDriver;
    protected final FlushModeType defaultFlushMode;
    protected final ConfigurableApplicationEventPublisher eventPublisher;
    protected final Neo4jDatastoreTransactionManager transactionManager;
    protected final GormEnhancer gormEnhancer;
    protected final ConnectionSources<Driver, Neo4jConnectionSourceSettings> connectionSources;
    protected final Map<String, Neo4jDatastore> datastoresByConnectionSource = new LinkedHashMap<>();
    protected final TenantResolver tenantResolver;
    protected final MultiTenancySettings.MultiTenancyMode multiTenancyMode;

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param mappingContext The {@link MappingContext} which contains information about the mapped classes
     * @param eventPublisher The Spring ApplicationContext
     */
    public Neo4jDatastore(ConnectionSources<Driver, Neo4jConnectionSourceSettings> connectionSources,
            Neo4jMappingContext mappingContext, ConfigurableApplicationEventPublisher eventPublisher) {
        super(mappingContext, connectionSources.getBaseConfiguration(), null);
        this.connectionSources = connectionSources;
        ConnectionSource<Driver, Neo4jConnectionSourceSettings> defaultConnectionSource = connectionSources
                .getDefaultConnectionSource();
        Neo4jConnectionSourceSettings settings = defaultConnectionSource.getSettings();
        MultiTenancySettings multiTenancySettings = settings.getMultiTenancy();

        this.boltDriver = defaultConnectionSource.getSource();
        this.eventPublisher = eventPublisher;
        this.defaultFlushMode = settings.getFlushMode();
        this.skipIndexSetup = !settings.isBuildIndex();
        this.multiTenancyMode = multiTenancySettings.getMode();
        this.tenantResolver = multiTenancySettings.getTenantResolver();

        if (!skipIndexSetup) {
            setupIndexing();
        }

        transactionManager = new Neo4jDatastoreTransactionManager(this);
        if (!(connectionSources instanceof SingletonConnectionSources)) {

            Iterable<ConnectionSource<Driver, Neo4jConnectionSourceSettings>> allConnectionSources = connectionSources
                    .getAllConnectionSources();
            for (ConnectionSource<Driver, Neo4jConnectionSourceSettings> connectionSource : allConnectionSources) {
                SingletonConnectionSources singletonConnectionSources = new SingletonConnectionSources(
                        connectionSource, connectionSources.getBaseConfiguration());
                Neo4jDatastore childDatastore;

                if (ConnectionSource.DEFAULT.equals(connectionSource.getName())) {
                    childDatastore = this;
                } else {
                    childDatastore = new Neo4jDatastore(singletonConnectionSources, mappingContext,
                            eventPublisher) {
                        @Override
                        protected GormEnhancer initialize(Neo4jConnectionSourceSettings settings) {
                            return null;
                        }
                    };
                }
                datastoresByConnectionSource.put(connectionSource.getName(), childDatastore);
            }
        }

        this.gormEnhancer = initialize(settings);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param eventPublisher The Spring ApplicationContext
     * @param classes The persistent classes
     */
    public Neo4jDatastore(ConnectionSources<Driver, Neo4jConnectionSourceSettings> connectionSources,
            ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
        this(connectionSources, createMappingContext(connectionSources, classes), eventPublisher);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param classes The persistent classes
     */
    public Neo4jDatastore(ConnectionSources<Driver, Neo4jConnectionSourceSettings> connectionSources,
            Class... classes) {
        this(connectionSources, createMappingContext(connectionSources, classes),
                new DefaultApplicationEventPublisher());
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param boltDriver The driver
     * @param configuration The configuration for the datastore
     * @param eventPublisher The Spring ApplicationContext
     * @param classes The persistent classes
     */
    public Neo4jDatastore(Driver boltDriver, PropertyResolver configuration,
            ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
        this(createDefaultConnectionSources(boltDriver, configuration), eventPublisher, classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param boltDriver The driver
     * @param eventPublisher The Spring ApplicationContext
     * @param classes The persistent classes
     */
    public Neo4jDatastore(Driver boltDriver, ConfigurableApplicationEventPublisher eventPublisher,
            Class... classes) {
        this(createDefaultConnectionSources(boltDriver, DatastoreUtils.createPropertyResolver(null)),
                eventPublisher, classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param boltDriver The driver
     * @param configuration The configuration for the datastore
     * @param classes The persistent classes
     */
    public Neo4jDatastore(Driver boltDriver, PropertyResolver configuration, Class... classes) {
        this(boltDriver, configuration, new DefaultApplicationEventPublisher(), classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param boltDriver The driver
     * @param classes The persistent classes
     */
    public Neo4jDatastore(Driver boltDriver, Class... classes) {
        this(boltDriver, DatastoreUtils.createPropertyResolver(null), new DefaultApplicationEventPublisher(),
                classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param configuration The configuration for the datastore
     * @param connectionSourceFactory The {@link Neo4jConnectionSourceFactory} to use to configure Neo4j
     * @param eventPublisher The Spring ApplicationContext
     * @param classes The persistent classes
     */
    public Neo4jDatastore(PropertyResolver configuration, Neo4jConnectionSourceFactory connectionSourceFactory,
            ConfigurableApplicationEventPublisher eventPublisher, Class... classes) {
        this(ConnectionSourcesInitializer.create(connectionSourceFactory, configuration), eventPublisher, classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param configuration The configuration for the datastore
     * @param eventPublisher The Spring ApplicationContext
     * @param classes The persistent classes
     */
    public Neo4jDatastore(PropertyResolver configuration, ConfigurableApplicationEventPublisher eventPublisher,
            Class... classes) {
        this(ConnectionSourcesInitializer.create(new Neo4jConnectionSourceFactory(), configuration), eventPublisher,
                classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param configuration The configuration for the datastore
     * @param classes The persistent classes
     */
    public Neo4jDatastore(PropertyResolver configuration, Class... classes) {
        this(configuration, new DefaultApplicationEventPublisher(), classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param classes The persistent classes
     */
    public Neo4jDatastore(Class... classes) {
        this(mapToPropertyResolver(null), classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param configuration The configuration
     * @param classes The persistent classes
     */
    public Neo4jDatastore(Map<String, Object> configuration, ConfigurableApplicationEventPublisher eventPublisher,
            Class... classes) {
        this(mapToPropertyResolver(configuration), eventPublisher, classes);
    }

    /**
     * Configures a new {@link Neo4jDatastore} for the given arguments
     *
     * @param configuration The configuration
     * @param classes The persistent classes
     */
    public Neo4jDatastore(Map<String, Object> configuration, Class... classes) {
        this(configuration, new DefaultApplicationEventPublisher(), classes);
    }

    /**
     * @return The transaction manager
     */
    public Neo4jDatastoreTransactionManager getTransactionManager() {
        return transactionManager;
    }

    @Override
    public ConfigurableApplicationEventPublisher getApplicationEventPublisher() {
        return this.eventPublisher;
    }

    /**
     * Creates the connection sources for an existing {@link Driver}
     *
     * @param driver The {@link Driver}
     * @param configuration The configuration
     * @return The {@link ConnectionSources}
     */
    protected static ConnectionSources<Driver, Neo4jConnectionSourceSettings> createDefaultConnectionSources(
            Driver driver, PropertyResolver configuration) {
        Neo4jConnectionSourceSettingsBuilder builder = new Neo4jConnectionSourceSettingsBuilder(configuration);
        Neo4jConnectionSourceSettings settings = builder.build();
        ConnectionSource<Driver, Neo4jConnectionSourceSettings> defaultConnectionSource = new DefaultConnectionSource<>(
                ConnectionSource.DEFAULT, driver, settings);
        return new InMemoryConnectionSources<>(defaultConnectionSource, new Neo4jConnectionSourceFactory(),
                configuration);
    }

    protected static Neo4jMappingContext createMappingContext(
            ConnectionSources<Driver, Neo4jConnectionSourceSettings> connectionSources, Class... classes) {
        ConnectionSource<Driver, Neo4jConnectionSourceSettings> defaultConnectionSource = connectionSources
                .getDefaultConnectionSource();
        Neo4jMappingContext neo4jMappingContext = new Neo4jMappingContext(defaultConnectionSource.getSettings(),
                classes);
        PropertyResolver configuration = connectionSources.getBaseConfiguration();
        DefaultValidatorRegistry defaultValidatorRegistry = new DefaultValidatorRegistry(neo4jMappingContext,
                configuration);
        defaultValidatorRegistry.addConstraintFactory(new MappingContextAwareConstraintFactory(
                UniqueConstraint.class, defaultValidatorRegistry.getMessageSource(), neo4jMappingContext));
        neo4jMappingContext.setValidatorRegistry(defaultValidatorRegistry);
        return neo4jMappingContext;
    }

    protected void registerEventListeners(ConfigurableApplicationEventPublisher eventPublisher) {
        eventPublisher.addApplicationListener(new DomainEventListener(this));
        eventPublisher.addApplicationListener(new AutoTimestampEventListener(this));
    }

    protected GormEnhancer initialize(Neo4jConnectionSourceSettings settings) {
        registerEventListeners(this.eventPublisher);

        this.mappingContext.addMappingContextListener(new MappingContext.Listener() {
            @Override
            public void persistentEntityAdded(PersistentEntity entity) {
                gormEnhancer.registerEntity(entity);
            }
        });

        return new GormEnhancer(this, transactionManager, settings) {

            @Override
            protected <D> GormStaticApi<D> getStaticApi(Class<D> cls, String qualifier) {
                Neo4jDatastore neo4jDatastore = getDatastoreForQualifier(cls, qualifier);
                return new GormStaticApi<>(cls, neo4jDatastore, getFinders(),
                        neo4jDatastore.getTransactionManager());
            }

            @Override
            protected <D> GormInstanceApi<D> getInstanceApi(Class<D> cls, String qualifier) {
                Neo4jDatastore neo4jDatastore = getDatastoreForQualifier(cls, qualifier);
                return new GormInstanceApi<>(cls, neo4jDatastore);
            }

            private <D> Neo4jDatastore getDatastoreForQualifier(Class<D> cls, String qualifier) {
                String defaultConnectionSourceName = ConnectionSourcesSupport
                        .getDefaultConnectionSourceName(getMappingContext().getPersistentEntity(cls.getName()));
                boolean isDefaultQualifier = qualifier.equals(ConnectionSource.DEFAULT);
                if (isDefaultQualifier && defaultConnectionSourceName.equals(ConnectionSource.DEFAULT)) {
                    return Neo4jDatastore.this;
                } else {
                    if (isDefaultQualifier) {
                        qualifier = defaultConnectionSourceName;
                    }
                    ConnectionSource<Driver, Neo4jConnectionSourceSettings> connectionSource = connectionSources
                            .getConnectionSource(qualifier);
                    if (connectionSource == null) {
                        throw new ConfigurationException("Invalid connection [" + defaultConnectionSourceName
                                + "] configured for class [" + cls + "]");
                    }
                    return Neo4jDatastore.this.datastoresByConnectionSource.get(qualifier);
                }
            }
        };
    }

    public void setSkipIndexSetup(boolean skipIndexSetup) {
        this.skipIndexSetup = skipIndexSetup;
    }

    @Override
    protected org.grails.datastore.mapping.core.Session createSession(PropertyResolver connectionDetails) {
        final Neo4jSession neo4jSession = new Neo4jSession(this, mappingContext, eventPublisher, false, boltDriver);
        neo4jSession.setFlushMode(defaultFlushMode);
        return neo4jSession;
    }

    public void setupIndexing() {
        if (skipIndexSetup)
            return;
        List<String> schemaStrings = new ArrayList<String>(); // using set to avoid duplicate index creation

        for (PersistentEntity persistentEntity : mappingContext.getPersistentEntities()) {
            if (persistentEntity.isExternal())
                continue;

            if (log.isDebugEnabled()) {
                log.debug("Setting up indexing for entity " + persistentEntity.getName());
            }
            final GraphPersistentEntity graphPersistentEntity = (GraphPersistentEntity) persistentEntity;
            for (String label : graphPersistentEntity.getLabels()) {
                StringBuilder sb = new StringBuilder();
                if (graphPersistentEntity.getIdGenerator() != null) {
                    sb.append("CREATE CONSTRAINT ON (n:").append(label).append(") ASSERT n.")
                            .append(CypherBuilder.IDENTIFIER).append(" IS UNIQUE");
                    schemaStrings.add(sb.toString());
                }

                for (PersistentProperty persistentProperty : persistentEntity.getPersistentProperties()) {
                    Property mappedForm = persistentProperty.getMapping().getMappedForm();
                    if ((persistentProperty instanceof Simple) && (mappedForm != null)) {

                        if (mappedForm.isUnique()) {
                            sb = new StringBuilder();

                            sb.append("CREATE CONSTRAINT ON (n:").append(label).append(") ASSERT n.")
                                    .append(persistentProperty.getName()).append(" IS UNIQUE");
                            schemaStrings.add(sb.toString());
                        } else if (mappedForm.isIndex()) {
                            sb = new StringBuilder();
                            sb.append("CREATE INDEX ON :").append(label).append("(")
                                    .append(persistentProperty.getName()).append(")");
                            schemaStrings.add(sb.toString());
                            if (log.isDebugEnabled()) {
                                log.debug("setting up indexing for " + label + " property "
                                        + persistentProperty.getName());
                            }
                        }
                    }
                }
            }
        }

        org.neo4j.driver.v1.Session boltSession = boltDriver.session();

        final Transaction transaction = boltSession.beginTransaction();
        ;
        try {
            for (String cypher : schemaStrings) {
                if (log.isDebugEnabled()) {
                    log.debug("CREATE INDEX Cypher [{}]", cypher);
                }
                transaction.run(cypher);
                transaction.success();
            }
        } catch (Throwable e) {
            log.error("Error creating Neo4j index: " + e.getMessage(), e);
            transaction.failure();
            throw new DatastoreConfigurationException("Error creating Neo4j index: " + e.getMessage(), e);
        } finally {
            transaction.close();
        }
        if (log.isDebugEnabled()) {
            log.debug("done setting up indexes");
        }
    }

    /**
     * @return The {@link Driver} used by this datastore
     */
    public Driver getBoltDriver() {
        return boltDriver;
    }

    @Override
    @PreDestroy
    public void close() throws IOException {
        try {
            try {
                gormEnhancer.close();
            } catch (Throwable e) {
                // ignore
            }
            try {
                connectionSources.close();
            } catch (Neo4jException e) {
                log.error("Error shutting down Bolt driver: " + e.getMessage(), e);
            }
            super.destroy();
        } catch (Exception e) {
            throw new IOException("Error shutting down Neo4j datastore", e);
        }
    }

    @Override
    public ConnectionSources<Driver, Neo4jConnectionSourceSettings> getConnectionSources() {
        return this.connectionSources;
    }

    @Override
    public MultiTenancySettings.MultiTenancyMode getMultiTenancyMode() {
        return this.multiTenancyMode;
    }

    @Override
    public TenantResolver getTenantResolver() {
        return this.tenantResolver;
    }

    @Override
    public Neo4jDatastore getDatastoreForTenantId(Serializable tenantId) {
        if (getMultiTenancyMode() == MultiTenancySettings.MultiTenancyMode.DATABASE) {
            return this.datastoresByConnectionSource.get(tenantId.toString());
        }
        return this;
    }

    @Override
    public <T1> T1 withNewSession(Serializable tenantId, Closure<T1> callable) {
        Neo4jDatastore neo4jDatastore = getDatastoreForTenantId(tenantId);
        org.grails.datastore.mapping.core.Session session = neo4jDatastore.connect();
        try {
            DatastoreUtils.bindNewSession(session);
            return callable.call(session);
        } finally {
            DatastoreUtils.unbindSession(session);
        }
    }
}