/**
* 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 );
}
}