/** * Copyright (C) 2012 Red Hat, Inc. (jdcasey@commonjava.org) * * 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.commonjava.cartographer.graph.spi.neo4j; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.commonjava.cartographer.graph.spi.RelationshipGraphConnection; import org.commonjava.cartographer.graph.spi.RelationshipGraphConnectionException; import org.commonjava.cartographer.graph.spi.RelationshipGraphConnectionFactory; import org.neo4j.kernel.StoreLockException; import org.neo4j.kernel.lifecycle.LifecycleException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.nio.channels.OverlappingFileLockException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class FileNeo4jConnectionFactory implements RelationshipGraphConnectionFactory { private final Map<String, FileNeo4JGraphConnection> openConnections = new HashMap<String, FileNeo4JGraphConnection>(); private final File dbBaseDirectory; private final boolean useShutdownHook; private final Logger logger = LoggerFactory.getLogger( getClass() ); private int storageBatchSize = FileNeo4JGraphConnection.DEFAULT_BATCH_SIZE; public FileNeo4jConnectionFactory( final File dbBaseDirectory, final boolean useShutdownHook, final int storageBatchSize ) { this.dbBaseDirectory = dbBaseDirectory; this.useShutdownHook = useShutdownHook; this.storageBatchSize = storageBatchSize; } public FileNeo4jConnectionFactory( final File dbBaseDirectory, final boolean useShutdownHook ) { this.dbBaseDirectory = dbBaseDirectory; this.useShutdownHook = useShutdownHook; } @Override public synchronized RelationshipGraphConnection openConnection( final String workspaceId, final boolean create ) throws RelationshipGraphConnectionException { final File db = new File( dbBaseDirectory, workspaceId ); if ( !db.exists() ) { if ( !create ) { throw new RelationshipGraphConnectionException( "Workspace does not exist: %s.", workspaceId ); } else if ( !db.mkdirs() ) { throw new RelationshipGraphConnectionException( "Failed to create workspace directory for: %s. (dir: %s)", workspaceId, db ); } // // try // { // Thread.sleep( 20 ); // } // catch ( final InterruptedException e ) // { // Thread.currentThread() // .interrupt(); // return null; // } } FileNeo4JGraphConnection conn = openConnections.get( workspaceId ); if ( conn == null || !conn.isOpen() ) { conn = null; int attempt = 0; while ( conn == null ) { attempt++; try { conn = new FileNeo4JGraphConnection( workspaceId, db, useShutdownHook, storageBatchSize, this ); } catch ( RuntimeException ex ) { if ( ex.getCause() instanceof LifecycleException && ex.getCause().getCause() instanceof StoreLockException && ex.getCause().getCause().getCause() instanceof OverlappingFileLockException && attempt < 3 ) { logger.warn( "Tried to connect to DB which is not closed (yet). {} Retrying in 5s.", ex.toString() ); try { Thread.sleep(5000); } catch ( InterruptedException ez ) { logger.error( "The wait delay was interrupted.", ex ); } } else { throw ex; } } } openConnections.put( workspaceId, conn ); } return conn; } @Override public Set<String> listWorkspaces() { if( !dbBaseDirectory.exists() ) { return Collections.emptySet(); } String[] listing = dbBaseDirectory.list(); if ( listing == null ) { return Collections.emptySet(); } return new HashSet<String>( Arrays.asList( listing ) ); } @Override public void flush( final RelationshipGraphConnection connection ) throws RelationshipGraphConnectionException { // TODO How do I flush the graph to disk while other views may be modifying it?? } @Override public boolean delete( final String workspaceId ) throws RelationshipGraphConnectionException { final File db = new File( dbBaseDirectory, workspaceId ); if ( !db.exists() || !db.isDirectory() ) { return false; } try { final FileNeo4JGraphConnection connection = openConnections.remove( workspaceId ); if ( connection != null ) { connection.close(); } FileUtils.forceDelete( db ); return !db.exists(); } catch ( final IOException e ) { throw new RelationshipGraphConnectionException( "Failed to delete: %s. Reason: %s", e, db, e.getMessage() ); } } @Override public synchronized void close() throws IOException { final Logger logger = LoggerFactory.getLogger( getClass() ); final Set<String> failedClose = new HashSet<String>(); for ( final FileNeo4JGraphConnection conn : new HashSet<FileNeo4JGraphConnection>( openConnections.values() ) ) { try { conn.close(); } catch ( final IOException e ) { failedClose.add( conn.getWorkspaceId() ); logger.error( "Failed to close: " + conn.getWorkspaceId() + ".", e ); } } openConnections.clear(); if ( !failedClose.isEmpty() ) { throw new IOException( "Failed to close: " + StringUtils.join( failedClose, ", " ) ); } } @Override public boolean exists( final String workspaceId ) { final File db = new File( dbBaseDirectory, workspaceId ); return db.exists() && db.isDirectory(); } public synchronized void connectionClosing( final String workspaceId ) { openConnections.remove( workspaceId ); } }