Skip to content

Instantly share code, notes, and snippets.

@mp911de
Created December 11, 2018 12:20
Show Gist options
  • Select an option

  • Save mp911de/92bbec63f0bbfc98362e2d786c2fb1e6 to your computer and use it in GitHub Desktop.

Select an option

Save mp911de/92bbec63f0bbfc98362e2d786c2fb1e6 to your computer and use it in GitHub Desktop.
/*
* Copyright 2018 the original author or authors.
*
* 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 io.r2dbc.spi.plugin;
import io.r2dbc.spi.ConnectionFactory;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ServiceLoader;
/**
* Provides access to R2DBC {@link ConnectionFactoryProvider connection factories} for programmatic lookup.
* R2DBC driver can expose their functionality as plugin by using Java's {@link ServiceLoader} mechanism with registering a {@link ConnectionFactoryProvider}.<p>
* This class accepts {@link ConnectionFactoryOptions} to determine a {@link ConnectionFactoryProvider} and to create a {@link ConnectionFactory}.
*
* @author Mark Paluch
* @see ServiceLoader
* @see ConnectionFactoryProvider
*/
public abstract class ConnectionFactories {
private ConnectionFactories() {
}
/**
* Creates a {@link ConnectionFactory} using registered R2DBC plugins through Java's {@link ServiceLoader} mechanism.
*
* @param connectionFactoryOptions
* @return
* @throws IllegalArgumentException if there is no {@link ConnectionFactoryProvider} can create a {@link ConnectionFactory} from {@link ConnectionFactoryOptions}.
*/
public ConnectionFactory createConnectionFactory(ConnectionFactoryOptions connectionFactoryOptions) {
ServiceLoader<ConnectionFactoryProvider> providers = loadService(ConnectionFactoryProvider.class, ConnectionFactories.class.getClassLoader());
for (ConnectionFactoryProvider provider : providers) {
if (provider.supports(connectionFactoryOptions)) {
return provider.createConnectionFactory();
}
}
throw new IllegalArgumentException(String.format("No suitable ConnectionFactoryProvider found for %s", connectionFactoryOptions));
}
/**
* Returns whether a {@link ConnectionFactory} using registered R2DBC plugins through Java's {@link ServiceLoader} mechanism supports the configuration supplied through
* {@link ConnectionFactoryOptions}.
*
* @param connectionFactoryOptions
* @return
*/
public boolean supports(ConnectionFactoryOptions connectionFactoryOptions) {
ServiceLoader<ConnectionFactoryProvider> providers = loadService(ConnectionFactoryProvider.class, ConnectionFactories.class.getClassLoader());
for (ConnectionFactoryProvider provider : providers) {
if (provider.supports(connectionFactoryOptions)) {
return true;
}
}
return false;
}
private static <T> ServiceLoader<T> loadService(Class<T> service, ClassLoader classLoader) {
return AccessController.doPrivileged(
(PrivilegedAction<ServiceLoader<T>>) () -> ServiceLoader.load(service, classLoader)
);
}
}
/*
* Copyright 2018 the original author or authors.
*
* 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 io.r2dbc.spi.plugin;
import io.r2dbc.spi.Nullable;
import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author Mark Paluch
*/
public class ConnectionFactoryOptions {
/**
* Port number.
*/
public static final Option<Integer> PORT = Option.valueOf("port");
/**
* Endpoint host name.
*/
public static final Option<String> HOST = Option.valueOf("host");
/**
* User for login.
*/
public static final Option<String> USER = Option.valueOf("user");
/**
* Password for login.
*/
public static final Option<CharSequence> PASSWORD = Option.valueOf("password");
/**
* Initial database name.
*/
public static final Option<String> DATABASE = Option.valueOf("database");
/**
* Driver name.
*/
public static final Option<String> DRIVER = Option.valueOf("driver");
/**
* Driver protocol name. Typically represented as {@code tcp} or a database vendor-specific protocol string.
*/
public static final Option<String> PROTOCOL = Option.valueOf("protocol");
/**
* Configures whether to support SSL.
*/
public static final Option<Boolean> SSL = Option.valueOf("ssl");
/**
* Connection timeout.
*/
public static final Option<Duration> CONNECT_TIMEOUT = Option.valueOf("connectTimeout");
private final Map<Option<?>, Object> options;
private ConnectionFactoryOptions(Map<Option<?>, Object> options) {
this.options = options;
}
public static Builder builder() {
return new Builder();
}
@Nullable
@SuppressWarnings("unchecked")
public <T> T getValue(Option<T> option) {
return (T) this.options.get(option);
}
@Nullable
public <T> T getRequiredValue(Option<T> option) {
T value = getValue(option);
if (value != null) {
return value;
}
throw new IllegalStateException("Value for option " + option + " is null!");
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(getClass().getSimpleName()).append(' ').append(this.options);
return sb.toString();
}
public static class Builder {
private final Map<Option<?>, Object> options = new HashMap<>();
private Builder() {
}
public <T> Builder option(Option<T> option, T value) {
this.options.put(option, value);
return this;
}
public ConnectionFactoryOptions build() {
return new ConnectionFactoryOptions(Collections.unmodifiableMap(new LinkedHashMap<>(options)));
}
}
}
/*
* Copyright 2018 the original author or authors.
*
* 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 io.r2dbc.spi.plugin;
import io.r2dbc.spi.ConnectionFactory;
/**
* @author Mark Paluch
*/
public interface ConnectionFactoryProvider {
boolean supports(ConnectionFactoryOptions options);
ConnectionFactory createConnectionFactory();
}
/*
* Copyright 2018 the original author or authors.
*
* 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 io.r2dbc.spi.plugin;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A pool of {@link Option}s.
*
* @param <T> the type of the {@link Option}.
*/
abstract class ConstantPool<T extends Option<?>> {
private final ConcurrentMap<String, T> constants = new ConcurrentHashMap<>();
private final AtomicInteger nextId = new AtomicInteger(1);
/**
* Returns the {@link Option} which is assigned to the specified {@code name}.
* If there's no such {@link Option}, a new one will be created and returned.
* Once created, the subsequent calls with the same {@code name} will always return the previously created one
* (i.e. singleton.)
*
* @param name the name of the {@link Option}
*/
public T valueOf(String name) {
checkNotNullAndNotEmpty(name);
return getOrCreate(name);
}
/**
* Get existing constant by name or creates new one if not exists. Threadsafe
*
* @param name the name of the {@link Constant}
*/
private T getOrCreate(String name) {
T constant = constants.get(name);
if (constant == null) {
T tempConstant = newConstant(nextId(), name);
constant = constants.putIfAbsent(name, tempConstant);
if (constant == null) {
return tempConstant;
}
}
return constant;
}
private static String checkNotNullAndNotEmpty(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("empty name");
}
return name;
}
protected abstract T newConstant(int id, String name);
private int nextId() {
return nextId.getAndIncrement();
}
}
/*
* Copyright 2018 the original author or authors.
*
* 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 io.r2dbc.spi.plugin;
/**
* @author Mark Paluch
*/
public class Option<T> {
private static final ConstantPool<Option<Object>> pool = new ConstantPool<Option<Object>>() {
@Override
protected Option<Object> newConstant(int id, String name) {
return new Option<Object>(id, name);
}
};
private final int id;
private final String name;
private Option(int id, String name) {
this.id = id;
this.name = name;
}
/**
* Returns the {@link Option} of the specified name.
*/
@SuppressWarnings("unchecked")
public static <T> Option<T> valueOf(String name) {
return (Option<T>) pool.valueOf(name);
}
public String name() {
return name;
}
@Override
public String toString() {
return name();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment