Skip to content

Commit

Permalink
Fix issue #1438 by generalizing the usage of the ServiceHelper class.
Browse files Browse the repository at this point in the history
Also add test to check the behavior of the `ServiceHelper` when using different classloaders.

Signed-off-by: Clement Escoffier <clement.escoffier@gmail.com>
  • Loading branch information
cescoffier committed May 31, 2016
1 parent 8f49444 commit 41326f9
Show file tree
Hide file tree
Showing 20 changed files with 454 additions and 42 deletions.
2 changes: 1 addition & 1 deletion src/main/java/io/vertx/core/Future.java
Expand Up @@ -305,6 +305,6 @@ default Handler<AsyncResult<T>> completer() {
}; };
} }


static FutureFactory factory = ServiceHelper.loadFactory(FutureFactory.class); FutureFactory factory = ServiceHelper.loadFactory(FutureFactory.class);


} }
44 changes: 38 additions & 6 deletions src/main/java/io/vertx/core/ServiceHelper.java
Expand Up @@ -16,7 +16,7 @@


package io.vertx.core; package io.vertx.core;


import java.util.ServiceLoader; import java.util.*;


/** /**
* A helper class for loading factories from the classpath and from the vert.x OSGi bundle. * A helper class for loading factories from the classpath and from the vert.x OSGi bundle.
Expand All @@ -26,18 +26,50 @@
public class ServiceHelper { public class ServiceHelper {


public static <T> T loadFactory(Class<T> clazz) { public static <T> T loadFactory(Class<T> clazz) {
ServiceLoader<T> factories = ServiceLoader.load(clazz); T factory = loadFactoryOrNull(clazz);
if (factory == null) {
throw new IllegalStateException("Cannot find META-INF/services/" + clazz.getName() + " on classpath");
}
return factory;
}

public static <T> T loadFactoryOrNull(Class<T> clazz) {
Collection<T> collection = loadFactories(clazz);
if (!collection.isEmpty()) {
return collection.iterator().next();
} else {
return null;
}
}


public static <T> Collection<T> loadFactories(Class<T> clazz) {
return loadFactories(clazz, null);
}

public static <T> Collection<T> loadFactories(Class<T> clazz, ClassLoader classLoader) {
List<T> list = new ArrayList<>();
ServiceLoader<T> factories;
if (classLoader != null) {
factories = ServiceLoader.load(clazz, classLoader);
} else {
// this is equivalent to:
// ServiceLoader.load(clazz, TCCL);
factories = ServiceLoader.load(clazz);
}
if (factories.iterator().hasNext()) { if (factories.iterator().hasNext()) {
return factories.iterator().next(); factories.iterator().forEachRemaining(list::add);
return list;
} else { } else {
// By default ServiceLoader.load uses the TCCL, this may not be enough in environment deading with // By default ServiceLoader.load uses the TCCL, this may not be enough in environment dealing with
// classloaders differently such as OSGi. So we should try to use the classloader having loaded this // classloaders differently such as OSGi. So we should try to use the classloader having loaded this
// class. In OSGi it would be the bundle exposing vert.x and so have access to all its classes. // class. In OSGi it would be the bundle exposing vert.x and so have access to all its classes.
factories = ServiceLoader.load(clazz, ServiceHelper.class.getClassLoader()); factories = ServiceLoader.load(clazz, ServiceHelper.class.getClassLoader());
if (factories.iterator().hasNext()) { if (factories.iterator().hasNext()) {
return factories.iterator().next(); factories.iterator().forEachRemaining(list::add);
return list;
} else { } else {
throw new IllegalStateException("Cannot find META-INF/services/" + clazz.getName() + " on classpath"); return Collections.emptyList();
} }
} }
} }
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/vertx/core/Vertx.java
Expand Up @@ -521,5 +521,5 @@ static void clusteredVertx(VertxOptions options, Handler<AsyncResult<Vertx>> res
*/ */
@Nullable @GenIgnore Handler<Throwable> exceptionHandler(); @Nullable @GenIgnore Handler<Throwable> exceptionHandler();


