/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*/
package org.neo4j.ogm.drivers.embedded.driver;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.io.FileUtils;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.graphdb.factory.HighlyAvailableGraphDatabaseFactory;
import org.neo4j.ogm.config.Configuration;
import org.neo4j.ogm.driver.AbstractConfigurableDriver;
import org.neo4j.ogm.drivers.embedded.request.EmbeddedRequest;
import org.neo4j.ogm.drivers.embedded.transaction.EmbeddedTransaction;
import org.neo4j.ogm.exception.ConnectionException;
import org.neo4j.ogm.request.Request;
import org.neo4j.ogm.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author vince
*/
public class EmbeddedDriver extends AbstractConfigurableDriver {
private GraphDatabaseService graphDatabaseService;
private final Logger logger = LoggerFactory.getLogger(EmbeddedDriver.class);
// required for service loader mechanism
public EmbeddedDriver() {
}
@Override
public synchronized void configure(Configuration config) {
super.configure(config);
try {
String fileStoreUri = config.getURI();
// if no URI is set, create a temporary folder for the graph db
// that will persist only for the duration of the JVM
// This is effectively what the ImpermanentDatabase does.
if (fileStoreUri == null) {
fileStoreUri = createTemporaryFileStore();
} else {
createPermanentFileStore(fileStoreUri);
}
File file = new File(new URI(fileStoreUri));
if (!file.exists()) {
throw new RuntimeException("Could not create/open filestore: " + fileStoreUri);
}
// do we want to start a HA instance or a community instance?
String haPropertiesFileName = config.getNeo4jHaPropertiesFile();
if (haPropertiesFileName != null) {
setHAGraphDatabase(file, Thread.currentThread().getContextClassLoader().getResource(haPropertiesFileName));
} else {
setGraphDatabase(file);
}
} catch (Exception e) {
throw new ConnectionException("Error connecting to embedded graph", e);
}
}
private void setHAGraphDatabase(File file, URL propertiesFileURL) {
graphDatabaseService = new HighlyAvailableGraphDatabaseFactory().newEmbeddedDatabaseBuilder(file).loadPropertiesFromURL(propertiesFileURL).newGraphDatabase();
}
private void setGraphDatabase(File file) {
graphDatabaseService = new GraphDatabaseFactory().newEmbeddedDatabase(file);
}
@Override
public Transaction newTransaction(Transaction.Type type, String bookmark) {
return new EmbeddedTransaction(transactionManager, nativeTransaction(), type);
}
@Override
public synchronized void close() {
if (graphDatabaseService != null) {
logger.info("Shutting down Embedded driver {} ", graphDatabaseService);
graphDatabaseService.shutdown();
graphDatabaseService = null;
}
}
public GraphDatabaseService getGraphDatabaseService() {
return graphDatabaseService;
}
@Override
public Request request() {
return new EmbeddedRequest(graphDatabaseService, transactionManager);
}
private org.neo4j.graphdb.Transaction nativeTransaction() {
org.neo4j.graphdb.Transaction nativeTransaction;
Transaction tx = transactionManager.getCurrentTransaction();
if (tx != null) {
logger.debug("Using current transaction: {}", tx);
nativeTransaction = ((EmbeddedTransaction) tx).getNativeTransaction();
} else {
logger.debug("No current transaction, starting a new one");
nativeTransaction = graphDatabaseService.beginTx();
}
logger.debug("Native transaction: {}", nativeTransaction);
return nativeTransaction;
}
private String createTemporaryFileStore() {
try {
Path path = Files.createTempDirectory("neo4j.db");
final File f = path.toFile();
URI uri = f.toURI();
final String fileStoreUri = uri.toString();
logger.warn("Creating temporary file store: " + fileStoreUri);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
close();
try {
logger.warn("Deleting temporary file store: " + fileStoreUri);
FileUtils.deleteDirectory(f);
} catch (IOException e) {
throw new RuntimeException("Failed to delete temporary files in " + fileStoreUri);
}
}));
return fileStoreUri;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void createPermanentFileStore(String strPath) {
try {
URI uri = new URI(strPath);
File file = new File(uri);
if (!file.exists()) {
Path graphDir = Files.createDirectories(Paths.get(uri.getRawPath()));
logger.warn("Creating new permanent file store: " + graphDir.toString());
}
Runtime.getRuntime().addShutdownHook(new Thread(this::close));
} catch (FileAlreadyExistsException e) {
logger.warn("Using existing permanent file store: " + strPath);
} catch (IOException | URISyntaxException ioe) {
throw new RuntimeException(ioe);
}
}
}