Skip to content

Commit

Permalink
Improve the error message when a kernel extension cannot be loaded fo…
Browse files Browse the repository at this point in the history
…r various reasons.

The error message is unfortunately still buried in the middle of the stack trace, but I think it would be too intrusive to improve that in a patch release.
  • Loading branch information
chrisvest committed Dec 10, 2018
1 parent 32c028a commit 45c3a07
Show file tree
Hide file tree
Showing 13 changed files with 244 additions and 41 deletions.
Expand Up @@ -29,7 +29,7 @@
import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.extension.DatabaseKernelExtensions; import org.neo4j.kernel.extension.DatabaseKernelExtensions;
import org.neo4j.kernel.extension.KernelExtensionFactory; import org.neo4j.kernel.extension.KernelExtensionFactory;
import org.neo4j.kernel.extension.UnsatisfiedDependencyStrategies; import org.neo4j.kernel.extension.KernelExtensionFailureStrategies;
import org.neo4j.kernel.impl.core.TokenHolders; import org.neo4j.kernel.impl.core.TokenHolders;
import org.neo4j.kernel.impl.factory.DatabaseInfo; import org.neo4j.kernel.impl.factory.DatabaseInfo;
import org.neo4j.kernel.impl.spi.KernelContext; import org.neo4j.kernel.impl.spi.KernelContext;
Expand All @@ -55,6 +55,6 @@ public static DatabaseKernelExtensions instantiateKernelExtensions(
@SuppressWarnings( "rawtypes" ) @SuppressWarnings( "rawtypes" )
Iterable kernelExtensions = Service.load( KernelExtensionFactory.class ); Iterable kernelExtensions = Service.load( KernelExtensionFactory.class );
KernelContext kernelContext = new SimpleKernelContext( databaseDirectory, databaseInfo, deps ); KernelContext kernelContext = new SimpleKernelContext( databaseDirectory, databaseInfo, deps );
return new DatabaseKernelExtensions( kernelContext, kernelExtensions, deps, UnsatisfiedDependencyStrategies.ignore() ); return new DatabaseKernelExtensions( kernelContext, kernelExtensions, deps, KernelExtensionFailureStrategies.ignore() );
} }
} }
Expand Up @@ -163,7 +163,7 @@
import org.neo4j.util.VisibleForTesting; import org.neo4j.util.VisibleForTesting;


import static org.neo4j.helpers.Exceptions.throwIfUnchecked; import static org.neo4j.helpers.Exceptions.throwIfUnchecked;
import static org.neo4j.kernel.extension.UnsatisfiedDependencyStrategies.fail; import static org.neo4j.kernel.extension.KernelExtensionFailureStrategies.fail;


