Skip to content

Commit

Permalink
Merge pull request jdbi#2313 from hgschmie/option-docs
Browse files Browse the repository at this point in the history
Documentation for extension framework
  • Loading branch information
hgschmie committed Apr 12, 2023
2 parents 2f9d588 + 724adf0 commit 5d57a80
Show file tree
Hide file tree
Showing 90 changed files with 2,631 additions and 943 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,20 @@
package org.jdbi.v3.commonstext.internal;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.jdbi.v3.commonstext.StringSubstitutorTemplateEngine;
import org.jdbi.v3.commonstext.UseStringSubstitutorTemplateEngine;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.extension.ExtensionConfigurer;
import org.jdbi.v3.core.extension.SimpleExtensionConfigurer;
import org.jdbi.v3.core.statement.SqlStatements;
import org.jdbi.v3.core.statement.TemplateEngine;

public class UseStringSubstitutorTemplateEngineImpl implements ExtensionConfigurer {
public class UseStringSubstitutorTemplateEngineImpl extends SimpleExtensionConfigurer {

@Override
public void configureForType(ConfigRegistry registry, Annotation annotation, Class<?> sqlObjectType) {
public void configure(ConfigRegistry config, Annotation annotation, Class<?> sqlObjectType) {
UseStringSubstitutorTemplateEngine anno = (UseStringSubstitutorTemplateEngine) annotation;
TemplateEngine engine = StringSubstitutorTemplateEngine.between(anno.prefix(), anno.suffix(), anno.escape());
registry.get(SqlStatements.class).setTemplateEngine(engine);
}

@Override
public void configureForMethod(ConfigRegistry registry, Annotation annotation, Class<?> sqlObjectType, Method method) {
configureForType(registry, annotation, sqlObjectType);
config.get(SqlStatements.class).setTemplateEngine(engine);
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/org/jdbi/v3/core/Jdbi.java
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ public <E> E onDemand(Class<E> extensionType) {
throw new IllegalArgumentException("On-demand extensions are only supported for interfaces.");
}
if (!getConfig(Extensions.class).hasExtensionFor(extensionType)) {
throw new NoSuchExtensionException("Extension not found: " + extensionType);
throw new NoSuchExtensionException(extensionType, "Extension not found: %s");
}

return getConfig(OnDemandExtensions.class).create(this, extensionType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.jdbi.v3.core.extension.ExtensionHandler.ExtensionHandlerFactory;

/**
* Extension handler factory for bridge methods. Forwards bridge methods to matching candidates.
*/
Expand All @@ -36,7 +34,7 @@ public boolean accepts(Class<?> extensionType, Method method) {
}

@Override
public Optional<ExtensionHandler> buildExtensionHandler(Class<?> extensionType, Method method) {
public Optional<ExtensionHandler> createExtensionHandler(Class<?> extensionType, Method method) {

List<Method> candidates = Stream.of(extensionType.getMethods())
.filter(candidate -> !candidate.isBridge())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;

import org.jdbi.v3.core.config.ConfigCustomizer;
import org.jdbi.v3.meta.Alpha;
Expand All @@ -33,7 +34,9 @@ public interface ConfigCustomizerFactory {
* @param extensionType The extension type
* @return A {@link Collection} of {@link ConfigCustomizer} objects. Must not be null
*/
Collection<ConfigCustomizer> forExtensionType(Class<?> extensionType);
default Collection<ConfigCustomizer> forExtensionType(Class<?> extensionType) {
return Collections.emptyList();
}

/**
* Creates a collection of {@link ConfigCustomizer} instances for an extension type method.
Expand All @@ -42,5 +45,7 @@ public interface ConfigCustomizerFactory {
* @param method The method on the extension type
* @return A {@link Collection} of {@link ConfigCustomizer} objects. Must not be null
*/
Collection<ConfigCustomizer> forExtensionMethod(Class<?> extensionType, Method method);
default Collection<ConfigCustomizer> forExtensionMethod(Class<?> extensionType, Method method) {
return Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import java.lang.reflect.Method;
import java.util.Optional;

import org.jdbi.v3.core.extension.ExtensionHandler.ExtensionHandlerFactory;

/**
* Provides {@link ExtensionHandler} instances for interface default methods.
*/
Expand All @@ -31,7 +29,7 @@ public boolean accepts(Class<?> extensionType, Method method) {
}

@Override
public Optional<ExtensionHandler> buildExtensionHandler(Class<?> extensionType, Method method) {
public Optional<ExtensionHandler> createExtensionHandler(Class<?> extensionType, Method method) {
try {
return Optional.of(ExtensionHandler.createForSpecialMethod(method));
} catch (IllegalAccessException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,27 @@
*/
@Alpha
public interface ExtensionConfigurer {

/**
* Updates configuration for the given annotation on an extension type.
*
* @param registry the registry to configure
* @param config the config to configure
* @param annotation the annotation
* @param extensionType the extension type which was annotated
*/
default void configureForType(ConfigRegistry registry, Annotation annotation, Class<?> extensionType) {
default void configureForType(ConfigRegistry config, Annotation annotation, Class<?> extensionType) {
throw new UnsupportedOperationException("Not supported for type");
}

/**
* Configures the registry for the given annotation on a extension type method.
* Configures the config for the given annotation on a extension type method.
*
* @param registry the registry to configure
* @param config the config to configure
* @param annotation the annotation
* @param extensionType the extension type
* @param method the method which was annotated
*/
default void configureForMethod(ConfigRegistry registry, Annotation annotation, Class<?> extensionType, Method method) {
default void configureForMethod(ConfigRegistry config, Annotation annotation, Class<?> extensionType, Method method) {
throw new UnsupportedOperationException("Not supported for method");
}

}
31 changes: 19 additions & 12 deletions core/src/main/java/org/jdbi/v3/core/extension/ExtensionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@

import org.jdbi.v3.core.config.ConfigCustomizer;
import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.extension.ExtensionHandler.ExtensionHandlerFactory;
import org.jdbi.v3.meta.Alpha;

/**
* Factory interface used to produce Jdbi extension objects. A factory can provide additional
* pieces of information by overriding the various default methods.
*/
@FunctionalInterface
public interface ExtensionFactory {

/**
Expand All @@ -36,24 +36,29 @@ public interface ExtensionFactory {
@Alpha
enum FactoryFlag {
/**
* The factory has no actual object to attach to. It will register a method handler for every method on an extension type.
* The factory provides a concrete instance to back the extension type.
* <br>
* E.g. the SQLObject handler will process every method in an interface class without requiring an implementation of the extension
* type. The extension framework will execute the method handlers and pass in a proxy object instead of an underlying instance.
* Unless this flag is present, factories do not
* create an object to attach to but register method handlers for every method on an extension type.
* <br>
* E.g. the SQLObject handler is an example of a virtual factory that processes every method in an interface class without requiring
* an implementation of the extension type.
* The extension framework will execute the method handlers and pass in a proxy object instead of an underlying instance.
* <br>
* When this flag is present, the {@link ExtensionFactory#attach(Class, HandleSupplier)} method will never be called.
*/
VIRTUAL_FACTORY,
NON_VIRTUAL_FACTORY,
/**
* The factory supports extending class objects (not just interfaces). Class objects are not wrapped into a proxy and
* the factory takes full responsibility for creating and managing invocation handlers.
* Do not wrap the backing object methods into {@link ExtensionHandler} instances and return a
* {@link java.lang.reflect.Proxy} instance but return it as is. This allows the factory to
* suport class objects as well as interfaces.
* <br>
* This is a corner use case and should normally not be used by any standard extension.
* <br>
* Legacy extension factories that need every method on an interface forwarded to the underlying implementation class
* can set this flag to bypass metadata creation and the proxy logic of the extension framework.
* can set this flag to bypass the proxy logic of the extension framework.
*/
CLASSES_ARE_SUPPORTED
DONT_USE_PROXY
}

/**
Expand All @@ -65,7 +70,7 @@ enum FactoryFlag {
boolean accepts(Class<?> extensionType);

/**
* Attaches an extension type. This method is not called if {@link #getFactoryFlags()} contains {@link FactoryFlag#VIRTUAL_FACTORY}.
* Attaches an extension type. This method is not called if {@link #getFactoryFlags()} contains {@link FactoryFlag#NON_VIRTUAL_FACTORY}.
*
* @param extensionType The extension type
* @param handleSupplier Supplies the database handle. This supplier may lazily open a Handle on the first
Expand All @@ -76,7 +81,9 @@ enum FactoryFlag {
* @throws IllegalArgumentException if the extension type is not supported by this factory
* @see org.jdbi.v3.core.Jdbi#onDemand(Class)
*/
<E> E attach(Class<E> extensionType, HandleSupplier handleSupplier);
default <E> E attach(Class<E> extensionType, HandleSupplier handleSupplier) {
throw new UnsupportedOperationException("Virtual factories do not support attach()");
}

/**
* Returns a collection of {@link ExtensionHandlerFactory} objects. These factories are used in
Expand Down Expand Up @@ -138,7 +145,7 @@ default Collection<ConfigCustomizerFactory> getConfigCustomizerFactories(ConfigR
* @since 3.38.0
*/
@Alpha
default void buildExtensionInitData(ExtensionMetadata.Builder builder) {}
default void buildExtensionMetadata(ExtensionMetadata.Builder builder) {}

/**
* Returns a set of {@link FactoryFlag}s that describe the extension factory.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,22 @@
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.extension.ExtensionHandler.ExtensionHandlerFactory;
import org.jdbi.v3.core.extension.ExtensionMetadata.Builder;
import org.jdbi.v3.core.extension.ExtensionMetadata.ExtensionHandlerInvoker;
import org.jdbi.v3.core.internal.JdbiClassUtils;

import static java.lang.String.format;

import static org.jdbi.v3.core.extension.ExtensionFactory.FactoryFlag.CLASSES_ARE_SUPPORTED;
import static org.jdbi.v3.core.extension.ExtensionFactory.FactoryFlag.VIRTUAL_FACTORY;
import static org.jdbi.v3.core.extension.ExtensionFactory.FactoryFlag.DONT_USE_PROXY;
import static org.jdbi.v3.core.extension.ExtensionFactory.FactoryFlag.NON_VIRTUAL_FACTORY;
import static org.jdbi.v3.core.extension.ExtensionHandler.EQUALS_HANDLER;
import static org.jdbi.v3.core.extension.ExtensionHandler.HASHCODE_HANDLER;
import static org.jdbi.v3.core.extension.ExtensionHandler.NULL_HANDLER;
Expand All @@ -54,7 +55,9 @@ ExtensionFactory getDelegatedFactory() {

@Override
public Collection<ExtensionHandlerFactory> getExtensionHandlerFactories(ConfigRegistry config) {
return delegatedFactory.getExtensionHandlerFactories(config);
return Collections.unmodifiableCollection(delegatedFactory.getExtensionHandlerFactories(config).stream()
.map(FilteringExtensionHandlerFactory::forDelegate)
.collect(Collectors.toList()));
}

@Override
Expand All @@ -68,8 +71,8 @@ public Collection<ConfigCustomizerFactory> getConfigCustomizerFactories(ConfigRe
}

@Override
public void buildExtensionInitData(Builder builder) {
delegatedFactory.buildExtensionInitData(builder);
public void buildExtensionMetadata(Builder builder) {
delegatedFactory.buildExtensionMetadata(builder);
}

@Override
Expand All @@ -90,7 +93,7 @@ public <E> E attach(Class<E> extensionType, HandleSupplier handleSupplier) {
//
// The extension factory is now responsible for managing the method invocations itself.
//
if (factoryFlags.contains(CLASSES_ARE_SUPPORTED)) {
if (factoryFlags.contains(DONT_USE_PROXY)) {
return delegatedFactory.attach(extensionType, handleSupplier);
}

Expand All @@ -104,7 +107,7 @@ public <E> E attach(Class<E> extensionType, HandleSupplier handleSupplier) {

extensions.onCreateProxy();

final ExtensionMetadata extensionMetaData = extensions.findMetadata(extensionType, config, delegatedFactory);
final ExtensionMetadata extensionMetaData = extensions.findMetadata(extensionType, delegatedFactory);
final ConfigRegistry instanceConfig = extensionMetaData.createInstanceConfiguration(config);

Map<Method, ExtensionHandlerInvoker> handlers = new HashMap<>();
Expand All @@ -116,7 +119,7 @@ public <E> E attach(Class<E> extensionType, HandleSupplier handleSupplier) {
// if the object created by the delegated factory has actual methods (it is not delegating), attach the
// delegate and pass it to the handlers. Otherwise assume that there is no backing object and do not call
// attach.
final Object delegatedInstance = factoryFlags.contains(VIRTUAL_FACTORY) ? proxy : delegatedFactory.attach(extensionType, handleSupplier);
final Object delegatedInstance = factoryFlags.contains(NON_VIRTUAL_FACTORY) ? delegatedFactory.attach(extensionType, handleSupplier) : proxy;

// add proxy specific methods (toString, equals, hashCode, finalize)
// those will only be added if they don't already exist in the method handler map.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;
import java.util.Optional;

import org.jdbi.v3.core.config.ConfigRegistry;
import org.jdbi.v3.core.internal.exceptions.Unchecked;
Expand Down Expand Up @@ -117,29 +116,4 @@ static ExtensionHandler createForMethodHandle(MethodHandle methodHandle) {
return Unchecked.<Object[], Object>function(methodHandle.bindTo(target)::invokeWithArguments).apply(args);
};
}

/**
* A factory to create {@link ExtensionHandler} instances.
*/
interface ExtensionHandlerFactory {

/**
* Determines whether the factory can create an {@link ExtensionHandler} for combination of extension type and method.
*
* @param extensionType The extension type class
* @param method A method
* @return True if the factory can create an extension handler for extension type and method, false otherwise
*/
boolean accepts(Class<?> extensionType, Method method);

/**
* Returns an {@link ExtensionHandler} for a extension type and method combination.
* @param extensionType The extension type class
* @param method A method
* @return An {@link ExtensionHandler} instance wrapped into an {@link Optional}. The optional can be empty. This is necessary to retrofit old code
* that does not have an accept/build code pair but unconditionally tries to build a handler and returns empty if it can not. New code should always
* return <code>Optional.of(extensionHandler}</code> and never return <code>Optional.empty()</code>
*/
Optional<ExtensionHandler> buildExtensionHandler(Class<?> extensionType, Method method);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.jdbi.v3.core.extension;

import java.lang.reflect.Method;
import java.util.Optional;

/**
* A factory to create {@link ExtensionHandler} instances.
*/
public interface ExtensionHandlerFactory {

/**
* Determines whether the factory can create an {@link ExtensionHandler} for combination of extension type and method.
*
* @param extensionType The extension type class
* @param method A method
* @return True if the factory can create an extension handler for extension type and method, false otherwise
*/
boolean accepts(Class<?> extensionType, Method method);

/**
* Returns an {@link ExtensionHandler} instance for a extension type and method combination.
*
* @param extensionType The extension type class
* @param method A method
* @return An {@link ExtensionHandler} instance wrapped into an {@link Optional}. The optional can be empty. This is necessary to retrofit old code
* that does not have an accept/build code pair but unconditionally tries to build a handler and returns empty if it can not. New code should always
* return <code>Optional.of(extensionHandler}</code> and never return <code>Optional.empty()</code>
*/
Optional<ExtensionHandler> createExtensionHandler(Class<?> extensionType, Method method);
}
Loading

0 comments on commit 5d57a80

Please sign in to comment.