Skip to content
Open
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 @@ -2,8 +2,11 @@
// Licensed under the MIT License.
package com.microsoft.durabletask;

import com.microsoft.durabletask.history.HistoryEvent;

import javax.annotation.Nullable;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeoutException;

/**
Expand Down Expand Up @@ -226,6 +229,31 @@ public abstract OrchestrationMetadata waitForInstanceCompletion(
*/
public abstract OrchestrationStatusQueryResult queryInstances(OrchestrationStatusQuery query);

/**
* Lists the IDs of terminal orchestration instances that completed within a time window.
* <p>
* Unlike {@link #queryInstances(OrchestrationStatusQuery)}, which filters by creation time and returns full
* metadata, this method filters by <em>completion</em> time and returns only instance IDs, making it efficient
* for bulk enumeration such as archival/export. Results are paged; pass
* {@link ListInstanceIdsResult#getContinuationToken()} back via
* {@link ListInstanceIdsQuery#setContinuationToken(String)} to fetch subsequent pages.
*
* @param query filter criteria: completion-time window, terminal runtime statuses, page size, and pagination cursor
* @return a page of matching instance IDs and a cursor for the next page
*/
public abstract ListInstanceIdsResult listInstanceIds(ListInstanceIdsQuery query);

/**
* Gets the full history of an orchestration instance as an ordered list of {@link HistoryEvent} objects.
* <p>
* The events are returned in execution order. This is useful for archiving or offline analysis of an instance's
* execution history. Use {@code instanceof} to inspect each concrete event type.
*
* @param instanceId the unique ID of the orchestration instance whose history to fetch
* @return the instance's history events in order; empty if the instance has no history
*/
public abstract List<HistoryEvent> getOrchestrationHistory(String instanceId);