static final VertxFactory factory = ServiceHelper.loadFactory(VertxFactory.class); VertxFactory factory = ServiceHelper.loadFactory(VertxFactory.class);
} }
2 changes: 1 addition & 1 deletion src/main/java/io/vertx/core/buffer/Buffer.java
Expand Up @@ -697,6 +697,6 @@ static Buffer buffer(ByteBuf byteBuf) {
@GenIgnore @GenIgnore
ByteBuf getByteBuf(); ByteBuf getByteBuf();


static final BufferFactory factory = ServiceHelper.loadFactory(BufferFactory.class); BufferFactory factory = ServiceHelper.loadFactory(BufferFactory.class);


} }
2 changes: 1 addition & 1 deletion src/main/java/io/vertx/core/http/WebSocketFrame.java
Expand Up @@ -106,5 +106,5 @@ static WebSocketFrame continuationFrame(Buffer data, boolean isFinal) {
*/ */
boolean isFinal(); boolean isFinal();


static final WebSocketFrameFactory factory = ServiceHelper.loadFactory(WebSocketFrameFactory.class); WebSocketFrameFactory factory = ServiceHelper.loadFactory(WebSocketFrameFactory.class);
} }
6 changes: 2 additions & 4 deletions src/main/java/io/vertx/core/impl/DeploymentManager.java
Expand Up @@ -52,10 +52,8 @@ public DeploymentManager(VertxInternal vertx) {
} }