public class NeoStoreDataSource extends LifecycleAdapter public class NeoStoreDataSource extends LifecycleAdapter
{ {
Expand Down
Expand Up @@ -40,13 +40,13 @@ public abstract class AbstractKernelExtensions extends DependencyResolver.Adapte
private final List<KernelExtensionFactory<?>> kernelExtensionFactories; private final List<KernelExtensionFactory<?>> kernelExtensionFactories;
private final Dependencies dependencies; private final Dependencies dependencies;
private final LifeSupport life = new LifeSupport(); private final LifeSupport life = new LifeSupport();
private final UnsatisfiedDependencyStrategy unsatisfiedDependencyStrategy; private final KernelExtensionFailureStrategy kernelExtensionFailureStrategy;


AbstractKernelExtensions( KernelContext kernelContext, Iterable<KernelExtensionFactory<?>> kernelExtensionFactories, Dependencies dependencies, AbstractKernelExtensions( KernelContext kernelContext, Iterable<KernelExtensionFactory<?>> kernelExtensionFactories, Dependencies dependencies,
UnsatisfiedDependencyStrategy unsatisfiedDependencyStrategy, ExtensionType extensionType ) KernelExtensionFailureStrategy kernelExtensionFailureStrategy, ExtensionType extensionType )
{ {
this.kernelContext = kernelContext; this.kernelContext = kernelContext;
this.unsatisfiedDependencyStrategy = unsatisfiedDependencyStrategy; this.kernelExtensionFailureStrategy = kernelExtensionFailureStrategy;
this.kernelExtensionFactories = stream( kernelExtensionFactories ).filter( e -> e.getExtensionType() == extensionType ).collect( toList() ); this.kernelExtensionFactories = stream( kernelExtensionFactories ).filter( e -> e.getExtensionType() == extensionType ).collect( toList() );
this.dependencies = dependencies; this.dependencies = dependencies;
} }
Expand All @@ -56,17 +56,20 @@ public void init()
{ {
for ( KernelExtensionFactory<?> kernelExtensionFactory : kernelExtensionFactories ) for ( KernelExtensionFactory<?> kernelExtensionFactory : kernelExtensionFactories )
{ {
Object kernelExtensionDependencies = getKernelExtensionDependencies( kernelExtensionFactory );
try try
{ {
Object kernelExtensionDependencies = getKernelExtensionDependencies( kernelExtensionFactory );
Lifecycle dependency = newInstance( kernelContext, kernelExtensionFactory, kernelExtensionDependencies ); Lifecycle dependency = newInstance( kernelContext, kernelExtensionFactory, kernelExtensionDependencies );
Objects.requireNonNull( dependency, kernelExtensionFactory.toString() + " returned a null " + Objects.requireNonNull( dependency, kernelExtensionFactory.toString() + " returned a null KernelExtension" );
"KernelExtension" );
life.add( dependencies.satisfyDependency( dependency ) ); life.add( dependencies.satisfyDependency( dependency ) );
} }
catch ( UnsatisfiedDependencyException e ) catch ( UnsatisfiedDependencyException exception )
{ {
unsatisfiedDependencyStrategy.handle( kernelExtensionFactory, e ); kernelExtensionFailureStrategy.handle( kernelExtensionFactory, exception );
}
catch ( Throwable throwable )
{
kernelExtensionFailureStrategy.handle( kernelExtensionFactory, throwable );
} }
} }


Expand Down
Expand Up @@ -25,8 +25,8 @@
public class DatabaseKernelExtensions extends AbstractKernelExtensions public class DatabaseKernelExtensions extends AbstractKernelExtensions
{ {
public DatabaseKernelExtensions( KernelContext kernelContext, Iterable<KernelExtensionFactory<?>> kernelExtensionFactories, public DatabaseKernelExtensions( KernelContext kernelContext, Iterable<KernelExtensionFactory<?>> kernelExtensionFactories,
Dependencies dependencies, UnsatisfiedDependencyStrategy unsatisfiedDependencyStrategy ) Dependencies dependencies, KernelExtensionFailureStrategy kernelExtensionFailureStrategy )
{ {
super( kernelContext, kernelExtensionFactories, dependencies, unsatisfiedDependencyStrategy, ExtensionType.DATABASE ); super( kernelContext, kernelExtensionFactories, dependencies, kernelExtensionFailureStrategy, ExtensionType.DATABASE );
} }
} }
Expand Up @@ -19,32 +19,20 @@
*/ */
package org.neo4j.kernel.extension; package org.neo4j.kernel.extension;


import java.io.PrintStream; public class FailedToBuildKernelExtensionException extends RuntimeException

public class UnsatisfiedDependencyStrategies
{ {
private UnsatisfiedDependencyStrategies() public FailedToBuildKernelExtensionException( String message )
{
}

public static UnsatisfiedDependencyStrategy fail()
{ {
return ( kernelExtensionFactory, e ) -> super( message );
{
throw e;
};
} }


public static UnsatisfiedDependencyStrategy ignore() public FailedToBuildKernelExtensionException( String message, Throwable cause )
{ {
return ( kernelExtensionFactory, e ) -> super( message, cause );
{ // just ignore
};
} }


// Perhaps not used, but very useful for debugging kernel extension loading problems public FailedToBuildKernelExtensionException( Throwable cause )
public static UnsatisfiedDependencyStrategy print( PrintStream out )
{ {
return ( kernelExtensionFactory, e ) -> out.println( kernelExtensionFactory + " missing dep " + e ); super( cause );
} }
} }
Expand Up @@ -25,8 +25,8 @@
public class GlobalKernelExtensions extends AbstractKernelExtensions public class GlobalKernelExtensions extends AbstractKernelExtensions
{ {
public GlobalKernelExtensions( KernelContext kernelContext, Iterable<KernelExtensionFactory<?>> kernelExtensionFactories, public GlobalKernelExtensions( KernelContext kernelContext, Iterable<KernelExtensionFactory<?>> kernelExtensionFactories,
Dependencies dependencies, UnsatisfiedDependencyStrategy unsatisfiedDependencyStrategy ) Dependencies dependencies, KernelExtensionFailureStrategy kernelExtensionFailureStrategy )
{ {
super( kernelContext, kernelExtensionFactories, dependencies, unsatisfiedDependencyStrategy, ExtensionType.GLOBAL ); super( kernelContext, kernelExtensionFactories, dependencies, kernelExtensionFailureStrategy, ExtensionType.GLOBAL );
} }
} }
@@ -0,0 +1,117 @@
/*
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.extension;

import java.io.PrintStream;

import org.neo4j.kernel.impl.util.UnsatisfiedDependencyException;

public class KernelExtensionFailureStrategies
{
private KernelExtensionFailureStrategies()
{
}

private static FailedToBuildKernelExtensionException wrap( KernelExtensionFactory kernelExtensionFactory, UnsatisfiedDependencyException e )
{
return new FailedToBuildKernelExtensionException(
"Failed to build kernel extension " + kernelExtensionFactory + " due to a missing dependency: " + e.getMessage(), e );
}

private static FailedToBuildKernelExtensionException wrap( KernelExtensionFactory kernelExtensionFactory, Throwable e )
{
StringBuilder message = new StringBuilder( "Failed to build kernel extension " ).append( kernelExtensionFactory );
if ( e instanceof LinkageError || e instanceof ReflectiveOperationException )
{
if ( e instanceof LinkageError )
{
message.append( " because it is compiled with a reference to a class, method, or field, that is not in the class path: " );
}
else
{
message.append( " because it a reflective access to a class, method, or field, that is not in the class path: " );
}
message.append( '\'' ).append( e.getMessage() ).append( '\'' );
message.append( ". The most common cause of this problem, is that Neo4j has been upgraded without also upgrading all" );
message.append( "installed extensions, such as APOC. " );
message.append( "Make sure that all of your extensions are build against your specific version of Neo4j." );
}
else
{
message.append( " because of an unanticipated error: '" ).append( e.getMessage() ).append( "'." );
}
return new FailedToBuildKernelExtensionException( message.toString(), e );
}

public static KernelExtensionFailureStrategy fail()
{
return new KernelExtensionFailureStrategy()
{
@Override
public void handle( KernelExtensionFactory kernelExtensionFactory, UnsatisfiedDependencyException e )
{
throw wrap( kernelExtensionFactory, e );
}

@Override
public void handle( KernelExtensionFactory kernelExtensionFactory, Throwable e )
{
throw wrap( kernelExtensionFactory, e );
}
};
}

public static KernelExtensionFailureStrategy ignore()
{
return new KernelExtensionFailureStrategy()
{
@Override
public void handle( KernelExtensionFactory kernelExtensionFactory, UnsatisfiedDependencyException e )
{
// Just ignore.
}

@Override
public void handle( KernelExtensionFactory kernelExtensionFactory, Throwable e )
{
// Just ignore.
}
};
}

// Perhaps not used, but very useful for debugging kernel extension loading problems
public static KernelExtensionFailureStrategy print( PrintStream out )
{
return new KernelExtensionFailureStrategy()
{
@Override
public void handle( KernelExtensionFactory kernelExtensionFactory, UnsatisfiedDependencyException e )
{
wrap( kernelExtensionFactory, e ).printStackTrace( out );
}

@Override
public void handle( KernelExtensionFactory kernelExtensionFactory, Throwable e )
{
wrap( kernelExtensionFactory, e ).printStackTrace( out );
}
};
}
}
Expand Up @@ -22,7 +22,9 @@
import org.neo4j.kernel.impl.util.UnsatisfiedDependencyException; import org.neo4j.kernel.impl.util.UnsatisfiedDependencyException;




public interface UnsatisfiedDependencyStrategy public interface KernelExtensionFailureStrategy
{ {
void handle( KernelExtensionFactory kernelExtensionFactory, UnsatisfiedDependencyException e ); void handle( KernelExtensionFactory kernelExtensionFactory, UnsatisfiedDependencyException e );

void handle( KernelExtensionFactory kernelExtensionFactory, Throwable e );
} }
Expand Up @@ -72,7 +72,7 @@
import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.extension.DatabaseKernelExtensions; import org.neo4j.kernel.extension.DatabaseKernelExtensions;
import org.neo4j.kernel.extension.KernelExtensionFactory; import org.neo4j.kernel.extension.KernelExtensionFactory;
import org.neo4j.kernel.extension.UnsatisfiedDependencyStrategies; import org.neo4j.kernel.extension.KernelExtensionFailureStrategies;
import org.neo4j.kernel.impl.api.DatabaseSchemaState; import org.neo4j.kernel.impl.api.DatabaseSchemaState;
import org.neo4j.kernel.impl.api.NonTransactionalTokenNameLookup; import org.neo4j.kernel.impl.api.NonTransactionalTokenNameLookup;
import org.neo4j.kernel.impl.api.index.IndexProviderMap; import org.neo4j.kernel.impl.api.index.IndexProviderMap;
Expand Down Expand Up @@ -307,7 +307,7 @@ public BatchInserterImpl( final File databaseDirectory, final FileSystemAbstract


DatabaseKernelExtensions extensions = life.add( new DatabaseKernelExtensions( DatabaseKernelExtensions extensions = life.add( new DatabaseKernelExtensions(
new SimpleKernelContext( databaseDirectory, DatabaseInfo.TOOL, deps ), new SimpleKernelContext( databaseDirectory, DatabaseInfo.TOOL, deps ),
kernelExtensions, deps, UnsatisfiedDependencyStrategies.ignore() ) ); kernelExtensions, deps, KernelExtensionFailureStrategies.ignore() ) );


indexProviderMap = life.add( new DefaultIndexProviderMap( extensions, config ) ); indexProviderMap = life.add( new DefaultIndexProviderMap( extensions, config ) );


Expand Down
Expand Up @@ -38,11 +38,11 @@
public class GlobalKernelExtensionsTest public class GlobalKernelExtensionsTest
{ {
@Test @Test
public void shouldConsultUnsatisfiedDependencyHandler() public void shouldConsultUnsatisfiedDependencyHandlerOnMissingDependencies()
{ {
// GIVEN // GIVEN
KernelContext context = mock( KernelContext.class ); KernelContext context = mock( KernelContext.class );
UnsatisfiedDependencyStrategy handler = mock( UnsatisfiedDependencyStrategy.class ); KernelExtensionFailureStrategy handler = mock( KernelExtensionFailureStrategy.class );
Dependencies dependencies = new Dependencies(); // that hasn't got anything. Dependencies dependencies = new Dependencies(); // that hasn't got anything.
TestingExtensionFactory extensionFactory = new TestingExtensionFactory(); TestingExtensionFactory extensionFactory = new TestingExtensionFactory();
GlobalKernelExtensions extensions = new GlobalKernelExtensions( context, iterable( extensionFactory ), dependencies, handler ); GlobalKernelExtensions extensions = new GlobalKernelExtensions( context, iterable( extensionFactory ), dependencies, handler );
Expand All @@ -63,6 +63,32 @@ public void shouldConsultUnsatisfiedDependencyHandler()
} }
} }


@Test
public void shouldConsultUnsatisfiedDependencyHandlerOnFailingDependencyClasses()
{
// GIVEN
KernelContext context = mock( KernelContext.class );
KernelExtensionFailureStrategy handler = mock( KernelExtensionFailureStrategy.class );
Dependencies dependencies = new Dependencies(); // that hasn't got anything.
UninitializableKernelExtensionFactory extensionFactory = new UninitializableKernelExtensionFactory();
GlobalKernelExtensions extensions = new GlobalKernelExtensions( context, iterable( extensionFactory ), dependencies, handler );

// WHEN
LifeSupport life = new LifeSupport();
life.add( extensions );
try
{
life.start();

// THEN
verify( handler ).handle( eq( extensionFactory ), any( IllegalArgumentException.class ) );
}
finally
{
life.shutdown();
}
}

private interface TestingDependencies private interface TestingDependencies
{ {
// Just some dependency // Just some dependency
Expand Down
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.extension;

import org.neo4j.kernel.impl.spi.KernelContext;
import org.neo4j.kernel.lifecycle.Lifecycle;

/**
* This kernel extension cannot be initialised, because an exception will be thrown we the machinery tries to create a proxy of the UnproxyableDepencies class.
*/
public class UninitializableKernelExtensionFactory extends KernelExtensionFactory<UnproxyableDependencies>
{
public UninitializableKernelExtensionFactory()
{
super( "uninitializable" );
}

@Override
public Lifecycle newInstance( KernelContext context, UnproxyableDependencies dependencies )
{
return null;
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j 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.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.extension;

/**
* This cannot be initialised as a dependencies proxy, because it is only possible to make proxies of interfaces, and this is a class!
*/
class UnproxyableDependencies
{
}

0 comments on commit 45c3a07

Please sign in to comment.