/**
* Initializes the target task hub data store.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import com.google.protobuf.StringValue;
import com.google.protobuf.Timestamp;
import com.microsoft.durabletask.history.HistoryEvent;
import com.microsoft.durabletask.implementation.protobuf.OrchestratorService;
import com.microsoft.durabletask.implementation.protobuf.OrchestratorService.*;
import com.microsoft.durabletask.implementation.protobuf.TaskHubSidecarServiceGrpc;
import com.microsoft.durabletask.implementation.protobuf.TaskHubSidecarServiceGrpc.*;
Expand Down Expand Up @@ -295,6 +297,37 @@ private OrchestrationStatusQueryResult toQueryResult(QueryInstancesResponse quer
return new OrchestrationStatusQueryResult(metadataList, queryInstancesResponse.getContinuationToken().getValue());
}

@Override
public ListInstanceIdsResult listInstanceIds(ListInstanceIdsQuery query) {
Helpers.throwIfArgumentNull(query, "query");
ListInstanceIdsRequest.Builder builder = ListInstanceIdsRequest.newBuilder();
Optional.ofNullable(query.getCompletedTimeFrom()).ifPresent(from -> builder.setCompletedTimeFrom(DataConverter.getTimestampFromInstant(from)));
Optional.ofNullable(query.getCompletedTimeTo()).ifPresent(to -> builder.setCompletedTimeTo(DataConverter.getTimestampFromInstant(to)));
String requestContinuationToken = query.getContinuationToken() != null ? query.getContinuationToken() : "";
builder.setLastInstanceKey(StringValue.of(requestContinuationToken));
builder.setPageSize(query.getPageSize());
query.getRuntimeStatusList().forEach(runtimeStatus -> Optional.ofNullable(runtimeStatus).ifPresent(status -> builder.addRuntimeStatus(OrchestrationRuntimeStatus.toProtobuf(status))));
ListInstanceIdsResponse response = this.sidecarClient.listInstanceIds(builder.build());
String continuationToken = response.hasLastInstanceKey() ? response.getLastInstanceKey().getValue() : null;
return new ListInstanceIdsResult(new ArrayList<>(response.getInstanceIdsList()), continuationToken);
}

@Override
public List<HistoryEvent> getOrchestrationHistory(String instanceId) {
Helpers.throwIfArgumentNull(instanceId, "instanceId");
StreamInstanceHistoryRequest request = StreamInstanceHistoryRequest.newBuilder()
.setInstanceId(instanceId)
.build();
List<HistoryEvent> historyEvents = new ArrayList<>();
Iterator<HistoryChunk> chunks = this.sidecarClient.streamInstanceHistory(request);
while (chunks.hasNext()) {
for (OrchestratorService.HistoryEvent protoEvent : chunks.next().getEventsList()) {
historyEvents.add(HistoryEventConverter.fromProto(protoEvent));
}
}
return historyEvents;
}

@Override
public void createTaskHub(boolean recreateIfExists) {
this.sidecarClient.createTaskHub(CreateTaskHubRequest.newBuilder().setRecreateIfExists(recreateIfExists).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* <p>
* Use {@link DurableEntityClient#getAllEntities(EntityQuery)} to obtain an instance of this class.
*
* <h3>Example: iterate over all entities</h3>
* <h2>Example: iterate over all entities</h2>
* <pre>{@code
* EntityQuery query = new EntityQuery()
* .setInstanceIdStartsWith("counter")
Expand All @@ -28,7 +28,7 @@
* }
* }</pre>
*
* <h3>Example: iterate page by page</h3>
* <h2>Example: iterate page by page</h2>
* <pre>{@code
* for (EntityQueryResult page : client.getEntities().getAllEntities(query).byPage()) {
* System.out.println("Got " + page.getEntities().size() + " entities");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.durabletask;

import com.microsoft.durabletask.history.*;
import com.microsoft.durabletask.implementation.protobuf.OrchestratorService;

import javax.annotation.Nullable;
import java.time.Instant;

/**
* Converts protobuf {@code HistoryEvent} messages into the public {@link HistoryEvent} domain model.
*/
final class HistoryEventConverter {
private HistoryEventConverter() {
}

/**
* Converts a protobuf history event into its domain representation.
*
* @param proto the protobuf history event
* @return the domain {@link HistoryEvent}
* @throws IllegalArgumentException if the event type is not set
* @throws UnsupportedOperationException if the event type is not recognized
*/
static HistoryEvent fromProto(OrchestratorService.HistoryEvent proto) {
int id = proto.getEventId();
Instant ts = DataConverter.getInstantFromTimestamp(proto.getTimestamp());
switch (proto.getEventTypeCase()) {
case EXECUTIONSTARTED: {
OrchestratorService.ExecutionStartedEvent p = proto.getExecutionStarted();
return new ExecutionStartedEvent(id, ts,
p.getName(),
stringOrNull(p.hasVersion(), p.getVersion()),
stringOrNull(p.hasInput(), p.getInput()),
p.hasOrchestrationInstance() ? toInstance(p.getOrchestrationInstance()) : null,
p.hasParentInstance() ? toParentInfo(p.getParentInstance()) : null,
p.hasScheduledStartTimestamp()
? DataConverter.getInstantFromTimestamp(p.getScheduledStartTimestamp()) : null,
p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
stringOrNull(p.hasOrchestrationSpanID(), p.getOrchestrationSpanID()),
p.getTagsMap());
}
case EXECUTIONCOMPLETED: {
OrchestratorService.ExecutionCompletedEvent p = proto.getExecutionCompleted();
return new ExecutionCompletedEvent(id, ts,
OrchestrationRuntimeStatus.fromProtobuf(p.getOrchestrationStatus()),
stringOrNull(p.hasResult(), p.getResult()),
p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
}
case EXECUTIONTERMINATED: {
OrchestratorService.ExecutionTerminatedEvent p = proto.getExecutionTerminated();
return new ExecutionTerminatedEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()), p.getRecurse());
}
case TASKSCHEDULED: {
OrchestratorService.TaskScheduledEvent p = proto.getTaskScheduled();
return new TaskScheduledEvent(id, ts,
p.getName(),
stringOrNull(p.hasVersion(), p.getVersion()),
stringOrNull(p.hasInput(), p.getInput()),
p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
p.getTagsMap());
}
case TASKCOMPLETED: {
OrchestratorService.TaskCompletedEvent p = proto.getTaskCompleted();
return new TaskCompletedEvent(id, ts, p.getTaskScheduledId(), stringOrNull(p.hasResult(), p.getResult()));
}
case TASKFAILED: {
OrchestratorService.TaskFailedEvent p = proto.getTaskFailed();
return new TaskFailedEvent(id, ts, p.getTaskScheduledId(),
p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
}
case SUBORCHESTRATIONINSTANCECREATED: {
OrchestratorService.SubOrchestrationInstanceCreatedEvent p = proto.getSubOrchestrationInstanceCreated();
return new SubOrchestrationInstanceCreatedEvent(id, ts,
p.getInstanceId(),
p.getName(),
stringOrNull(p.hasVersion(), p.getVersion()),
stringOrNull(p.hasInput(), p.getInput()),
p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
p.getTagsMap());
}
case SUBORCHESTRATIONINSTANCECOMPLETED: {
OrchestratorService.SubOrchestrationInstanceCompletedEvent p =
proto.getSubOrchestrationInstanceCompleted();
return new SubOrchestrationInstanceCompletedEvent(id, ts,
p.getTaskScheduledId(), stringOrNull(p.hasResult(), p.getResult()));
}
case SUBORCHESTRATIONINSTANCEFAILED: {
OrchestratorService.SubOrchestrationInstanceFailedEvent p = proto.getSubOrchestrationInstanceFailed();
return new SubOrchestrationInstanceFailedEvent(id, ts, p.getTaskScheduledId(),
p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
}
case TIMERCREATED: {
OrchestratorService.TimerCreatedEvent p = proto.getTimerCreated();
return new TimerCreatedEvent(id, ts, DataConverter.getInstantFromTimestamp(p.getFireAt()));
}
case TIMERFIRED: {
OrchestratorService.TimerFiredEvent p = proto.getTimerFired();
return new TimerFiredEvent(id, ts, DataConverter.getInstantFromTimestamp(p.getFireAt()), p.getTimerId());
}
case ORCHESTRATORSTARTED:
return new OrchestratorStartedEvent(id, ts);
case ORCHESTRATORCOMPLETED:
return new OrchestratorCompletedEvent(id, ts);
case EVENTSENT: {
OrchestratorService.EventSentEvent p = proto.getEventSent();
return new EventSentEvent(id, ts, p.getInstanceId(), p.getName(),
stringOrNull(p.hasInput(), p.getInput()));
}
case EVENTRAISED: {
OrchestratorService.EventRaisedEvent p = proto.getEventRaised();
return new EventRaisedEvent(id, ts, p.getName(), stringOrNull(p.hasInput(), p.getInput()));
}
case GENERICEVENT: {
OrchestratorService.GenericEvent p = proto.getGenericEvent();
return new GenericEvent(id, ts, stringOrNull(p.hasData(), p.getData()));
}
case HISTORYSTATE: {
OrchestratorService.HistoryStateEvent p = proto.getHistoryState();
return new HistoryStateEvent(id, ts,
p.hasOrchestrationState() ? toOrchestrationState(p.getOrchestrationState()) : null);
}
case CONTINUEASNEW: {
OrchestratorService.ContinueAsNewEvent p = proto.getContinueAsNew();
return new ContinueAsNewEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()));
}
case EXECUTIONSUSPENDED: {
OrchestratorService.ExecutionSuspendedEvent p = proto.getExecutionSuspended();
return new ExecutionSuspendedEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()));
}
case EXECUTIONRESUMED: {
OrchestratorService.ExecutionResumedEvent p = proto.getExecutionResumed();
return new ExecutionResumedEvent(id, ts, stringOrNull(p.hasInput(), p.getInput()));
}
case ENTITYOPERATIONSIGNALED: {
OrchestratorService.EntityOperationSignaledEvent p = proto.getEntityOperationSignaled();
return new EntityOperationSignaledEvent(id, ts,
p.getRequestId(),
p.getOperation(),
p.hasScheduledTime() ? DataConverter.getInstantFromTimestamp(p.getScheduledTime()) : null,
stringOrNull(p.hasInput(), p.getInput()),
stringOrNull(p.hasTargetInstanceId(), p.getTargetInstanceId()));
}
case ENTITYOPERATIONCALLED: {
OrchestratorService.EntityOperationCalledEvent p = proto.getEntityOperationCalled();
return new EntityOperationCalledEvent(id, ts,
p.getRequestId(),
p.getOperation(),
p.hasScheduledTime() ? DataConverter.getInstantFromTimestamp(p.getScheduledTime()) : null,
stringOrNull(p.hasInput(), p.getInput()),
stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()),
stringOrNull(p.hasParentExecutionId(), p.getParentExecutionId()),
stringOrNull(p.hasTargetInstanceId(), p.getTargetInstanceId()));
}
case ENTITYOPERATIONCOMPLETED: {
OrchestratorService.EntityOperationCompletedEvent p = proto.getEntityOperationCompleted();
return new EntityOperationCompletedEvent(id, ts, p.getRequestId(),
stringOrNull(p.hasOutput(), p.getOutput()));
}
case ENTITYOPERATIONFAILED: {
OrchestratorService.EntityOperationFailedEvent p = proto.getEntityOperationFailed();
return new EntityOperationFailedEvent(id, ts, p.getRequestId(),
p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null);
}
case ENTITYLOCKREQUESTED: {
OrchestratorService.EntityLockRequestedEvent p = proto.getEntityLockRequested();
return new EntityLockRequestedEvent(id, ts,
p.getCriticalSectionId(),
p.getLockSetList(),
p.getPosition(),
stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()));
}
case ENTITYLOCKGRANTED: {
OrchestratorService.EntityLockGrantedEvent p = proto.getEntityLockGranted();
return new EntityLockGrantedEvent(id, ts, p.getCriticalSectionId());
}
case ENTITYUNLOCKSENT: {
OrchestratorService.EntityUnlockSentEvent p = proto.getEntityUnlockSent();
return new EntityUnlockSentEvent(id, ts,
p.getCriticalSectionId(),
stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()),
stringOrNull(p.hasTargetInstanceId(), p.getTargetInstanceId()));
}
case EXECUTIONREWOUND: {
OrchestratorService.ExecutionRewoundEvent p = proto.getExecutionRewound();
return new ExecutionRewoundEvent(id, ts,
stringOrNull(p.hasReason(), p.getReason()),
stringOrNull(p.hasParentExecutionId(), p.getParentExecutionId()),
stringOrNull(p.hasInstanceId(), p.getInstanceId()),
p.hasParentTraceContext() ? toTrace(p.getParentTraceContext()) : null,
stringOrNull(p.hasName(), p.getName()),
stringOrNull(p.hasVersion(), p.getVersion()),
stringOrNull(p.hasInput(), p.getInput()),
p.hasParentInstance() ? toParentInfo(p.getParentInstance()) : null,
p.getTagsMap());
}
case EVENTTYPE_NOT_SET:
throw new IllegalArgumentException("History event does not have an eventType set.");
default:
throw new UnsupportedOperationException(
"Deserialization of history event type " + proto.getEventTypeCase() + " is not supported.");
}
}