private void loadVerticleFactories() { private void loadVerticleFactories() {
ServiceLoader<VerticleFactory> factories = ServiceLoader.load(VerticleFactory.class); Collection<VerticleFactory> factories = ServiceHelper.loadFactories(VerticleFactory.class);
for (VerticleFactory factory: factories) { factories.forEach(this::registerVerticleFactory);
registerVerticleFactory(factory);
}
VerticleFactory defaultFactory = new JavaVerticleFactory(); VerticleFactory defaultFactory = new JavaVerticleFactory();
defaultFactory.init(vertx); defaultFactory.init(vertx);
defaultFactories.add(defaultFactory); defaultFactories.add(defaultFactory);
Expand Down
17 changes: 7 additions & 10 deletions src/main/java/io/vertx/core/impl/VertxImpl.java
Expand Up @@ -365,10 +365,8 @@ private VertxMetrics initialiseMetrics(VertxOptions options) {
if (options.getMetricsOptions() != null && options.getMetricsOptions().isEnabled()) { if (options.getMetricsOptions() != null && options.getMetricsOptions().isEnabled()) {
VertxMetricsFactory factory = options.getMetricsOptions().getFactory(); VertxMetricsFactory factory = options.getMetricsOptions().getFactory();
if (factory == null) { if (factory == null) {
ServiceLoader<VertxMetricsFactory> factories = ServiceLoader.load(VertxMetricsFactory.class); factory = ServiceHelper.loadFactoryOrNull(VertxMetricsFactory.class);
if (factories.iterator().hasNext()) { if (factory == null) {
factory = factories.iterator().next();
} else {
log.warn("Metrics has been set to enabled but no VertxMetricsFactory found on classpath"); log.warn("Metrics has been set to enabled but no VertxMetricsFactory found on classpath");
} }
} }
Expand All @@ -392,16 +390,15 @@ private ClusterManager getClusterManager(VertxOptions options) {
// We allow specify a sys prop for the cluster manager factory which overrides ServiceLoader // We allow specify a sys prop for the cluster manager factory which overrides ServiceLoader
try { try {
Class<?> clazz = Class.forName(clusterManagerClassName); Class<?> clazz = Class.forName(clusterManagerClassName);
mgr = (ClusterManager)clazz.newInstance(); mgr = (ClusterManager) clazz.newInstance();
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("Failed to instantiate " + clusterManagerClassName, e); throw new IllegalStateException("Failed to instantiate " + clusterManagerClassName, e);
} }
} else { } else {
ServiceLoader<ClusterManager> mgrs = ServiceLoader.load(ClusterManager.class); mgr = ServiceHelper.loadFactoryOrNull(ClusterManager.class);
if (!mgrs.iterator().hasNext()) { if (mgr == null) {
throw new IllegalStateException("No ClusterManagerFactory instances found on classpath"); throw new IllegalStateException("No ClusterManagerFactory instances found on classpath");
} }
mgr = mgrs.iterator().next();
} }
return mgr; return mgr;
} }
Expand All @@ -424,7 +421,7 @@ private long scheduleTimeout(ContextImpl context, Handler<Long> handler, long de
public static Context context() { public static Context context() {
Thread current = Thread.currentThread(); Thread current = Thread.currentThread();
if (current instanceof VertxThread) { if (current instanceof VertxThread) {
return ((VertxThread)current).getContext(); return ((VertxThread) current).getContext();
} }
return null; return null;
} }
Expand All @@ -450,7 +447,7 @@ private void closeClusterManager(Handler<AsyncResult<Void>> completionHandler) {
if (clusterManager != null) { if (clusterManager != null) {
// Workaround fo Hazelcast bug https://github.com/hazelcast/hazelcast/issues/5220 // Workaround fo Hazelcast bug https://github.com/hazelcast/hazelcast/issues/5220
if (clusterManager instanceof ExtendedClusterManager) { if (clusterManager instanceof ExtendedClusterManager) {
ExtendedClusterManager ecm = (ExtendedClusterManager)clusterManager; ExtendedClusterManager ecm = (ExtendedClusterManager) clusterManager;
ecm.beforeLeave(); ecm.beforeLeave();
} }
clusterManager.leave(ar -> { clusterManager.leave(ar -> {
Expand Down
Expand Up @@ -15,13 +15,13 @@
*/ */
package io.vertx.core.impl.launcher; package io.vertx.core.impl.launcher;


import io.vertx.core.ServiceHelper;
import io.vertx.core.spi.launcher.CommandFactory; import io.vertx.core.spi.launcher.CommandFactory;
import io.vertx.core.spi.launcher.CommandFactoryLookup; import io.vertx.core.spi.launcher.CommandFactoryLookup;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.ServiceLoader;


/** /**
* Looks for command factories using a service loader. * Looks for command factories using a service loader.
Expand All @@ -30,28 +30,28 @@
*/ */
public class ServiceCommandFactoryLoader implements CommandFactoryLookup { public class ServiceCommandFactoryLoader implements CommandFactoryLookup {


private ServiceLoader<CommandFactory> loader; private Collection<CommandFactory> commands;


/** /**
* Creates a new instance of {@link ServiceCommandFactoryLoader} using the classloader having loaded the * Creates a new instance of {@link ServiceCommandFactoryLoader} using the classloader having loaded the
* {@link ServiceCommandFactoryLoader} class. * {@link ServiceCommandFactoryLoader} class.
*/ */
public ServiceCommandFactoryLoader() { public ServiceCommandFactoryLoader() {
this.loader = ServiceLoader.load(CommandFactory.class, getClass().getClassLoader()); this.commands = ServiceHelper.loadFactories(CommandFactory.class, getClass().getClassLoader());
} }


/** /**
* Creates a new instance of {@link ServiceCommandFactoryLoader} using specified classloader. * Creates a new instance of {@link ServiceCommandFactoryLoader} using specified classloader.
*/ */
public ServiceCommandFactoryLoader(ClassLoader loader) { public ServiceCommandFactoryLoader(ClassLoader loader) {
this.loader = ServiceLoader.load(CommandFactory.class, loader); this.commands = ServiceHelper.loadFactories(CommandFactory.class, loader);
} }


@Override @Override
public Collection<CommandFactory<?>> lookup() { public Collection<CommandFactory<?>> lookup() {
List<CommandFactory<?>> commands = new ArrayList<>(); List<CommandFactory<?>> list = new ArrayList<>();
loader.forEach(commands::add); commands.stream().forEach(list::add);
return commands; return list;
} }


} }
Expand Up @@ -15,10 +15,7 @@
*/ */
package io.vertx.core.impl.launcher.commands; package io.vertx.core.impl.launcher.commands;


import io.vertx.core.AsyncResult; import io.vertx.core.*;
import io.vertx.core.Vertx;
import io.vertx.core.VertxException;
import io.vertx.core.VertxOptions;
import io.vertx.core.cli.annotations.*; import io.vertx.core.cli.annotations.*;
import io.vertx.core.impl.launcher.VertxLifecycleHooks; import io.vertx.core.impl.launcher.VertxLifecycleHooks;
import io.vertx.core.metrics.MetricsOptions; import io.vertx.core.metrics.MetricsOptions;
Expand All @@ -32,7 +29,6 @@
import java.net.SocketException; import java.net.SocketException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Properties; import java.util.Properties;
import java.util.ServiceLoader;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
Expand Down Expand Up @@ -231,9 +227,8 @@ protected void beforeStartingVertx(VertxOptions options) {
*/ */
protected MetricsOptions getMetricsOptions() { protected MetricsOptions getMetricsOptions() {
MetricsOptions metricsOptions; MetricsOptions metricsOptions;
ServiceLoader<VertxMetricsFactory> factories = ServiceLoader.load(VertxMetricsFactory.class); VertxMetricsFactory factory = ServiceHelper.loadFactoryOrNull(VertxMetricsFactory.class);
if (factories.iterator().hasNext()) { if (factory != null) {
VertxMetricsFactory factory = factories.iterator().next();
metricsOptions = factory.newOptions(); metricsOptions = factory.newOptions();
} else { } else {
metricsOptions = new MetricsOptions(); metricsOptions = new MetricsOptions();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/io/vertx/core/streams/Pump.java
Expand Up @@ -101,7 +101,7 @@ static <T> Pump pump(ReadStream<T> rs, WriteStream<T> ws, int writeQueueMaxSize)
*/ */
int numberPumped(); int numberPumped();


static final PumpFactory factory = ServiceHelper.loadFactory(PumpFactory.class); PumpFactory factory = ServiceHelper.loadFactory(PumpFactory.class);




} }
16 changes: 16 additions & 0 deletions src/test/externals/META-INF/services/io.vertx.test.spi.SomeFactory
@@ -0,0 +1,16 @@
#
# Copyright (c) 2011-2015 The original author or authors
# ------------------------------------------------------
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# and Apache License v2.0 which accompanies this distribution.
#
# The Eclipse Public License is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# The Apache License v2.0 is available at
# http://www.opensource.org/licenses/apache2.0.php
#
# You may elect to redistribute this code under either of these licenses.
#
io.vertx.core.externals.SomeFactoryImplA
34 changes: 34 additions & 0 deletions src/test/externals/SomeFactoryImplA.java
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2011-2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/

package io.vertx.core.externals;

import io.vertx.test.spi.SomeFactory;

/**
* @author <a href="http://escoffier.me">Clement Escoffier</a>
*/
public class SomeFactoryImplA implements SomeFactory {
@Override
public String name() {
return "A";
}

@Override
public ClassLoader classloader() {
return this.getClass().getClassLoader();
}
}
34 changes: 34 additions & 0 deletions src/test/java/io/vertx/core/FakeFactoryImplA.java
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2011-2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/

package io.vertx.core;

import io.vertx.test.spi.FakeFactory;

/**
* @author <a href="http://escoffier.me">Clement Escoffier</a>
*/
public class FakeFactoryImplA implements FakeFactory {
@Override
public String name() {
return "A";
}

@Override
public ClassLoader classloader() {
return this.getClass().getClassLoader();
}
}
34 changes: 34 additions & 0 deletions src/test/java/io/vertx/core/FakeFactoryImplB.java
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2011-2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/

package io.vertx.core;

import io.vertx.test.spi.FakeFactory;

/**
* @author <a href="http://escoffier.me">Clement Escoffier</a>
*/
public class FakeFactoryImplB implements FakeFactory {
@Override
public String name() {
return "B";
}

@Override
public ClassLoader classloader() {
return this.getClass().getClassLoader();
}
}

0 comments on commit 41326f9

Please sign in to comment.