Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* A relationshipVector is the definition of the variant of relationship from one thing to another
* e.g. a specific a -> b relationship might have a different name from the main relationships
*
* <p>task <- estimates /estimate of-> estimate
* <p>{@code task <- estimates /estimate of-> estimate}
*
* <p>task to estimate would be called 'estimates' and would be 1(o):M i.e. 1 task can have 0 to
* many estimates estimate to task would be called 'estimate-of' and would be 1:1 an estimate must
Expand Down
34 changes: 34 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,44 @@
<maven-pmd-plugin.version>3.28.0</maven-pmd-plugin.version>
<spotless.version>3.8.0</spotless.version>
<google-java-format.version>1.35.0</google-java-format.version>
<maven-source-plugin.version>3.4.0</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.12.0</maven-javadoc-plugin.version>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${maven-source-plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>package</phase>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>${maven-javadoc-plugin.version}</version>
<configuration>
<doclint>none</doclint>
<quiet>true</quiet>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.diffplug.spotless</groupId>
<artifactId>spotless-maven-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package uk.co.compendiumdev.thingifier.adapter.http.apihandlers;

import java.util.logging.Level;
import java.util.logging.Logger;
import uk.co.compendiumdev.thingifier.adapter.http.apihandlers.route.ThingRoute;
import uk.co.compendiumdev.thingifier.adapter.http.lifecycle.ThingifierApiLifecycleContext;
import uk.co.compendiumdev.thingifier.api.callbacks.ThingifierApiFinalResponse;
import uk.co.compendiumdev.thingifier.api.callbacks.ThingifierApiOperationContext;
import uk.co.compendiumdev.thingifier.api.callbacks.ThingifierApiResponseCallbackDefinition;
import uk.co.compendiumdev.thingifier.api.docgen.RoutingVerb;
import uk.co.compendiumdev.thingifier.api.http.ApiRequestEnvelope;
import uk.co.compendiumdev.thingifier.api.http.HttpApiResponse;
import uk.co.compendiumdev.thingifier.api.http.ThingifierRequestContext;
import uk.co.compendiumdev.thingifier.api.spec.ThingifierApiRouteRule;

/**
* Runs route-level final-response callbacks after HTTP rendering and content negotiation.
*
* <p>Callbacks are observational in v1. If application code throws, the exception is logged and the
* already-produced response is preserved so metrics or challenge-completion code cannot
* accidentally corrupt the HTTP outcome.
*/
public final class RouteAfterResponseCallbackApplier {

private static final Logger LOGGER =
Logger.getLogger(RouteAfterResponseCallbackApplier.class.getName());

private final RouteCallbackContextFactory contextFactory;

/**
* Creates an applier for the current API runtime.
*
* @param runtime runtime used to find route rules and route metadata
*/
public RouteAfterResponseCallbackApplier(final ThingifierApiRuntime runtime) {
this.contextFactory = new RouteCallbackContextFactory(runtime);
}

/**
* Runs final-response callbacks registered on the matched route rule.
*
* @param verb route verb for route-rule lookup
* @param publicPath public request path
* @param response final rendered HTTP response
* @param requestContext active request context, or null to derive it from lifecycle
* @param lifecycle lifecycle context when processing an HTTP/lifecycle request
* @param request parsed request envelope when available
* @param bodyAvailable false when response body access should not be exposed
* @return original final response
*/
public HttpApiResponse apply(
final RoutingVerb verb,
final String publicPath,
final HttpApiResponse response,
final ThingifierRequestContext requestContext,
final ThingifierApiLifecycleContext lifecycle,
final ApiRequestEnvelope request,
final boolean bodyAvailable) {
if (response == null) {
return null;
}

final ThingifierApiRouteRule routeRule =
contextFactory.routeRuleFor(verb, publicPath).orElse(null);
if (routeRule == null || !routeRule.hasResponseCallbacks()) {
return response;
}

final ThingRoute route = contextFactory.route(lifecycle, verb, publicPath);
final ThingifierApiOperationContext context =
contextFactory.contextFor(
verb, publicPath, route, routeRule, requestContext, lifecycle, request);
final ThingifierApiFinalResponse finalResponse =
ThingifierApiFinalResponse.from(response, bodyAvailable);

for (ThingifierApiResponseCallbackDefinition definition : routeRule.responseCallbacks()) {
try {
definition.callback().run(context, finalResponse);
} catch (Exception exception) {
logCallbackFailure(definition, verb, publicPath, finalResponse, exception);
}
}
return response;
}

private void logCallbackFailure(
final ThingifierApiResponseCallbackDefinition definition,
final RoutingVerb verb,
final String publicPath,
final ThingifierApiFinalResponse response,
final Exception exception) {
LOGGER.log(
Level.SEVERE,
String.format(
"Route final response callback '%s' failed for %s %s after status %d",
definition.name(), verb, publicPath, response.statusCode()),
exception);
}
}
Loading
Loading