@Nullable
private static String stringOrNull(boolean present, com.google.protobuf.StringValue value) {
return present ? value.getValue() : null;
}

private static OrchestrationInstance toInstance(OrchestratorService.OrchestrationInstance p) {
return new OrchestrationInstance(p.getInstanceId(), stringOrNull(p.hasExecutionId(), p.getExecutionId()));
}

private static ParentInstanceInfo toParentInfo(OrchestratorService.ParentInstanceInfo p) {
return new ParentInstanceInfo(
p.getTaskScheduledId(),
stringOrNull(p.hasName(), p.getName()),
stringOrNull(p.hasVersion(), p.getVersion()),
p.hasOrchestrationInstance() ? toInstance(p.getOrchestrationInstance()) : null);
}

private static TraceContext toTrace(OrchestratorService.TraceContext p) {
return new TraceContext(p.getTraceParent(), stringOrNull(p.hasTraceState(), p.getTraceState()));
}

private static OrchestrationState toOrchestrationState(OrchestratorService.OrchestrationState p) {
return new OrchestrationState(
p.getInstanceId(),
p.getName(),
stringOrNull(p.hasVersion(), p.getVersion()),
OrchestrationRuntimeStatus.fromProtobuf(p.getOrchestrationStatus()),
p.hasScheduledStartTimestamp()
? DataConverter.getInstantFromTimestamp(p.getScheduledStartTimestamp()) : null,
p.hasCreatedTimestamp() ? DataConverter.getInstantFromTimestamp(p.getCreatedTimestamp()) : null,
p.hasLastUpdatedTimestamp() ? DataConverter.getInstantFromTimestamp(p.getLastUpdatedTimestamp()) : null,
p.hasCompletedTimestamp() ? DataConverter.getInstantFromTimestamp(p.getCompletedTimestamp()) : null,
stringOrNull(p.hasInput(), p.getInput()),
stringOrNull(p.hasOutput(), p.getOutput()),
stringOrNull(p.hasCustomStatus(), p.getCustomStatus()),
p.hasFailureDetails() ? new FailureDetails(p.getFailureDetails()) : null,
stringOrNull(p.hasExecutionId(), p.getExecutionId()),
stringOrNull(p.hasParentInstanceId(), p.getParentInstanceId()),
p.getTagsMap());
}
}
Loading
Loading