/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr 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. * * Structr 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 Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.neo4j; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.util.Date; import org.structr.neo4j.wrapper.TransactionWrapper; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.factory.GraphDatabaseBuilder; import org.neo4j.graphdb.factory.GraphDatabaseFactory; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.shell.ShellSettings; import org.neo4j.tooling.GlobalGraphOperations; import org.structr.api.DatabaseService; import org.structr.api.graph.GraphProperties; import org.structr.api.util.Iterables; import org.structr.api.graph.Label; import org.structr.api.NativeResult; import org.structr.api.NotFoundException; import org.structr.api.graph.Node; import org.structr.api.NotInTransactionException; import org.structr.api.graph.Relationship; import org.structr.api.graph.RelationshipType; import org.structr.api.Transaction; import org.structr.api.config.Structr; import org.structr.api.index.Index; import org.structr.api.index.IndexManager; import org.structr.api.util.FixedSizeCache; import org.structr.neo4j.mapper.NodeMapper; import org.structr.neo4j.mapper.RelationshipMapper; import org.structr.neo4j.wrapper.NodeWrapper; import org.structr.neo4j.wrapper.RelationshipWrapper; import org.structr.neo4j.wrapper.Neo4jResultWrapper; /** * */ public class Neo4jDatabaseService implements DatabaseService, GraphProperties { private static final Logger logger = LoggerFactory.getLogger(Neo4jDatabaseService.class.getName()); public static final String RELATIONSHIP_CACHE_SIZE = "database.cache.relationship.size"; public static final String NODE_CACHE_SIZE = "database.cache.node.size"; public static final String NEO4J_SHELL_ENABLED = "neo4j.shell.enabled"; public static final String NEO4J_SHELL_PORT = "neo4j.shell.port"; public static final String NEO4J_PAGE_CACHE_MEMORY = "neo4j.pagecache.memory"; private static final Map<String, RelationshipType> relTypeCache = new ConcurrentHashMap<>(); private static final Map<String, Label> labelCache = new ConcurrentHashMap<>(); private FixedSizeCache<Long, RelationshipWrapper> relationshipCache = null; private FixedSizeCache<Long, NodeWrapper> nodeCache = null; private IndexManager relationshipIndexer = null; private IndexManager nodeIndexer = null; private GraphDatabaseService graphDb = null; private String databasePath = null; @Override public void initialize(final Properties config) { this.databasePath = config.getProperty(Structr.DATABASE_PATH); final int relationshipCacheSize = Integer.valueOf(config.getProperty(RELATIONSHIP_CACHE_SIZE, "10000")); if (relationshipCacheSize > 0) { logger.info("Relationship cache size set to {}", relationshipCacheSize); relationshipCache = new FixedSizeCache<>(relationshipCacheSize); } else { logger.info("Relationship cache disabled."); } final int nodeCacheSize = Integer.valueOf(config.getProperty(NODE_CACHE_SIZE, "10000")); if (nodeCacheSize > 0) { logger.info("Node cache size set to {}", nodeCacheSize); nodeCache = new FixedSizeCache<>(nodeCacheSize); } else { logger.info("Node cache disabled."); } final File confFile = new File(databasePath + "/neo4j.conf"); final GraphDatabaseBuilder builder = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(databasePath); // load additional settings if (confFile.exists()) { builder.loadPropertiesFromFile(confFile.getAbsolutePath()); } // neo4j remote shell configuration builder.setConfig(ShellSettings.remote_shell_enabled, config.getProperty(NEO4J_SHELL_ENABLED, "false")); builder.setConfig(ShellSettings.remote_shell_port, config.getProperty(NEO4J_SHELL_PORT, "1337")); // Neo4j page cache memory, default 64m builder.setConfig(GraphDatabaseSettings.pagecache_memory, config.getProperty(NEO4J_PAGE_CACHE_MEMORY, "64m")); logger.info("Initializing database ({}) ...", databasePath); graphDb = builder.newGraphDatabase(); } @Override public void shutdown() { graphDb.shutdown(); nodeCache.clear(); relationshipCache.clear(); } @Override public Transaction beginTx() { return new TransactionWrapper(graphDb.beginTx()); } @Override public Node createNode(final Set<String> labels, final Map<String, Object> properties) { try { return NodeWrapper.getWrapper(this, graphDb.createNode()); } catch (org.neo4j.graphdb.NotInTransactionException t) { throw new NotInTransactionException(t); } } @Override public Node getNodeById(long id) { try { return NodeWrapper.getWrapper(this, graphDb.getNodeById(id)); } catch (org.neo4j.graphdb.NotInTransactionException t) { throw new NotInTransactionException(t); } catch (org.neo4j.graphdb.NotFoundException t) { throw new NotFoundException(t); } } @Override public Relationship getRelationshipById(long id) { try { return RelationshipWrapper.getWrapper(this, graphDb.getRelationshipById(id)); } catch (org.neo4j.graphdb.NotInTransactionException t) { throw new NotInTransactionException(t); } catch (org.neo4j.graphdb.NotFoundException t) { throw new NotFoundException(t); } } @Override public Iterable<Node> getAllNodes() { try { return Iterables.map(new NodeMapper(this), GlobalGraphOperations.at(graphDb).getAllNodes()); } catch (org.neo4j.graphdb.NotInTransactionException t) { throw new NotInTransactionException(t); } catch (org.neo4j.graphdb.NotFoundException t) { throw new NotFoundException(t); } } @Override public Iterable<Relationship> getAllRelationships() { try { return Iterables.map(new RelationshipMapper(this), GlobalGraphOperations.at(graphDb).getAllRelationships()); } catch (org.neo4j.graphdb.NotInTransactionException t) { throw new NotInTransactionException(t); } catch (org.neo4j.graphdb.NotFoundException t) { throw new NotFoundException(t); } } @Override public GraphProperties getGlobalProperties() { return this; } @Override public Index<Node> nodeIndex() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public Index<Relationship> relationshipIndex() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public NativeResult execute(final String nativeQuery) { try { return new Neo4jResultWrapper<>(this, graphDb.execute(nativeQuery)); } catch (org.neo4j.graphdb.NotInTransactionException t) { throw new NotInTransactionException(t); } } @Override public NativeResult execute(final String nativeQuery, final Map<String, Object> parameters) { try { return new Neo4jResultWrapper<>(this, graphDb.execute(nativeQuery, parameters)); } catch (org.neo4j.graphdb.NotInTransactionException t) { throw new NotInTransactionException(t); } } @Override public <T> T forName(final Class<T> type, final String name) { if (Label.class.equals(type)) { return (T)getOrCreateLabel(name); } if (RelationshipType.class.equals(type)) { return (T)getOrCreateRelationshipType(name); } throw new RuntimeException("Cannot create object of type " + type); } public Label getOrCreateLabel(final String name) { Label label = labelCache.get(name); if (label == null) { label = new LabelImpl(name); labelCache.put(name, label); } return label; } public RelationshipType getOrCreateRelationshipType(final String name) { RelationshipType relType = relTypeCache.get(name); if (relType == null) { relType = new RelationshipTypeImpl(name); relTypeCache.put(name, relType); } return relType; } public GraphDatabaseService getGraphDb() { return graphDb; } public NodeWrapper getNodeFromCache(final long id) { if (nodeCache != null) { return nodeCache.get(id); } return null; } public void storeNodeInCache(final NodeWrapper node) { if (nodeCache != null) { nodeCache.put(node.getId(), node); } } public void removeNodeFromCache(final long id) { if (nodeCache != null) { nodeCache.remove(id); } } public RelationshipWrapper getRelationshipFromCache(final long id) { if (relationshipCache != null) { return relationshipCache.get(id); } return null; } public void storeRelationshipInCache(final RelationshipWrapper relationship) { if (relationshipCache != null) { relationshipCache.put(relationship.getId(), relationship); } } public void removeRelationshipFromCache(final long id) { if (relationshipCache != null) { relationshipCache.remove(id); } } // ----- interface GraphProperties ----- @Override public void setProperty(final String name, final Object value) { final Properties properties = new Properties(); final File propertiesFile = new File(databasePath + "/graph.properties"); try (final Reader reader = new FileReader(propertiesFile)) { properties.load(reader); } catch (IOException ioex) {} properties.setProperty(name, value.toString()); try (final Writer writer = new FileWriter(propertiesFile)) { properties.store(writer, "Created by Structr at " + new Date()); } catch (IOException ioex) { logger.warn("Unable to write properties file", ioex); } } @Override public Object getProperty(final String name) { final Properties properties = new Properties(); final File propertiesFile = new File(databasePath + "/graph.properties"); try (final Reader reader = new FileReader(propertiesFile)) { properties.load(reader); } catch (IOException ioex) {} return properties.getProperty(name); } @Override public void invalidateCache() { if (nodeCache != null) { nodeCache.clear(); } if (relationshipCache != null) { relationshipCache.clear(); } } @Override public boolean needsIndexRebuild() { return false; } // ----- nested classes ----- private static class LabelImpl implements Label { private String name = null; private LabelImpl(final String name) { this.name = name; } @Override public String name() { return name; } @Override public int hashCode() { return name.hashCode(); } @Override public boolean equals(final Object other) { if (other instanceof Label) { return other.hashCode() == hashCode(); } return false; } } private static class RelationshipTypeImpl implements RelationshipType { private String name = null; private RelationshipTypeImpl(final String name) { this.name = name; } @Override public String name() { return name; } @Override public int hashCode() { return name.hashCode(); } @Override public boolean equals(final Object other) { if (other instanceof RelationshipType) { return other.hashCode() == hashCode(); } return false; } } }