├── .gitignore ├── README.md ├── src ├── main │ └── java │ │ └── org │ │ └── neo4j │ │ └── server │ │ └── extension │ │ └── test │ │ └── delete │ │ ├── DeleteDatabaseResource.java │ │ └── Neo4jDatabaseCleaner.java └── test │ └── java │ └── org │ └── neo4j │ └── server │ └── DeleteDatabaseTest.java └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | *.ipr 4 | *.iws 5 | *.iml 6 | *.zip 7 | target 8 | *.log 9 | tmp 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Just put the [test-delete-db-extension-VERSION.jar](https://github.com/jexp/neo4j-clean-remote-db-addon/archives/master) for your Neo4j-VERSION into neo4j-server/plugins and add this to the server properties file 2 | 3 | org.neo4j.server.thirdparty_jaxrs_classes=org.neo4j.server.extension.test.delete=/db/data/cleandb 4 | org.neo4j.server.thirdparty.delete.key=secret-key 5 | 6 | 7 | Then you can issue 8 | 9 | curl -X DELETE http://localhost:7474/db/data/cleandb/secret-key 10 | 11 | 12 | to delete the graph database without restarting the server 13 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/test/delete/DeleteDatabaseResource.java: -------------------------------------------------------------------------------- 1 | package org.neo4j.server.extension.test.delete; 2 | 3 | /** 4 | * @author mh 5 | * @since 27.02.11 6 | */ 7 | 8 | import org.apache.commons.configuration.Configuration; 9 | import org.apache.commons.io.FileUtils; 10 | import org.neo4j.kernel.AbstractGraphDatabase; 11 | import org.neo4j.kernel.EmbeddedGraphDatabase; 12 | import org.neo4j.kernel.GraphDatabaseAPI; 13 | import org.neo4j.server.database.Database; 14 | import org.neo4j.server.rest.domain.JsonHelper; 15 | 16 | import javax.ws.rs.DELETE; 17 | import javax.ws.rs.Path; 18 | import javax.ws.rs.PathParam; 19 | import javax.ws.rs.Produces; 20 | import javax.ws.rs.core.Context; 21 | import javax.ws.rs.core.MediaType; 22 | import javax.ws.rs.core.Response; 23 | import javax.ws.rs.core.Response.Status; 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | import java.util.logging.Logger; 29 | 30 | import static org.neo4j.server.configuration.Configurator.DATABASE_LOCATION_PROPERTY_KEY; 31 | 32 | @Path("/") 33 | public class DeleteDatabaseResource { 34 | 35 | private static final String CONFIG_DELETE_AUTH_KEY = "org.neo4j.server.thirdparty.delete.key"; 36 | public static final long MAX_NODES_TO_DELETE = 1000; 37 | private final Database database; 38 | private Configuration config; 39 | private Logger log = Logger.getLogger(DeleteDatabaseResource.class.getName()); 40 | 41 | public DeleteDatabaseResource(@Context Database database, @Context Configuration config) { 42 | this.database = database; 43 | this.config = config; 44 | } 45 | 46 | @DELETE 47 | @Path("/{key}") 48 | @Produces(MediaType.APPLICATION_JSON) 49 | public Response cleanDb(@PathParam("key") String deleteKey) { 50 | GraphDatabaseAPI graph = database.getGraph(); 51 | String configKey = config.getString(CONFIG_DELETE_AUTH_KEY); 52 | 53 | if (deleteKey == null || configKey == null || !deleteKey.equals(configKey)) { 54 | return Response.status(Status.UNAUTHORIZED).build(); 55 | } 56 | try { 57 | Map result = new Neo4jDatabaseCleaner(graph).cleanDb(MAX_NODES_TO_DELETE); 58 | if ((Long)result.get("nodes")>=MAX_NODES_TO_DELETE) { 59 | result.putAll(cleanDbDirectory(database)); 60 | } 61 | log.warning("Deleted Database: " + result); 62 | return Response.status(Status.OK).entity(JsonHelper.createJsonFrom(result)).build(); 63 | } catch (Throwable e) { 64 | return Response.status(Status.INTERNAL_SERVER_ERROR).entity(JsonHelper.createJsonFrom(e.getMessage())).build(); 65 | } 66 | } 67 | 68 | private Map cleanDbDirectory(Database database) throws Throwable { 69 | AbstractGraphDatabase graph = database.graph; 70 | String storeDir = graph.getStoreDir(); 71 | if (storeDir == null) { 72 | storeDir = config.getString(DATABASE_LOCATION_PROPERTY_KEY); 73 | } 74 | graph.shutdown(); 75 | Map result = removeDirectory(storeDir); 76 | 77 | // TODO wtf? 78 | // database.graph = new EmbeddedGraphDatabase(storeDir, graph.getKernelData().getConfigParams()); 79 | database.start(); 80 | 81 | return result; 82 | } 83 | 84 | private Map removeDirectory(String storeDir) throws IOException { 85 | File dir = new File(storeDir); 86 | Map result=new HashMap(); 87 | result.put("store-dir",dir); 88 | result.put("size", FileUtils.sizeOfDirectory(dir)); 89 | FileUtils.deleteDirectory(dir); 90 | return result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/neo4j/server/extension/test/delete/Neo4jDatabaseCleaner.java: -------------------------------------------------------------------------------- 1 | package org.neo4j.server.extension.test.delete; 2 | 3 | import org.neo4j.graphdb.Node; 4 | import org.neo4j.graphdb.PropertyContainer; 5 | import org.neo4j.graphdb.Relationship; 6 | import org.neo4j.graphdb.Transaction; 7 | import org.neo4j.graphdb.index.Index; 8 | import org.neo4j.graphdb.index.IndexManager; 9 | import org.neo4j.graphdb.index.RelationshipIndex; 10 | import org.neo4j.kernel.AbstractGraphDatabase; 11 | import org.neo4j.kernel.GraphDatabaseAPI; 12 | 13 | import java.lang.reflect.Field; 14 | import java.util.Arrays; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | 18 | /** 19 | * @author mh 20 | * @since 02.03.11 21 | */ 22 | public class Neo4jDatabaseCleaner { 23 | private GraphDatabaseAPI graph; 24 | 25 | public Neo4jDatabaseCleaner(GraphDatabaseAPI graph) { 26 | this.graph = graph; 27 | } 28 | 29 | public Map cleanDb() { 30 | return cleanDb(Long.MAX_VALUE); 31 | } 32 | 33 | public Map cleanDb(long maxNodesToDelete) { 34 | Map result = new HashMap(); 35 | Transaction tx = graph.beginTx(); 36 | try { 37 | clearIndex(result); 38 | removeNodes(result, maxNodesToDelete); 39 | tx.success(); 40 | } finally { 41 | tx.finish(); 42 | } 43 | return result; 44 | } 45 | 46 | private void removeNodes(Map result, long maxNodesToDelete) { 47 | Node refNode = graph.getReferenceNode(); 48 | long nodes = 0, relationships = 0; 49 | for (Node node : graph.getAllNodes()) { 50 | for (Relationship rel : node.getRelationships()) { 51 | rel.delete(); 52 | relationships++; 53 | } 54 | if (!refNode.equals(node)) { 55 | node.delete(); 56 | nodes++; 57 | } 58 | if (nodes >= maxNodesToDelete) break; 59 | } 60 | result.put("maxNodesToDelete", maxNodesToDelete); 61 | result.put("nodes", nodes); 62 | result.put("relationships", relationships); 63 | 64 | } 65 | 66 | private void clearIndex(Map result) { 67 | IndexManager indexManager = graph.index(); 68 | result.put("node-indexes", Arrays.asList(indexManager.nodeIndexNames())); 69 | result.put("relationship-indexes", Arrays.asList(indexManager.relationshipIndexNames())); 70 | try { 71 | for (String ix : indexManager.nodeIndexNames()) { 72 | final Index index = indexManager.forNodes(ix); 73 | getMutableIndex(index).delete(); 74 | } 75 | for (String ix : indexManager.relationshipIndexNames()) { 76 | final RelationshipIndex index = indexManager.forRelationships(ix); 77 | getMutableIndex(index).delete(); 78 | } 79 | } catch (UnsupportedOperationException uoe) { 80 | throw new RuntimeException("Implementation detail assumption failed for cleaning readonly indexes, please make sure that the version of this extension and the Neo4j server align"); 81 | } 82 | } 83 | 84 | private Index getMutableIndex(Index index) { 85 | final Class indexClass = index.getClass(); 86 | if (indexClass.getName().endsWith("ReadOnlyIndexToIndexAdapter")) { 87 | try { 88 | final Field delegateIndexField = indexClass.getDeclaredField("delegate"); 89 | delegateIndexField.setAccessible(true); 90 | return (Index) delegateIndexField.get(index); 91 | } catch (Exception e) { 92 | throw new UnsupportedOperationException(e); 93 | } 94 | } else { 95 | return index; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/org/neo4j/server/DeleteDatabaseTest.java: -------------------------------------------------------------------------------- 1 | package org.neo4j.server; 2 | 3 | import com.sun.jersey.api.client.Client; 4 | import com.sun.jersey.api.client.ClientResponse; 5 | import org.junit.AfterClass; 6 | import org.junit.Before; 7 | import org.junit.BeforeClass; 8 | import org.junit.Test; 9 | import org.mortbay.util.ajax.JSON; 10 | import org.neo4j.graphdb.DynamicRelationshipType; 11 | import org.neo4j.graphdb.Node; 12 | import org.neo4j.graphdb.Relationship; 13 | import org.neo4j.graphdb.Transaction; 14 | import org.neo4j.graphdb.index.Index; 15 | import org.neo4j.graphdb.index.IndexManager; 16 | import org.neo4j.kernel.AbstractGraphDatabase; 17 | import org.neo4j.kernel.EmbeddedGraphDatabase; 18 | import org.neo4j.kernel.GraphDatabaseAPI; 19 | import org.neo4j.server.configuration.ServerConfigurator; 20 | import org.neo4j.server.configuration.ThirdPartyJaxRsPackage; 21 | import org.neo4j.server.extension.test.delete.Neo4jDatabaseCleaner; 22 | 23 | import java.util.Map; 24 | import java.util.Random; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | import static org.junit.Assert.assertFalse; 28 | 29 | /** 30 | * @author mh 31 | * @since 02.03.11 32 | */ 33 | public class DeleteDatabaseTest { 34 | private static final int MANY_NODES = 1500; 35 | private static final int FEW_NODES = 500; 36 | 37 | private static final String CONTEXT_PATH = "cleandb"; 38 | private static AbstractGraphDatabase graphDatabase; 39 | private static NeoServer neoServer; 40 | 41 | @BeforeClass 42 | public static void startServerWithACleanDb() { 43 | graphDatabase = new EmbeddedGraphDatabase("target/db1"); 44 | 45 | ServerConfigurator config = new ServerConfigurator(graphDatabase); 46 | config.configuration().setProperty("org.neo4j.server.thirdparty.delete.key", "secret-key"); 47 | config.getThirdpartyJaxRsClasses().add(new ThirdPartyJaxRsPackage("org.neo4j.server.extension.test.delete", "/cleandb")); 48 | 49 | WrappingNeoServerBootstrapper bootstrapper = new WrappingNeoServerBootstrapper(graphDatabase, config); 50 | bootstrapper.start(); 51 | neoServer = bootstrapper.getServer(); 52 | } 53 | 54 | @AfterClass 55 | public static void shutdownServer() { 56 | neoServer.stop(); 57 | } 58 | 59 | @Before 60 | public void cleanDb() { 61 | Neo4jDatabaseCleaner cleaner = new Neo4jDatabaseCleaner(graphDatabase); 62 | cleaner.cleanDb(); 63 | } 64 | 65 | private GraphDatabaseAPI getGraphDb() { 66 | return graphDatabase; 67 | } 68 | 69 | private long getNumberOfNodes(GraphDatabaseAPI graph) { 70 | long count = 0; 71 | for (Node node : graph.getAllNodes()) { 72 | count++; 73 | } 74 | return count; 75 | //return graph.getConfig().getGraphDbModule().getNodeManager().getNumberOfIdsInUse(Node.class); 76 | } 77 | 78 | @Test 79 | public void deleteWithWrongKey() throws Exception { 80 | ClientResponse response = Client.create().resource(createDeleteURI("wrong-key")).delete(ClientResponse.class); 81 | assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(), response.getStatus()); 82 | } 83 | 84 | @Test 85 | public void deleteWithFewNodes() throws Exception { 86 | createData(getGraphDb(), FEW_NODES); 87 | assertEquals(FEW_NODES + 1, getNumberOfNodes(getGraphDb())); 88 | ClientResponse response = Client.create().resource(createDeleteURI("secret-key")).delete(ClientResponse.class); 89 | assertEquals(ClientResponse.Status.OK.getStatusCode(), response.getStatus()); 90 | assertEquals(1, getNumberOfNodes(getGraphDb())); 91 | } 92 | 93 | @Test 94 | public void multipleDeletesWithFewNodesDontDeleteDirectories() throws Exception { 95 | for (int i = 0; i < 10; i++) { 96 | createData(getGraphDb(), FEW_NODES); 97 | assertEquals(FEW_NODES + 1, getNumberOfNodes(getGraphDb())); 98 | ClientResponse response = Client.create().resource(createDeleteURI("secret-key")).delete(ClientResponse.class); 99 | final Map result = (Map) JSON.parse(response.getEntity(String.class)); 100 | assertEquals(ClientResponse.Status.OK.getStatusCode(), response.getStatus()); 101 | assertEquals(1, getNumberOfNodes(getGraphDb())); 102 | assertFalse(result.containsKey("store-dir")); 103 | } 104 | } 105 | 106 | @Test 107 | public void deleteWithManyNodes() throws Exception { 108 | createData(getGraphDb(), MANY_NODES); 109 | assertEquals(MANY_NODES+1, getNumberOfNodes(getGraphDb())); 110 | ClientResponse response = Client.create().resource(createDeleteURI("secret-key")).delete(ClientResponse.class); 111 | assertEquals(ClientResponse.Status.OK.getStatusCode(), response.getStatus()); 112 | assertEquals(0, getNumberOfNodes(getGraphDb())); 113 | } 114 | 115 | private String createDeleteURI(String key) { 116 | return String.format(neoServer.baseUri().toString() + "%s/%s", CONTEXT_PATH, key); 117 | } 118 | 119 | private void createData(GraphDatabaseAPI db, int max) { 120 | Transaction tx = db.beginTx(); 121 | try { 122 | final IndexManager indexManager = db.index(); 123 | Node[] nodes = new Node[max]; 124 | for (int i = 0; i < max; i++) { 125 | nodes[i] = db.createNode(); 126 | final Index index = indexManager.forNodes("node_index_" + String.valueOf(i % 5)); 127 | index.add(nodes[i],"ID",i); 128 | } 129 | Random random = new Random(); 130 | for (int i = 0; i < max * 2; i++) { 131 | int from = random.nextInt(max); 132 | final int to = (from + 1 + random.nextInt(max - 1)) % max; 133 | final Relationship relationship = nodes[from].createRelationshipTo(nodes[to], DynamicRelationshipType.withName("TEST_" + i)); 134 | final Index index = indexManager.forRelationships("rel_index_" + String.valueOf(i % 5)); 135 | index.add(relationship, "ID", i); 136 | } 137 | tx.success(); 138 | } finally { 139 | tx.finish(); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.neo4j 6 | test-delete-db-extension 7 | 1.8 8 | jar 9 | 10 | Neo4j Server Extension - Delete Database for Testing 11 | 12 | 13 | UTF-8 14 | 1.6.1 15 | UTF-8 16 | 1.8-SNAPSHOT 17 | 1.4 18 | 19 | 20 | 21 | 22 | neo4j-public-repository 23 | http://m2.neo4j.org/ 24 | Publicly available Maven 2 repository for Neo4j 25 | 26 | 27 | central 28 | http://uk.maven.org/maven2/ 29 | Maven Central Mirror 30 | 31 | 32 | java.net 33 | http://download.java.net/maven/2/ 34 | 35 | 36 | 37 | 38 | 39 | 40 | org.neo4j 41 | neo4j-kernel 42 | ${neo4j.version} 43 | true 44 | 45 | 46 | org.neo4j 47 | neo4j-kernel 48 | ${neo4j.version} 49 | tests 50 | true 51 | 52 | 53 | org.neo4j 54 | neo4j-ha 55 | ${neo4j.version} 56 | test 57 | 58 | 59 | org.neo4j 60 | neo4j-shell 61 | ${neo4j.version} 62 | test 63 | 64 | 65 | org.neo4j 66 | neo4j-udc 67 | ${neo4j.version} 68 | test 69 | 70 | 71 | 72 | org.mortbay.jetty 73 | jetty 74 | 6.1.25 75 | test 76 | 77 | 78 | 79 | commons-configuration 80 | commons-configuration 81 | 1.6 82 | true 83 | 84 | 85 | 86 | org.neo4j.app 87 | neo4j-server 88 | ${neo4j.version} 89 | true 90 | 91 | 92 | com.tinkerpop 93 | gremlin 94 | 95 | 96 | org.neo4j 97 | neo4j 98 | 99 | 100 | org.neo4j 101 | neo4j-com 102 | 103 | 104 | org.neo4j 105 | neo4j-backup 106 | 107 | 108 | org.mortbay.jetty 109 | jetty 110 | 111 | 112 | com.sun.jersey 113 | jersey-server 114 | 115 | 116 | commons-configuration 117 | commons-configuration 118 | 119 | 120 | de.huxhorn.lilith 121 | de.huxhorn.lilith.3rdparty.rrd4j 122 | 123 | 124 | com.sun.jersey.contribs 125 | jersey-multipart 126 | 127 | 128 | org.apache.felix 129 | org.apache.felix.main 130 | 131 | 132 | org.apache.felix 133 | org.apache.felix.fileinstall 134 | 135 | 136 | org.neo4j 137 | neo4j-shell 138 | 139 | 140 | 141 | 142 | 143 | com.sun.jersey 144 | jersey-server 145 | ${com.sun.jersey.version} 146 | true 147 | 148 | 149 | com.sun.jersey 150 | jersey-client 151 | ${com.sun.jersey.version} 152 | test 153 | 154 | 155 | 156 | junit 157 | junit 158 | 4.8.1 159 | test 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | org.apache.maven.plugins 168 | maven-compiler-plugin 169 | 2.1 170 | 171 | 1.6 172 | 1.6 173 | 174 | 175 | 176 | org.apache.maven.plugins 177 | maven-surefire-plugin 178 | 2.12 179 | 180 | true 181 | 182 | **/*Tests.java 183 | **/*Test.java 184 | 185 | 186 | **/Abstract*.java 187 | 188 | junit:junit 189 | 190 | 191 | 192 | 193 | 194 | 195 | --------------------------------------------------------------------------------