
com.io7m.cardant.server.controller.command_exec.CACommandContext Maven / Gradle / Ivy
/*
* Copyright © 2023 Mark Raynsford https://www.io7m.com
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package com.io7m.cardant.server.controller.command_exec;
import com.io7m.cardant.database.api.CADatabaseException;
import com.io7m.cardant.database.api.CADatabaseTransactionType;
import com.io7m.cardant.error_codes.CAErrorCode;
import com.io7m.cardant.error_codes.CAStandardErrorCodes;
import com.io7m.cardant.model.CAValidityException;
import com.io7m.cardant.protocol.api.CAProtocolException;
import com.io7m.cardant.protocol.api.CAProtocolMessageType;
import com.io7m.cardant.security.CASecurityException;
import com.io7m.cardant.server.service.clock.CAServerClock;
import com.io7m.cardant.server.service.sessions.CASession;
import com.io7m.cardant.server.service.telemetry.api.CAServerTelemetryServiceType;
import com.io7m.cardant.strings.CAStringConstantType;
import com.io7m.cardant.strings.CAStrings;
import com.io7m.repetoir.core.RPServiceDirectoryType;
import io.opentelemetry.api.trace.Tracer;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* The context for execution of a command (or set of commands in a
* transaction).
*
* @param The type of error messages
*/
public abstract class CACommandContext
{
private final RPServiceDirectoryType services;
private final UUID requestId;
private final CADatabaseTransactionType transaction;
private final CAServerClock clock;
private final CAStrings strings;
private final CASession session;
private final String remoteHost;
private final String remoteUserAgent;
private final Tracer tracer;
private final Map attributes;
/**
* The context for execution of a command (or set of commands in a
* transaction).
*
* @param inServices The service directory
* @param inRequestId The request ID
* @param inTransaction The transaction
* @param inSession The user session
* @param inRemoteHost The remote host
* @param inRemoteUserAgent The remote user agent
*/
public CACommandContext(
final RPServiceDirectoryType inServices,
final UUID inRequestId,
final CADatabaseTransactionType inTransaction,
final CASession inSession,
final String inRemoteHost,
final String inRemoteUserAgent)
{
this.services =
Objects.requireNonNull(inServices, "services");
this.requestId =
Objects.requireNonNull(inRequestId, "requestId");
this.transaction =
Objects.requireNonNull(inTransaction, "transaction");
this.session =
Objects.requireNonNull(inSession, "inSession");
this.remoteHost =
Objects.requireNonNull(inRemoteHost, "remoteHost");
this.remoteUserAgent =
Objects.requireNonNull(inRemoteUserAgent, "remoteUserAgent");
this.clock =
inServices.requireService(CAServerClock.class);
this.strings =
inServices.requireService(CAStrings.class);
this.tracer =
inServices.requireService(CAServerTelemetryServiceType.class)
.tracer();
this.attributes =
new HashMap<>(0);
}
/**
* @return The attributes as an immutable map
*/
public final Map attributes()
{
return Map.copyOf(this.attributes);
}
/**
* Set an attribute for error reporting.
*
* @param key The key string constant
* @param value The value
*/
public final void setAttribute(
final CAStringConstantType key,
final String value)
{
Objects.requireNonNull(key, "constant");
Objects.requireNonNull(value, "value");
this.attributes.put(key, value);
}
/**
* @return The user session
*/
public final CASession session()
{
return this.session;
}
/**
* @return The remote host
*/
public final String remoteHost()
{
return this.remoteHost;
}
/**
* @return The remote user agent
*/
public final String remoteUserAgent()
{
return this.remoteUserAgent;
}
/**
* @return The service directory used during execution
*/
public final RPServiceDirectoryType services()
{
return this.services;
}
/**
* @return The ID of the incoming request
*/
public final UUID requestId()
{
return this.requestId;
}
/**
* @return The database transaction
*/
public final CADatabaseTransactionType transaction()
{
return this.transaction;
}
/**
* Localize the given string.
*
* @param constant The string
* @param objects The object arguments
*
* @return The localized string
*/
public final String local(
final CAStringConstantType constant,
final Object... objects)
{
return this.strings.format(constant, objects);
}
/**
* @return The OpenTelemetry tracer
*/
public final Tracer tracer()
{
return this.tracer;
}
/**
* @return The current time
*/
public final OffsetDateTime now()
{
return this.clock.now();
}
/**
* Produce an exception indicating an error, with a formatted error message.
*
* @param statusCode The HTTP status kind
* @param errorCode The error kind
* @param errorAttributes The error attributes
* @param messageId The string resource message ID
* @param args The string resource format arguments
*
* @return An execution failure
*/
public final CACommandExecutionFailure failFormatted(
final int statusCode,
final CAErrorCode errorCode,
final Map errorAttributes,
final CAStringConstantType messageId,
final Object... args)
{
return this.fail(
statusCode,
errorCode,
this.strings.format(messageId, args),
errorAttributes
);
}
/**
* Produce an exception indicating an error, with a formatted error message.
*
* @param statusCode The HTTP status kind
* @param exception The exception
* @param errorCode The error kind
* @param errorAttributes The error attributes
* @param messageId The string resource message ID
* @param args The string resource format arguments
*
* @return An execution failure
*/
public final CACommandExecutionFailure failFormatted(
final Exception exception,
final int statusCode,
final CAErrorCode errorCode,
final Map errorAttributes,
final CAStringConstantType messageId,
final Object... args)
{
return new CACommandExecutionFailure(
this.strings.format(messageId, args),
exception,
errorCode,
this.localMap(errorAttributes),
Optional.empty(),
this.requestId,
statusCode
);
}
private Map localMap(
final Map unformattedAttributes)
{
return unformattedAttributes.entrySet()
.stream()
.map(e -> Map.entry(this.local(e.getKey()), e.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
/**
* Produce an exception indicating an error, with a string constant message.
*
* @param statusCode The HTTP status kind
* @param errorCode The error kind
* @param errorAttributes The error attributes
* @param message The string message
*
* @return An execution failure
*/
public final CACommandExecutionFailure fail(
final int statusCode,
final CAErrorCode errorCode,
final String message,
final Map errorAttributes)
{
return new CACommandExecutionFailure(
message,
errorCode,
this.localMap(errorAttributes),
Optional.empty(),
this.requestId,
statusCode
);
}
/**
* Produce an exception indicating an error, with a string constant message.
*
* @param statusCode The HTTP status kind
* @param errorCode The error kind
* @param errorAttributes The error attributes
* @param message The string message
* @param cause The cause
*
* @return An execution failure
*/
public final CACommandExecutionFailure failWithCause(
final Exception cause,
final int statusCode,
final CAErrorCode errorCode,
final String message,
final Map errorAttributes)
{
return new CACommandExecutionFailure(
message,
cause,
errorCode,
errorAttributes,
Optional.empty(),
this.requestId,
statusCode
);
}
/**
* Produce an exception indicating a database error.
*
* @param e The database exception
*
* @return An execution failure
*/
public final CACommandExecutionFailure failDatabase(
final CADatabaseException e)
{
return new CACommandExecutionFailure(
e.getMessage(),
e,
e.errorCode(),
e.attributes(),
Optional.empty(),
this.requestId,
500
);
}
/**
* Produce an exception indicating a security policy error.
*
* @param e The security exception
*
* @return An execution failure
*/
public CACommandExecutionFailure failSecurity(
final CASecurityException e)
{
return new CACommandExecutionFailure(
e.getMessage(),
e,
CAStandardErrorCodes.errorSecurityPolicyDenied(),
e.attributes(),
Optional.empty(),
this.requestId,
400
);
}
/**
* Produce an exception indicating a protocol error.
*
* @param e The exception
*
* @return An execution failure
*/
public CACommandExecutionFailure failProtocol(
final CAProtocolException e)
{
return new CACommandExecutionFailure(
e.getMessage(),
e,
CAStandardErrorCodes.errorProtocol(),
e.attributes(),
Optional.empty(),
this.requestId,
400
);
}
/**
* Produce an exception indicating a validation error.
*
* @param e The exception
*
* @return An execution failure
*/
public CACommandExecutionFailure failValidity(
final CAValidityException e)
{
return new CACommandExecutionFailure(
e.getMessage(),
e,
CAStandardErrorCodes.errorProtocol(),
Map.of(),
Optional.empty(),
this.requestId,
400
);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy