Skip to content

Commit

Permalink
JAVA-1761: Add OSGi descriptors
Browse files Browse the repository at this point in the history
Motivation:

To enable the driver to be used in OSGi containers.

Modifications:

Use maven-bundle-plugin to add OSGi information to the generated
MANIFEST.MF.

Shaded jctools instead of using netty-shaded for MpscLinkedAtomicQueue
in DefaultWriteCoalescer.  This was required because netty does not
export its shaded jctools packages in its Export-Package definition.

Add SessionBuilder.withClassLoader to allow overriding ClassLoader
used to load classes from config which is useful for OSGi.

Add OSGi integration tests.

Make all dependency versions properties so they may be passed to
failsafe plugin for use in OSGi tests.

Result:

The driver now supports OSGi.
  • Loading branch information
tolbertam authored and olim7t committed Jun 18, 2018
1 parent 548994d commit 944056c
Show file tree
Hide file tree
Showing 24 changed files with 781 additions and 47 deletions.
1 change: 1 addition & 0 deletions changelog/README.md
Expand Up @@ -4,6 +4,7 @@

### 4.0.0-alpha4 (in progress)

- [new feature] JAVA-1761: Add OSGi descriptors
- [bug] JAVA-1560: Correctly propagate policy initialization errors
- [improvement] JAVA-1865: Add RelationMetadata.getPrimaryKey()
- [improvement] JAVA-1862: Add ConsistencyLevel.isDcLocal and isSerial
Expand Down
79 changes: 77 additions & 2 deletions core/pom.xml
Expand Up @@ -15,7 +15,9 @@
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
Expand All @@ -25,7 +27,7 @@
</parent>

<artifactId>java-driver-core</artifactId>
<packaging>jar</packaging>
<packaging>bundle</packaging>

<name>DataStax Java driver for Apache Cassandra(R) - core</name>

Expand Down Expand Up @@ -75,6 +77,10 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.jctools</groupId>
<artifactId>jctools-core</artifactId>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
Expand Down Expand Up @@ -154,6 +160,75 @@
</properties>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<manifestLocation>${project.build.outputDirectory}/META-INF</manifestLocation>
<instructions>
<Bundle-SymbolicName>com.datastax.oss.driver.core</Bundle-SymbolicName>
<!-- Allow importing code from other packages (so reflection-based loading of policies works) -->
<DynamicImport-Package>*</DynamicImport-Package>
<!-- Don't include JNR packages since some JNR modules are not OSGi bundles,
jcip because its not an OSGi bundle, jctools because its shaded.
Import sun.misc as jctools does this. -->
<Import-Package>
!net.jcip.annotations,!jnr.*,!org.jctools.*,sun.misc;resolution:=optional,*
</Import-Package>
<!-- Explicitly declare shaded jctools packages since this isn't covered by shade plugin -->
<Export-Package>
com.datastax.oss.driver.*.core.*,
com.datastax.oss.driver.shaded.jctools.util;version="${project.version}";uses:="sun.misc",
com.datastax.oss.driver.shaded.jctools.queues;version="${project.version}";uses:="com.datastax.oss.driver.shaded.jctools.queues.spec",
com.datastax.oss.driver.shaded.jctools.queues.spec;version="${project.version}",
com.datastax.oss.driver.shaded.jctools.queues.atomic;version="${project.version}";uses:="com.datastax.oss.driver.shaded.jctools.queues,com.datastax.oss.driver.shaded.jctools.queues.spec",
com.datastax.oss.driver.shaded.jctools.maps;version="${project.version}"
</Export-Package>
</instructions>
<!--
Prevent customized manifest entries from the project's maven-jar-plugin configuration from being read, see
http://apache-felix.18485.x6.nabble.com/how-lt-manifestLocation-gt-is-used-in-maven-bundle-plugin-td4835566.html
-->
<archive>
<forced>true</forced>
</archive>
</configuration>
</plugin>
<!-- shade jctools -->
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>org.jctools:jctools-core</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>org.jctools</pattern>
<shadedPattern>com.datastax.oss.driver.shaded.jctools</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.DontIncludeResourceTransformer">
<resources>
<resource>META-INF/maven/org.jctools/jctools-core/pom.properties</resource>
<resource>META-INF/maven/org.jctools/jctools-core/pom.xml</resource>
</resources>
</transformer>
</transformers>
<promoteTransitiveDependencies>true</promoteTransitiveDependencies>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Expand Up @@ -68,6 +68,7 @@ public abstract class SessionBuilder<SelfT extends SessionBuilder, SessionT> {
protected RequestTracker requestTracker;
private ImmutableMap.Builder<String, Predicate<Node>> nodeFilters = ImmutableMap.builder();
protected CqlIdentifier keyspace;
private ClassLoader classLoader = null;

/**
* Sets the configuration loader to use.
Expand Down Expand Up @@ -222,6 +223,20 @@ public SelfT withKeyspace(String keyspaceName) {
return withKeyspace(CqlIdentifier.fromCql(keyspaceName));
}

/**
* The {@link ClassLoader} to use to reflectively load class names defined in configuration.
*
* <p>This is typically only needed when using OSGi or other in environments where there are
* complex class loading requirements.
*
* <p>If null, the driver attempts to use {@link Thread#getContextClassLoader()} of the current
* thread or the same {@link ClassLoader} that loaded the core driver classes.
*/
public SelfT withClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
return self;
}

