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
@@ -0,0 +1,8 @@
public <T extends Template> CompletableFuture<List<Contract<T>>> active(Class<T> clazz) {
Identifier identifier = Utils.getTemplateIdByClass(clazz);
String sql = "select contract_id, payload from active(?)";
return runAndTraceAsync(ctx, () ->
jdbcTemplate.query(sql, new PqsContractRowMapper<>(identifier),
identifier.qualifiedName())
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public <T extends Template> CompletableFuture<List<Contract<T>>> activeWhere(
Class<T> clazz, String whereClause, Object... params) {
Identifier identifier = Utils.getTemplateIdByClass(clazz);
String sql = "select contract_id, payload from active(?) where " + whereClause;
return runAndTraceAsync(ctx, () ->
jdbcTemplate.query(sql, new PqsContractRowMapper<>(identifier),
combineParams(identifier.qualifiedName(), params))
);
}
18 changes: 18 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m4-backend-dev_L182.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
UpdateServiceGrpc.UpdateServiceStub updateServiceStub = UpdateServiceGrpc.newStub(channel);
updateServiceStub.getUpdates(getUpdatesRequest.toProto(), new StreamObserver<>() {
public void onNext(UpdateServiceOuterClass.GetUpdatesResponse r) {
GetUpdatesResponse response = GetUpdatesResponse.fromProto(r);
response.getTransaction().ifPresent(transaction -> {
for (Event event : transaction.getEvents()) {
if (event instanceof CreatedEvent createdEvent) {
Iou.Contract contract = Iou.Contract.fromCreatedEvent(createdEvent);
// update local cache or trigger side effects
} else if (event instanceof ArchivedEvent archivedEvent) {
// remove from local cache
}
}
});
}
public void onError(Throwable throwable) { /* handle error */ }
public void onCompleted() { /* stream ended */ }
});
12 changes: 12 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m4-backend-dev_L216.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import io.grpc.StatusRuntimeException;
import io.grpc.Status;

try {
ledger.exerciseAndGetResult(contractId, choice, commandId).join();
} catch (CompletionException e) {
if (e.getCause() instanceof StatusRuntimeException sre) {
if (sre.getStatus().getCode() == Status.Code.NOT_FOUND) {
// contract was archived — re-query PQS and retry
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ManagedChannelBuilder<?> builder = ManagedChannelBuilder
.forAddress(ledgerConfig.getHost(), ledgerConfig.getPort())
.usePlaintext();
builder.intercept(new Interceptor(tokenProvider));
ManagedChannel channel = builder.build();

submission = CommandSubmissionServiceGrpc.newFutureStub(channel);
commands = CommandServiceGrpc.newFutureStub(channel);
12 changes: 12 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m4-backend-dev_L35.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@Override
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
MethodDescriptor<ReqT, RespT> method, CallOptions callOptions, Channel next) {
ClientCall<ReqT, RespT> clientCall = next.newCall(method, callOptions);
return new ForwardingClientCall.SimpleForwardingClientCall<>(clientCall) {
@Override
public void start(Listener<RespT> responseListener, Metadata headers) {
headers.put(AUTHORIZATION_HEADER, "Bearer " + tokenProvider.getToken());
super.start(responseListener, headers);
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public CompletableFuture<List<Contract<LicenseComment>>> findCommentsByLicenseNum(int licenseNum) {
return pqs.activeWhere(
LicenseComment.class,
"payload->>'licenseNum' = ?",
String.valueOf(licenseNum)
);
}
20 changes: 20 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m4-backend-dev_L424.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@Override
@WithSpan
public CompletableFuture<ResponseEntity<List<LicenseComment>>> listLicenseComments(
String contractId) {
var ctx = tracingCtx(logger, "listLicenseComments", "contractId", contractId);
return auth.asAuthenticatedParty(party -> traceServiceCallAsync(ctx, () ->
damlRepository.findLicenseById(contractId).thenCompose(optLicense -> {
var license = ensurePresent(optLicense,
"License not found for contract %s", contractId);
return damlRepository.findCommentsByLicenseNum(
license.payload.getLicenseNum.intValue())
.thenApply(comments -> {
var result = comments.stream()
.map(LicenseApiImpl::toLicenseCommentApi)
.toList();
return ResponseEntity.ok(result);
});
})
));
}
26 changes: 26 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m4-backend-dev_L449.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@Override
@WithSpan
public CompletableFuture<ResponseEntity<Void>> addLicenseComment(
String contractId, String commandId, AddCommentRequest request) {
var ctx = tracingCtx(logger, "addLicenseComment",
"contractId", contractId, "commandId", commandId);
return auth.asAuthenticatedParty(party -> traceServiceCallAsync(ctx, () ->
damlRepository.findLicenseById(contractId).thenCompose(optLicense -> {
var license = ensurePresent(optLicense,
"License not found for contract %s", contractId);
var now = Instant.now();
var comment = new quickstart_licensing.licensing
.licensecomment.LicenseComment(
license.payload.getProvider,
license.payload.getUser,
license.payload.getLicenseNum,
new Party(party),
request.getBody(),
now
);
return ledger.create(comment, commandId)
.thenApply(v -> ResponseEntity.status(HttpStatus.CREATED)
.<Void>build());
})
));
}
13 changes: 13 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m4-backend-dev_L482.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
private static LicenseComment toLicenseCommentApi(
Contract<quickstart_licensing.licensing.licensecomment.LicenseComment> contract) {
var p = contract.payload;
var api = new LicenseComment();
api.setContractId(contract.contractId.getContractId);
api.setProvider(p.getProvider.getParty);
api.setUser(p.getUser.getParty);
api.setLicenseNum(p.getLicenseNum.intValue());
api.setCommenter(p.getCommenter.getParty);
api.setBody(p.getBody);
api.setCreatedAt(toOffsetDateTime(p.getCreatedAt));
return api;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
public <T extends Template> CompletableFuture<Void> create(T entity, String commandId) {
CommandsOuterClass.Command.Builder command = CommandsOuterClass.Command.newBuilder();
ValueOuterClass.Value payload = dto2Proto.template(entity.templateId()).convert(entity);
command.getCreateBuilder()
.setTemplateId(toIdentifier(entity.templateId()))
.setCreateArguments(payload.getRecord());
return submitCommands(List.of(command.build()), commandId)
.thenApply(submitResponse -> null);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
CommandsOuterClass.Command.Builder cmdBuilder = CommandsOuterClass.Command.newBuilder();
ValueOuterClass.Value payload =
dto2Proto.choiceArgument(choice.templateId(), choice.choiceName()).convert(choice);

cmdBuilder.getExerciseBuilder()
.setTemplateId(toIdentifier(choice.templateId()))
.setContractId(contractId.getContractId)
.setChoice(choice.choiceName())
.setChoiceArgument(payload);
15 changes: 15 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m4-backend-dev_L92.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@Override
public CompletableFuture<ResponseEntity<String>> expireLicense(
String contractId, String commandId, LicenseExpireRequest request) {
return auth.asAuthenticatedParty(party -> {
return damlRepository.findLicenseById(contractId).thenCompose(optContract -> {
var license = ensurePresent(optContract,
"License not found for contract %s", contractId);
License_Expire choice = new License_Expire(
new Party(auth.getAppProviderPartyId()),
toTokenStandardMetadata(request.getMeta().getData()));
return ledger.exerciseAndGetResult(license.contractId, choice, commandId)
.thenApply(result -> ResponseEntity.ok("License expired successfully"));
});
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@WithSpan
public CompletableFuture<ResponseEntity<List<License>>> listLicenses() {
// The @WithSpan annotation creates a trace span automatically
return damlRepository.findActiveLicenses()
.thenApply(licenses -> ResponseEntity.ok(licenses));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Set up a gRPC channel to the participant's Ledger API
Channel channel = ManagedChannelBuilder
.forAddress(ledgerhost, ledgerport)
.usePlaintext()
.build();

// Create a blocking stub for command submission
CommandServiceGrpc.CommandServiceBlockingStub commandService =
CommandServiceGrpc.newBlockingStub(channel);

// Submit a contract creation and wait for the transaction result
var updateSubmission = UpdateSubmission
.create(APP_ID, randomUUID().toString(), update)
.withActAs(party);
var request = new SubmitAndWaitForTransactionRequest(
updateSubmission.toCommandsSubmission());
var response = commandService.submitAndWaitForTransaction(request.toProto());
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
StateServiceGrpc.StateServiceBlockingStub stateService =
StateServiceGrpc.newBlockingStub(channel);
long ledgerEnd = stateService
.getLedgerEnd(GetLedgerEndRequest.newBuilder().build())
.getOffset();

var request = new GetActiveContractsRequest(eventFormat, ledgerEnd);
Iterator<GetActiveContractsResponse> activeContracts =
stateService.getActiveContracts(request.toProto());
19 changes: 19 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m7-error-handling_L46.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
int maxRetries = 3;
for (int attempt = 0; attempt < maxRetries; attempt++) {
try {
var contract = damlRepository.findActiveAsset(assetId).join();
if (contract.isEmpty()) {
throw new NotFoundException("Asset no longer active");
}
ledger.exerciseAndGetResult(
contract.get().contractId, choice, UUID.randomUUID().toString()
).join();
return; // success
} catch (CompletionException e) {
if (isContention(e) && attempt < maxRetries - 1) {
Thread.sleep((long) Math.pow(2, attempt) * 100); // exponential backoff
continue;
}
throw e;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
String commandId = "renew-license-" + licenseNum + "-" + requestNonce;
ledger.exerciseAndGetResult(contractId, renewChoice, commandId).join();
// Safe to retry with the same commandId if the response is lost
6 changes: 6 additions & 0 deletions docs-snippet-tests/java/appdev_modules_m7-security_L58.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ManagedChannel channel = NettyChannelBuilder
.forAddress(host, port)
.sslContext(GrpcSslContexts.forClient()
.trustManager(new File("ca-cert.pem"))
.build())
.build();
34 changes: 34 additions & 0 deletions docs-snippet-tests/java/appdev_reference_error-codes_L176.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import com.google.rpc.ErrorInfo;
import com.google.rpc.RequestInfo;
import com.google.rpc.RetryInfo;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.StatusProto;

try {
// your gRPC call here
} catch (StatusRuntimeException e) {
com.google.rpc.Status status = StatusProto.fromThrowable(e);

// gRPC status code
int code = status.getCode();

// Full error description
String message = status.getMessage();

// Extract structured details
for (com.google.protobuf.Any detail : status.getDetailsList()) {
if (detail.is(ErrorInfo.class)) {
ErrorInfo info = detail.unpack(ErrorInfo.class);
String errorId = info.getReason();
String category = info.getMetadataMap().get("category");
}
if (detail.is(RequestInfo.class)) {
String requestId = detail.unpack(RequestInfo.class).getRequestId();
}
if (detail.is(RetryInfo.class)) {
RetryInfo retry = detail.unpack(RetryInfo.class);
long retryMs = retry.getRetryDelay().getSeconds() * 1000
+ retry.getRetryDelay().getNanos() / 1_000_000;
}
}
}
34 changes: 34 additions & 0 deletions docs-snippet-tests/java/appdev_reference_error-codes_L76.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import com.google.rpc.ErrorInfo;
import com.google.rpc.RequestInfo;
import com.google.rpc.RetryInfo;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.StatusProto;

try {
// your gRPC call here
} catch (StatusRuntimeException e) {
com.google.rpc.Status status = StatusProto.fromThrowable(e);

// gRPC status code
int code = status.getCode();

// Full error description
String message = status.getMessage();

// Extract structured details
for (com.google.protobuf.Any detail : status.getDetailsList()) {
if (detail.is(ErrorInfo.class)) {
ErrorInfo info = detail.unpack(ErrorInfo.class);
String errorId = info.getReason();
String category = info.getMetadataMap().get("category");
}
if (detail.is(RequestInfo.class)) {
String requestId = detail.unpack(RequestInfo.class).getRequestId();
}
if (detail.is(RetryInfo.class)) {
RetryInfo retry = detail.unpack(RetryInfo.class);
long retryMs = retry.getRetryDelay().getSeconds() * 1000
+ retry.getRetryDelay().getNanos() / 1_000_000;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Query only your party's data via your validator
const myContracts = await ledgerApi.getActiveContracts({
party: myParty,
templateId: "Token"
});

// Can't query other parties' balances
// Must be added as observer to see their data
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const LicenseProvider = ({ children }: { children: React.ReactNode }) => {
const [licenses, setLicenses] = useState<License[]>([]);
const toast = useToast();

const fetchLicenses = useCallback(
withErrorHandling(`Fetching Licenses`)(async () => {
const client: Client = await api.getClient();
const response = await client.listLicenses();
setLicenses(response.data);
}), [withErrorHandling, setLicenses, toast]);

// ... other operations (renew, expire, complete renewal)

return (
<LicenseContext.Provider value={{ licenses, fetchLicenses, /* ... */ }}>
{children}
</LicenseContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const LicensesView: React.FC = () => {
const { licenses, fetchLicenses, initiateLicenseRenewal,
initiateLicenseExpiration, completeLicenseRenewal } = useLicenseStore();
const { user } = useUserStore();

useEffect(() => {
fetchLicenses();
const intervalId = setInterval(() => {
fetchLicenses();
}, 5000);
return () => clearInterval(intervalId);
}, [fetchLicenses]);

return (
<div>
<h2>Licenses</h2>
<table className="table table-fixed" id="licenses-table">
<thead>
<tr>
<th>License Contract ID</th>
<th>Expires At</th>
<th>License #</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{licenses.map((license) => (
<tr key={license.contractId}>
<td>{license.contractId}</td>
<td>{formatDateTime(license.expiresAt)}</td>
<td>{license.licenseNum}</td>
<td>{license.isExpired ? 'EXPIRED' : 'ACTIVE'}</td>
<td>
{/* Renew, Archive buttons */}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import OpenAPIClientAxios from 'openapi-client-axios';
import openApi from '../../common/openapi.yaml'

const api: OpenAPIClientAxios = new OpenAPIClientAxios({
definition: openApi as any,
withServer: { url: '/api' },
});

api.init();

export default api;
Loading