Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flip extension API #56

Merged
merged 1 commit into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions context/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ plugins {

val creekBaseVersion : String by extra
val creekObsVersion : String by extra
val spotBugsVersion : String by extra

dependencies {
api(project(":extension"))
api("org.creekservice:creek-base-annotation:$creekBaseVersion")
api("org.creekservice:creek-base-type:$creekBaseVersion")

implementation("org.creekservice:creek-observability-logging:$creekObsVersion")
implementation("com.github.spotbugs:spotbugs-annotations:$spotBugsVersion")
}
5 changes: 3 additions & 2 deletions context/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@

import org.creekservice.api.service.extension.CreekExtensionBuilder;
import org.creekservice.api.service.extension.CreekExtensionProvider;

module creek.service.context {
requires transitive creek.base.type;
requires transitive creek.service.extension;
requires creek.observability.logging;
requires com.github.spotbugs.annotations;

exports org.creekservice.api.service.context;

uses CreekExtensionBuilder;
uses CreekExtensionProvider;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import org.creekservice.api.base.type.temporal.Clock;
import org.creekservice.api.platform.metadata.ServiceDescriptor;
import org.creekservice.api.service.extension.CreekExtensionOptions;
import org.creekservice.api.service.extension.CreekExtensionProviders;
import org.creekservice.internal.service.context.ContextBuilder;
import org.creekservice.internal.service.context.api.Creek;

/** Defines the entry point for initialising Creek and getting hold of a {@link CreekContext}. */
public final class CreekServices {
Expand All @@ -44,7 +46,7 @@ public static CreekContext context(final ServiceDescriptor service) {
* @return the context builder.
*/
public static Builder builder(final ServiceDescriptor service) {
return new ContextBuilder(service);
return new ContextBuilder(service, new Creek(service), CreekExtensionProviders.load());
}

public interface Builder {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.creekservice.api.base.annotation.VisibleForTesting;
import org.creekservice.api.base.type.temporal.AccurateClock;
Expand All @@ -35,9 +36,9 @@
import org.creekservice.api.service.context.CreekContext;
import org.creekservice.api.service.context.CreekServices;
import org.creekservice.api.service.extension.CreekExtension;
import org.creekservice.api.service.extension.CreekExtensionBuilder;
import org.creekservice.api.service.extension.CreekExtensionOptions;
import org.creekservice.api.service.extension.CreekExtensions;
import org.creekservice.api.service.extension.CreekExtensionProvider;
import org.creekservice.internal.service.context.api.Creek;
import org.creekservice.internal.service.context.temporal.SystemEnvClockLoader;

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
Expand All @@ -50,14 +51,18 @@ public final class ContextBuilder implements CreekServices.Builder {
private final ContextFactory contextFactory;
private final UnhandledExceptionHandlerInstaller unhandledExceptionHandlerInstaller;
private final Runnable systemExit;
private final String installedExtensions;
private final List<CreekExtensionBuilder> builders;
private final Creek api;
private final List<CreekExtensionProvider> extensionProviders;
private Optional<Clock> explicitClock = Optional.empty();

public ContextBuilder(final ComponentDescriptor component) {
public ContextBuilder(
final ComponentDescriptor component,
final Creek api,
final List<CreekExtensionProvider> extensionProviders) {
this(
component,
CreekExtensions.load(),
api,
extensionProviders,
Context::new,
Thread::setDefaultUncaughtExceptionHandler,
() -> System.exit(-1));
Expand All @@ -66,23 +71,20 @@ public ContextBuilder(final ComponentDescriptor component) {
@VisibleForTesting
ContextBuilder(
final ComponentDescriptor component,
final List<CreekExtensionBuilder> builders,
final Creek api,
final List<CreekExtensionProvider> extensionProviders,
final ContextFactory contextFactory,
final UnhandledExceptionHandlerInstaller unhandledExceptionHandlerInstaller,
final Runnable systemExit) {
this.builders = List.copyOf(requireNonNull(builders, "builders"));
this.api = requireNonNull(api, "api");
this.extensionProviders =
List.copyOf(requireNonNull(extensionProviders, "extensionProviders"));
this.contextFactory = requireNonNull(contextFactory, "contextFactory");
this.unhandledExceptionHandlerInstaller =
requireNonNull(
unhandledExceptionHandlerInstaller, "unhandledExceptionHandlerInstaller");
this.systemExit = requireNonNull(systemExit, "systemExit");
this.installedExtensions =
builders.stream()
.map(CreekExtensionBuilder::name)
.collect(Collectors.joining(", ", "installed_extensions: ", ""));
this.component = requireNonNull(component, "component");

throwOnUnsupportedResourceType();
}

@Override
Expand All @@ -93,28 +95,19 @@ public CreekServices.Builder with(final Clock clock) {

@Override
public ContextBuilder with(final CreekExtensionOptions options) {
final boolean handled =
builders.stream()
.map(builder -> builder.with(options))
.reduce((b0, b1) -> b0 || b1)
.orElse(false);

if (!handled) {
throw new IllegalArgumentException(
"No registered extensions support the supplied options: "
+ options
+ ", "
+ installedExtensions);
}

api.options().add(options);
return this;
}

@Override
public CreekContext build() {
installDefaultUncaughtExceptionHandler();

return contextFactory.build(createClock(), createExtensions());
final Collection<CreekExtension> extensions = createExtensions();
final CreekContext ctx = contextFactory.build(createClock(), extensions);
throwOnUnsupportedResourceType(extensions);
throwOnUnusedOptionType(extensions);
return ctx;
}

private void installDefaultUncaughtExceptionHandler() {
Expand All @@ -129,20 +122,33 @@ private void installDefaultUncaughtExceptionHandler() {
});
}

private void throwOnUnsupportedResourceType() {
private void throwOnUnsupportedResourceType(final Collection<CreekExtension> extensions) {
final List<Object> unsupported =
component
.resources()
.filter(
resourceDef ->
builders.stream()
.noneMatch(ext -> ext.handles(resourceDef)))
.filter(resourceDef -> !api.model().hasType(resourceDef.getClass()))
.collect(Collectors.toList());

if (!unsupported.isEmpty()) {
throw new UnsupportedResourceTypesException(
component, installedExtensions, unsupported);
component, installedExtensions(extensions), unsupported);
}
}

private void throwOnUnusedOptionType(final Collection<CreekExtension> extensions) {
final Set<CreekExtensionOptions> unused = api.options().unused();
if (unused.isEmpty()) {
return;
}

final String unusedText =
unused.stream().map(Object::toString).collect(Collectors.joining(", "));

throw new IllegalArgumentException(
"No registered Creek extensions were interested in the following options: "
+ unusedText
+ ", "
+ installedExtensions(extensions));
}

private Clock createClock() {
Expand All @@ -151,8 +157,8 @@ private Clock createClock() {
}

private Collection<CreekExtension> createExtensions() {
return builders.stream()
.map(ext -> ext.build(component))
return extensionProviders.stream()
.map(this::initialize)
.collect(
Collectors.groupingBy(
CreekExtension::getClass,
Expand All @@ -167,6 +173,14 @@ private Collection<CreekExtension> createExtensions() {
.collect(Collectors.toUnmodifiableList());
}

private CreekExtension initialize(final CreekExtensionProvider provider) {
try {
return provider.initialize(api.initializing(Optional.of(provider)));
} finally {
api.initializing(Optional.empty());
}
}

private static CreekExtension throwOnExtensionTypeClash(final List<CreekExtension> extensions) {
if (extensions.size() == 1) {
return extensions.get(0);
Expand All @@ -175,6 +189,12 @@ private static CreekExtension throwOnExtensionTypeClash(final List<CreekExtensio
throw new ExtensionTypeClashException(extensions);
}

private static String installedExtensions(final Collection<CreekExtension> extensions) {
return extensions.stream()
.map(CreekExtension::name)
.collect(Collectors.joining(", ", "installed_extensions: ", ""));
}

private static class ExtensionTypeClashException extends IllegalArgumentException {
ExtensionTypeClashException(final Collection<? extends CreekExtension> extensions) {
super(
Expand Down Expand Up @@ -206,7 +226,7 @@ static final class UnsupportedResourceTypesException extends RuntimeException {
final String installedExtensions,
final List<Object> unsupportedResources) {
super(
"Component defines resources for which no extension is installed. "
"Service descriptor defines resources for which no extension is installed. "
+ "Are you missing a Creek extension on the class or module path? "
+ "component: "
+ component.name()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2022 Creek Contributors (https://github.com/creek-service)
*
* 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.creekservice.internal.service.context.api;

import static java.util.Objects.requireNonNull;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Optional;
import org.creekservice.api.base.annotation.VisibleForTesting;
import org.creekservice.api.platform.metadata.ServiceDescriptor;
import org.creekservice.api.service.extension.CreekExtensionProvider;
import org.creekservice.api.service.extension.CreekService;

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public final class Creek implements CreekService {

private final Model model;
private final Options options;
private final ServiceDescriptor service;

public Creek(final ServiceDescriptor service) {
this(service, new Options(), new Model());
}

@VisibleForTesting
Creek(final ServiceDescriptor service, final Options options, final Model model) {
this.service = requireNonNull(service, "service");
this.options = requireNonNull(options, "options");
this.model = requireNonNull(model, "model");
}

@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intentional exposure")
@Override
public Options options() {
return options;
}

@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intentional exposure")
@Override
public Model model() {
return model;
}

@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "intentional exposure")
@Override
public ServiceDescriptor service() {
return service;
}

public Creek initializing(final Optional<CreekExtensionProvider> provider) {
model.initializing(provider);
return this;
}
}
Loading