/**
* Creates the session with the options set by this builder.
*
Expand Down Expand Up @@ -269,7 +284,8 @@ protected final CompletionStage<CqlSession> buildDefaultSessionAsync() {
nodeStateListener,
schemaChangeListener,
requestTracker,
nodeFilters.build()),
nodeFilters.build(),
classLoader),
contactPoints,
keyspace);

Expand All @@ -290,14 +306,16 @@ protected DriverContext buildContext(
NodeStateListener nodeStateListener,
SchemaChangeListener schemaChangeListener,
RequestTracker requestTracker,
Map<String, Predicate<Node>> nodeFilters) {
Map<String, Predicate<Node>> nodeFilters,
ClassLoader classLoader) {
return new DefaultDriverContext(
configLoader,
typeCodecs,
nodeStateListener,
schemaChangeListener,
requestTracker,
nodeFilters);
nodeFilters,
classLoader);
}

private static <T> T buildIfNull(T value, Supplier<T> builder) {
Expand Down
Expand Up @@ -22,7 +22,6 @@
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoop;
import io.netty.util.internal.shaded.org.jctools.queues.atomic.MpscLinkedAtomicQueue;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
Expand All @@ -31,6 +30,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import net.jcip.annotations.ThreadSafe;
import org.jctools.queues.atomic.MpscLinkedAtomicQueue;

/**
* Default write coalescing strategy.
Expand Down
Expand Up @@ -189,14 +189,16 @@ public class DefaultDriverContext implements InternalDriverContext {
private final SchemaChangeListener schemaChangeListenerFromBuilder;
private final RequestTracker requestTrackerFromBuilder;
private final Map<String, Predicate<Node>> nodeFiltersFromBuilder;
private final ClassLoader classLoader;

public DefaultDriverContext(
DriverConfigLoader configLoader,
List<TypeCodec<?>> typeCodecs,
NodeStateListener nodeStateListener,
SchemaChangeListener schemaChangeListener,
RequestTracker requestTracker,
Map<String, Predicate<Node>> nodeFilters) {
Map<String, Predicate<Node>> nodeFilters,
ClassLoader classLoader) {
this.config = configLoader.getInitialConfig();
this.configLoader = configLoader;
DriverConfigProfile defaultProfile = config.getDefaultProfile();
Expand All @@ -223,6 +225,7 @@ public DefaultDriverContext(
new LazyReference<>(
"requestTracker", () -> buildRequestTracker(requestTrackerFromBuilder), cycleDetector);
this.nodeFiltersFromBuilder = nodeFilters;
this.classLoader = classLoader;
}

protected Map<String, LoadBalancingPolicy> buildLoadBalancingPolicies() {
Expand Down Expand Up @@ -671,6 +674,11 @@ public Predicate<Node> nodeFilter(String profileName) {
return nodeFiltersFromBuilder.get(profileName);
}

@Override
public ClassLoader classLoader() {
return classLoader;
}

@Override
public CodecRegistry codecRegistry() {
return codecRegistry;
Expand Down
Expand Up @@ -95,4 +95,11 @@ public interface InternalDriverContext extends DriverContext {
* {@code null}.
*/
Predicate<Node> nodeFilter(String profileName);

