Current.java
// Copyright (c) ZeroC, Inc.
package com.zeroc.Ice;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletionException;
import java.util.function.BiConsumer;
/** Provides information about an incoming request being dispatched. */
public final class Current implements Cloneable {
/** The object adapter that received the request. */
public final ObjectAdapter adapter;
/**
* The connection that received the request. It's null when the invocation and dispatch are
* collocated.
*/
public final Connection con;
/** The identity of the target Ice object. */
public final Identity id;
/** The facet of the target Ice object. */
public String facet;
/** The name of the operation. */
public String operation;
/** The operation mode (idempotent or not). */
public OperationMode mode;
/** The request context. */
public final Map<String, String> ctx;
/** The request ID. 0 means the request is a one-way request. */
public final int requestId;
/** The encoding of the request payload. */
public EncodingVersion encoding;
/**
* Constructs a Current object.
*
* @param adapter The adapter.
* @param con The connection. Can be null.
* @param id The identity of the target object.
* @param facet The facet of the target object.
* @param operation The name of the operation.
* @param mode The operation mode.
* @param ctx The request context.
* @param requestId The request ID.
* @param encoding The encoding of the payload.
*/
public Current(
ObjectAdapter adapter,
Connection con,
Identity id,
String facet,
String operation,
OperationMode mode,
Map<String, String> ctx,
int requestId,
EncodingVersion encoding) {
// We may occasionally construct a Current with a null adapter, however we never
// return such a current to the application code.
Objects.requireNonNull(id);
Objects.requireNonNull(facet);
Objects.requireNonNull(operation);
Objects.requireNonNull(mode);
Objects.requireNonNull(ctx);
// Objects.requireNonNull(encoding);
this.adapter = adapter;
this.con = con;
this.id = id;
this.facet = facet;
this.operation = operation;
this.mode = mode;
this.ctx = ctx;
this.requestId = requestId;
this.encoding = encoding;
}
/**
* Ensures the operation mode of an incoming request is not idempotent. The generated code calls
* this method to ensure that when an operation's mode is not idempotent (locally), the incoming
* request's operation mode is not idempotent.
*
* @throws MarshalException Thrown when the request's operation mode is {@link
* OperationMode#Idempotent} or {@link OperationMode#Nonmutating}.
*/
public void checkNonIdempotent() {
if (mode != OperationMode.Normal) {
throw new MarshalException(
String.format(
"Operation mode mismatch for operation '%s': received %s for non-idempotent operation",
operation, mode));
}
}
@Override
public Current clone() {
Current clone = null;
try {
clone = (Current) super.clone();
} catch (CloneNotSupportedException ex) {
assert false; // impossible
}
return clone;
}
/**
* Creates an outgoing response with reply status {@link ReplyStatus#Ok}.
*
* @param <TResult> The type of result.
* @param result The result to marshal into the response payload.
* @param marshal The action that marshals result into an output stream.
* @param formatType The class format.
* @return A new outgoing response.
*/
public <TResult> OutgoingResponse createOutgoingResponse(
TResult result, BiConsumer<OutputStream, TResult> marshal, FormatType formatType) {
OutputStream ostr = startReplyStream();
if (requestId != 0) {
try {
ostr.startEncapsulation(encoding, formatType);
marshal.accept(ostr, result);
ostr.endEncapsulation();
return new OutgoingResponse(ostr);
} catch (Exception exception) {
return createOutgoingResponse(exception);
}
} else {
assert false : "A one-way request cannot return a response";
return new OutgoingResponse(ostr);
}
}
/**
* Creates an empty outgoing response with reply status {@link ReplyStatus#Ok}.
*
* @return An outgoing response with an empty payload.
*/
public OutgoingResponse createEmptyOutgoingResponse() {
OutputStream ostr = startReplyStream();
if (requestId != 0) {
try {
ostr.writeEmptyEncapsulation(encoding);
} catch (Exception ex) {
return createOutgoingResponse(ex);
}
}
return new OutgoingResponse(ostr);
}
/**
* Creates an outgoing response with the specified payload.
*
* @param ok When true, the reply status of the response is {@link ReplyStatus#Ok}; otherwise,
* it's {@link ReplyStatus#UserException}.
* @param encapsulation The payload of the response.
* @return A new outgoing response.
*/
public OutgoingResponse createOutgoingResponse(boolean ok, byte[] encapsulation) {
// For compatibility with the Ice 3.7 and earlier.
encapsulation = encapsulation != null ? encapsulation : new byte[0];
OutputStream ostr = startReplyStream(ok ? ReplyStatus.Ok : ReplyStatus.UserException);
if (requestId != 0) {
try {
if (encapsulation.length > 0) {
ostr.writeEncapsulation(encapsulation);
} else {
ostr.writeEmptyEncapsulation(encoding);
}
} catch (Throwable ex) {
return createOutgoingResponse(ex);
}
}
return new OutgoingResponse(
ok ? ReplyStatus.Ok.value() : ReplyStatus.UserException.value(),
null,
null,
ostr);
}
/**
* Creates an outgoing response that marshals an exception.
*
* @param exception The exception to marshal into the response payload.
* @return A new outgoing response.
*/
public OutgoingResponse createOutgoingResponse(Throwable exception) {
assert exception != null;
try {
if (exception instanceof CompletionException completionException) {
// Unwrap the completion exception.
exception = completionException.getCause();
assert exception != null;
}
return createOutgoingResponseCore(exception);
} catch (Throwable ex) {
// Try a second time with the marshal exception. This should not fail.
return createOutgoingResponseCore(ex);
}
}
private OutgoingResponse createOutgoingResponseCore(Throwable exc) {
OutputStream ostr;
if (requestId != 0) {
// The default class format doesn't matter since we always encode user exceptions in
// Sliced format.;
ostr =
new OutputStream(
Protocol.currentProtocolEncoding, FormatType.SlicedFormat, false);
ostr.writeBlob(Protocol.replyHdr);
ostr.writeInt(requestId);
} else {
ostr = new OutputStream();
}
int replyStatus;
String exceptionId;
String dispatchExceptionMessage = null;
// TODO: replace by switch statement with Java 21
if (exc instanceof UserException ex) {
exceptionId = null;
replyStatus = ReplyStatus.UserException.value();
if (requestId != 0) {
ReplyStatus.ice_write(ostr, ReplyStatus.UserException);
ostr.startEncapsulation(encoding, FormatType.SlicedFormat);
ostr.writeException(ex);
ostr.endEncapsulation();
}
} else if (exc instanceof DispatchException ex) {
exceptionId = ex.ice_id();
replyStatus = ex.replyStatus;
dispatchExceptionMessage = ex.getMessage();
} else if (exc instanceof LocalException ex) {
exceptionId = ex.ice_id();
replyStatus = ReplyStatus.UnknownLocalException.value();
} else {
replyStatus = ReplyStatus.UnknownException.value();
exceptionId =
exc.getClass().getName() != null
? exc.getClass().getName()
: "java.lang.Exception";
}
if (replyStatus > ReplyStatus.UserException.value() && requestId != 0) {
// two-way, so we marshal a reply
// We can't use ReplyStatus to marshal a possibly unknown reply status value.
ostr.writeByte((byte) replyStatus);
if (replyStatus >= ReplyStatus.ObjectNotExist.value()
&& replyStatus <= ReplyStatus.OperationNotExist.value()) {
var objectId = new Identity();
String objectFacet = "";
String operationName = "";
if (exc instanceof RequestFailedException rfe) {
objectId = rfe.id;
objectFacet = rfe.facet;
operationName = rfe.operation;
}
if (objectId.name.isEmpty()) {
objectId = this.id;
objectFacet = this.facet;
}
if (operationName.isEmpty()) {
operationName = this.operation;
}
Identity.ice_write(ostr, objectId);
if (objectFacet.isEmpty()) {
ostr.writeStringSeq(new String[]{});
} else {
ostr.writeStringSeq(new String[]{objectFacet});
}
ostr.writeString(operationName);
// and we don't use the dispatchExceptionMessage.
} else {
// If the exception is a DispatchException, we keep its message as-is; otherwise, we
// create a custom message. This message doesn't include the stack trace.
if (dispatchExceptionMessage == null) {
dispatchExceptionMessage = "Dispatch failed with " + exc.toString();
}
ostr.writeString(dispatchExceptionMessage);
}
}
var stringWriter = new StringWriter();
var printWriter = new PrintWriter(stringWriter);
exc.printStackTrace(printWriter);
printWriter.flush();
return new OutgoingResponse(
replyStatus,
exceptionId,
exceptionId != null ? stringWriter.toString() : null,
ostr);
}
/**
* Starts the output stream for a successful reply, with everything up to and including the
* reply status. When the request ID is 0 (one-way request), the returned output stream is
* empty.
*
* @return The new output stream with status ReplyStatus.Ok.
*/
public OutputStream startReplyStream() {
return startReplyStream(ReplyStatus.Ok);
}
/**
* Starts the output stream for a reply, with everything up to and including the reply status.
* When the request ID is 0 (one-way request), the returned output stream is empty.
*
* @param replyStatus The reply status.
* @return The new output stream.
*/
private OutputStream startReplyStream(ReplyStatus replyStatus) {
if (requestId == 0) {
return new OutputStream();
} else {
assert (adapter != null);
var ostr =
new OutputStream(
Protocol.currentProtocolEncoding,
adapter.getCommunicator()
.getInstance()
.defaultsAndOverrides()
.defaultFormat,
false);
ostr.writeBlob(Protocol.replyHdr);
ostr.writeInt(requestId);
ostr.writeByte((byte) replyStatus.value());
return ostr;
}
}
}