/**
* The {@link ClassLoader} to use to reflectively load class names defined in configuration. If
* null, the driver attempts to use {@link Thread#getContextClassLoader()} of the current thread
* or {@link com.datastax.oss.driver.internal.core.util.Reflection}'s {@link ClassLoader}.
*/
ClassLoader classLoader();
}
Expand Up @@ -298,7 +298,7 @@ private static Predicate<Node> getFilterFromConfig(DriverContext context, String
? filterFromBuilder
: (Predicate<Node>)
Reflection.buildFromConfig(
context,
(InternalDriverContext) context,
profileName,
DefaultDriverOption.LOAD_BALANCING_FILTER_CLASS,
Predicate.class)
Expand Down
Expand Up @@ -18,6 +18,7 @@
import com.datastax.oss.driver.api.core.config.DriverConfigProfile;
import com.datastax.oss.driver.api.core.config.DriverOption;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.shaded.guava.common.base.Joiner;
import com.datastax.oss.driver.shaded.guava.common.base.Preconditions;
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
Expand Down Expand Up @@ -45,11 +46,14 @@ public class Reflection {
*
* @return null if the class does not exist.
*/
public static Class<?> loadClass(String className) {
public static Class<?> loadClass(ClassLoader classLoader, String className) {
try {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
return Class.forName(className, true, contextClassLoader);
// If input classLoader is null, use current thread's ClassLoader, if that is null, use
// default (calling class') ClassLoader.
ClassLoader cl =
classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader();
if (cl != null) {
return Class.forName(className, true, cl);
} else {
return Class.forName(className);
}
Expand Down Expand Up @@ -80,7 +84,7 @@ public static Class<?> loadClass(String className) {
* configuration.
*/
public static <T> Optional<T> buildFromConfig(
DriverContext context,
InternalDriverContext context,
DriverOption classNameOption,
Class<T> expectedSuperType,
String... defaultPackages) {
Expand Down Expand Up @@ -116,7 +120,7 @@ public static <T> Optional<T> buildFromConfig(
* configuration, a single instance will be shared by all their entries.
*/
public static <T> Map<String, T> buildFromConfigProfiles(
DriverContext context,
InternalDriverContext context,
DriverOption rootOption,
Class<T> expectedSuperType,
String... defaultPackages) {
Expand Down Expand Up @@ -155,7 +159,7 @@ context, profileName, classOption(rootOption), expectedSuperType, defaultPackage
* constructor.
*/
public static <T> Optional<T> buildFromConfig(
DriverContext context,
InternalDriverContext context,
String profileName,
DriverOption classNameOption,
Class<T> expectedSuperType,
Expand All @@ -178,13 +182,13 @@ public static <T> Optional<T> buildFromConfig(
Class<?> clazz = null;
if (className.contains(".")) {
LOG.debug("Building from fully-qualified name {}", className);
clazz = loadClass(className);
clazz = loadClass(context.classLoader(), className);
} else {
LOG.debug("Building from unqualified name {}", className);
for (String defaultPackage : defaultPackages) {
String qualifiedClassName = defaultPackage + "." + className;
LOG.debug("Trying with default package {}", qualifiedClassName);
clazz = loadClass(qualifiedClassName);
clazz = loadClass(context.classLoader(), qualifiedClassName);
if (clazz != null) {
break;
}
Expand All @@ -201,13 +205,13 @@ public static <T> Optional<T> buildFromConfig(
configPath,
expectedSuperType.getName());

Constructor<?> constructor;
Constructor<? extends T> constructor;
Class<?>[] argumentTypes =
(profileName == null)
? new Class<?>[] {DriverContext.class}
: new Class<?>[] {DriverContext.class, String.class};
try {
constructor = clazz.getConstructor(argumentTypes);
constructor = clazz.asSubclass(expectedSuperType).getConstructor(argumentTypes);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(
String.format(
Expand All @@ -217,11 +221,11 @@ public static <T> Optional<T> buildFromConfig(
}
try {
@SuppressWarnings("JavaReflectionInvocation")
Object instance =
T instance =
(profileName == null)
? constructor.newInstance(context)
: constructor.newInstance(context, profileName);
return Optional.of(expectedSuperType.cast(instance));
return Optional.of(instance);
} catch (Exception e) {
// ITE just wraps an exception thrown by the constructor, get rid of it:
Throwable cause = (e instanceof InvocationTargetException) ? e.getCause() : e;
Expand Down
Expand Up @@ -19,9 +19,9 @@

import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverConfigProfile;
import com.datastax.oss.driver.api.core.context.DriverContext;
import com.datastax.oss.driver.api.core.specex.SpeculativeExecutionPolicy;
import com.datastax.oss.driver.internal.core.config.typesafe.TypesafeDriverConfig;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.internal.core.specex.ConstantSpeculativeExecutionPolicy;
import com.datastax.oss.driver.internal.core.specex.NoSpeculativeExecutionPolicy;
import com.typesafe.config.ConfigFactory;
Expand Down Expand Up @@ -55,7 +55,7 @@ public void should_build_policies_per_profile() {
+ " advanced.speculative-execution-policy.class = NoSpeculativeExecutionPolicy\n"
+ " }\n"
+ "}\n";
DriverContext context = Mockito.mock(DriverContext.class);
InternalDriverContext context = Mockito.mock(InternalDriverContext.class);
TypesafeDriverConfig config = new TypesafeDriverConfig(ConfigFactory.parseString(configSource));
Mockito.when(context.config()).thenReturn(config);

Expand Down

0 comments on commit 944056c

Please sign in to comment.