Skip to content

Commit

Permalink
Expose a contract's agreement text on the Ledger API
Browse files Browse the repository at this point in the history
Fixes #1110
  • Loading branch information
gerolf-da committed May 15, 2019
1 parent 91f2414 commit d6236a9
Show file tree
Hide file tree
Showing 49 changed files with 294 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ final case class CreateEvent[Cid, Val](
contractId: Cid,
templateId: Identifier,
argument: Val,
agreementText: String,
stakeholders: Set[Party],
witnesses: Set[Party])
extends Event[Nothing, Cid, Val] {
Expand Down Expand Up @@ -153,6 +154,7 @@ object Event {
nc.coid,
templateId,
nc.coinst.arg,
nc.coinst.agreementText,
stakeholders intersect disclosure(nodeId),
disclosure(nodeId))
evts += (nodeId -> evt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,7 @@ class EngineTest extends WordSpec with Matchers {
(Some[Name]("giver"), ValueParty("Alice")),
(Some[Name]("receiver"), ValueParty("Clara")))
)),
"",
Set("Clara", "Alice"),
Set("Bob", "Clara", "Alice"),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class SemanticTester(
nextScenarioCoidToLedgerCoid(scenarioCreateNode.coid),
scenarioCreateNode.coinst.template,
scenarioCreateNode.coinst.arg.mapContractId(nextScenarioCoidToLedgerCoid),
scenarioCreateNode.coinst.agreementText,
scenarioCreateNode.stakeholders intersect scenarioWitnesses(scenarioNodeId),
scenarioWitnesses(scenarioNodeId),
)
Expand Down
6 changes: 3 additions & 3 deletions docs/source/app-dev/bindings-java/codegen.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ To avoid possible name clashes in the generated Java sources, you should specify
Generate the decoder utility class
----------------------------------

When reading transactions from the ledger, you typically want to convert a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ from the Ledger API to the corresponding generated ``Contract`` class. The Java codegen can optionally generate a decoder class based on the input DAR files that calls the ``fromIdAndRecord`` method of the respective generated ``Contract`` class (see :ref:`daml-codegen-java-templates`). The decoder class can do this for all templates in the input DAR files.
When reading transactions from the ledger, you typically want to convert a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ from the Ledger API to the corresponding generated ``Contract`` class. The Java codegen can optionally generate a decoder class based on the input DAR files that calls the ``fromCreatedEvent`` method of the respective generated ``Contract`` class (see :ref:`daml-codegen-java-templates`). The decoder class can do this for all templates in the input DAR files.

To generate such a decoder class, provide the command line parameter ``-d`` or ``--decoderClass`` with a fully qualified class name:

Expand Down Expand Up @@ -254,7 +254,7 @@ The Java codegen generates three classes for a DAML template:
.. TODO: refer to another section explaining exactly that, when we have it.
**TemplateName.Contract**
Represents an actual contract on the ledger. It contains a field for the contract ID (of type ``TemplateName.ContractId``) and a field for the template data (of type ``TemplateName``). With the static method ``TemplateName.Contract.fromIdAndRecord``, you can deserialize a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ to an instance of ``TemplateName.Contract``.
Represents an actual contract on the ledger. It contains a field for the contract ID (of type ``TemplateName.ContractId``) and a field for the template data (of type ``TemplateName``). With the static method ``TemplateName.Contract.fromCreatedEvent``, you can deserialize a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ to an instance of ``TemplateName.Contract``.


.. literalinclude:: ./code-snippets/Templates.daml
Expand Down Expand Up @@ -300,7 +300,7 @@ A file is generated that defines three Java classes:
public final ContractId id;
public final Bar data;
public static Contract fromIdAndRecord(String contractId, Record record) { /* ... */ }
public static Contract fromCreatedEvent(CreatedEvent event) { /* ... */ }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public static void main(String[] args) {
.blockingForEach(response -> {
response.getOffset().ifPresent(offset -> acsOffset.set(new LedgerOffset.Absolute(offset)));
response.getCreatedEvents().stream()
.map(e -> Iou.Contract.fromIdAndRecord(e.getContractId(), e.getArguments()))
.map(Iou.Contract::fromCreatedEvent)
.forEach(contract -> {
long id = idCounter.getAndIncrement();
contracts.put(id, contract.data);
Expand All @@ -85,7 +85,7 @@ public static void main(String[] args) {
if (event instanceof CreatedEvent) {
CreatedEvent createdEvent = (CreatedEvent) event;
long id = idCounter.getAndIncrement();
Iou.Contract contract = Iou.Contract.fromIdAndRecord(createdEvent.getContractId(), createdEvent.getArguments());
Iou.Contract contract = Iou.Contract.fromCreatedEvent(createdEvent);
contracts.put(id, contract.data);
idMap.put(id, contract.id);
} else if (event instanceof ArchivedEvent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,8 @@ class BotTest extends FlatSpec with Matchers with DataLayerHelpers {
s"eid_$id",
templateId,
s"cid_$id",
new Record(List.empty[Record.Field].asJava))
new Record(List.empty[Record.Field].asJava),
Optional.empty())

def archive(event: CreatedEvent): ArchivedEvent =
new ArchivedEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package com.daml.ledger.rxjava.grpc.helpers

import java.time.Instant
import java.util
import java.util.Collections
import java.util.{Collections, Optional}

import com.daml.ledger.javaapi.data
import com.daml.ledger.testkit.services.TransactionServiceImpl
Expand Down Expand Up @@ -183,13 +183,27 @@ object TransactionGenerator {
val createdEventGen: Gen[(Created, data.CreatedEvent)] = for {
eventId <- nonEmptyId
contractId <- nonEmptyId
agreementText <- Gen.option(Gen.asciiStr)
(scalaTemplateId, javaTemplateId) <- identifierGen
(scalaRecord, javaRecord) <- Gen.sized(recordGen)
parties <- Gen.listOf(nonEmptyId)
} yield
(
Created(CreatedEvent(eventId, contractId, Some(scalaTemplateId), Some(scalaRecord), parties)),
new data.CreatedEvent(parties.asJava, eventId, javaTemplateId, contractId, javaRecord)
Created(
CreatedEvent(
eventId,
contractId,
Some(scalaTemplateId),
Some(scalaRecord),
parties,
agreementText = agreementText)),
new data.CreatedEvent(
parties.asJava,
eventId,
javaTemplateId,
contractId,
javaRecord,
agreementText.map(Optional.of[String]).getOrElse(Optional.empty()))
)

val archivedEventGen: Gen[(Archived, data.ArchivedEvent)] = for {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
package com.daml.ledger.javaapi.data;

import com.digitalasset.ledger.api.v1.EventOuterClass;
import com.google.protobuf.StringValue;
import org.checkerframework.checker.nullness.qual.NonNull;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

public final class CreatedEvent implements Event, TreeEvent {

Expand All @@ -21,12 +23,15 @@ public final class CreatedEvent implements Event, TreeEvent {

private final Record arguments;

public CreatedEvent(@NonNull List<@NonNull String> witnessParties, @NonNull String eventId, @NonNull Identifier templateId, @NonNull String contractId, @NonNull Record arguments) {
private final Optional<String> agreementText;

public CreatedEvent(@NonNull List<@NonNull String> witnessParties, @NonNull String eventId, @NonNull Identifier templateId, @NonNull String contractId, @NonNull Record arguments, Optional<String> agreementText) {
this.witnessParties = witnessParties;
this.eventId = eventId;
this.templateId = templateId;
this.contractId = contractId;
this.arguments = arguments;
this.agreementText = agreementText;
}

@NonNull
Expand Down Expand Up @@ -58,6 +63,11 @@ public Record getArguments() {
return arguments;
}

@NonNull
public Optional<String> getAgreementText() {
return agreementText;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -67,13 +77,14 @@ public boolean equals(Object o) {
Objects.equals(eventId, that.eventId) &&
Objects.equals(templateId, that.templateId) &&
Objects.equals(contractId, that.contractId) &&
Objects.equals(arguments, that.arguments);
Objects.equals(arguments, that.arguments) &&
Objects.equals(agreementText, that.agreementText);
}

@Override
public int hashCode() {

return Objects.hash(witnessParties, eventId, templateId, contractId, arguments);
return Objects.hash(witnessParties, eventId, templateId, contractId, arguments, agreementText);
}

@Override
Expand All @@ -84,17 +95,19 @@ public String toString() {
", templateId=" + templateId +
", contractId='" + contractId + '\'' +
", arguments=" + arguments +
", agreementText='" + agreementText + '\'' +
'}';
}

public EventOuterClass.@NonNull CreatedEvent toProto() {
return EventOuterClass.CreatedEvent.newBuilder()
EventOuterClass.CreatedEvent.Builder builder = EventOuterClass.CreatedEvent.newBuilder()
.setContractId(getContractId())
.setCreateArguments(getArguments().toProtoRecord())
.setEventId(getEventId())
.setTemplateId(getTemplateId().toProto())
.addAllWitnessParties(this.getWitnessParties())
.build();
.addAllWitnessParties(this.getWitnessParties());
agreementText.ifPresent(a -> builder.setAgreementText(StringValue.of(a)));
return builder.build();
}

public static CreatedEvent fromProto(EventOuterClass.CreatedEvent createdEvent) {
Expand All @@ -103,7 +116,8 @@ public static CreatedEvent fromProto(EventOuterClass.CreatedEvent createdEvent)
createdEvent.getEventId(),
Identifier.fromProto(createdEvent.getTemplateId()),
createdEvent.getContractId(),
Record.fromProto(createdEvent.getCreateArguments()));
Record.fromProto(createdEvent.getCreateArguments()),
createdEvent.hasAgreementText() ? Optional.of(createdEvent.getAgreementText().getValue()) : Optional.empty());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@

package com.digitalasset;

import com.daml.ledger.javaapi.data.CreateAndExerciseCommand;
import com.daml.ledger.javaapi.data.CreateCommand;
import com.daml.ledger.javaapi.data.ExerciseCommand;
import com.daml.ledger.javaapi.data.*;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import tests.template1.SimpleTemplate;
import tests.template1.TestTemplate_Int;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.Collections;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

@RunWith(JUnitPlatform.class)
public class TemplateMethodTest {
Expand All @@ -21,6 +22,8 @@ public class TemplateMethodTest {
// at compilation time in case any of the methods are generated differently
// or not at all

private static Record simpleTemplateRecord = new Record(new Record.Field(new Party("Bob")));

@Test
void templateHasCreateMethods() {
CreateCommand fromStatic = SimpleTemplate.create("Bob");
Expand Down Expand Up @@ -50,5 +53,34 @@ void templateHasCreateAndExerciseMethods() {

assertNotNull(fromSplatted, "CreateAndExerciseCommand from splatted choice was null");
assertNotNull(fromRecord, "CreateAndExerciseCommand from record choice was null");
assertEquals(fromRecord, fromSplatted, "CreateAndExerciseCommands from both methods are not the same");
}

@Test
void contractHasDeprecatedFromIdAndRecord() {
SimpleTemplate.Contract contract = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord);
assertFalse(contract.agreementText.isPresent(), "Field agreementText should not be present");
}

@Test
void contractHasFromIdAndRecord() {
SimpleTemplate.Contract emptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.empty());
assertFalse(emptyAgreement.agreementText.isPresent(), "Field agreementText should not be present");

SimpleTemplate.Contract nonEmptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.of("I agree"));
assertTrue(nonEmptyAgreement.agreementText.isPresent(), "Field agreementText should be present");
assertEquals(nonEmptyAgreement.agreementText, Optional.of("I agree"), "Unexpected agreementText");
}

@Test
void contractHasFromCreatedEvent() {
CreatedEvent agreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.of("I agree"));
CreatedEvent noAgreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.empty());

SimpleTemplate.Contract withAgreement = SimpleTemplate.Contract.fromCreatedEvent(agreementEvent);
assertTrue(withAgreement.agreementText.isPresent(), "AgreementText was not present");

SimpleTemplate.Contract withoutAgreement = SimpleTemplate.Contract.fromCreatedEvent(noAgreementEvent);
assertFalse(withoutAgreement.agreementText.isPresent(), "AgreementText was present");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,7 @@ class CodegenLedgerTest extends FlatSpec with Matchers {
.foldLeft(Map[String, Wolpertinger.Contract]())((acc, event) =>
event match {
case e: CreatedEvent =>
acc + (e.getContractId -> Wolpertinger.Contract
.fromIdAndRecord(e.getContractId, e.getArguments))
acc + (e.getContractId -> Wolpertinger.Contract.fromCreatedEvent(e))
case a: ArchivedEvent => acc - a.getContractId
})
.toList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

package com.digitalasset.daml.lf.codegen.backend.java.inner

import java.util.function.BiFunction

import com.daml.ledger.javaapi.data._
import com.squareup.javapoet._
import javax.lang.model.element.Modifier
Expand All @@ -29,9 +27,8 @@ object DecoderClass {
)

private val decoderFunctionType = ParameterizedTypeName.get(
ClassName.get(classOf[BiFunction[_, _, _]]),
ClassName.get(classOf[String]),
ClassName.get(classOf[Record]),
ClassName.get(classOf[java.util.function.Function[_, _]]),
ClassName.get(classOf[CreatedEvent]),
ClassName.get(classOf[Contract])
)

Expand All @@ -52,10 +49,10 @@ object DecoderClass {
.builder()
.addStatement("Identifier templateId = event.getTemplateId()")
.addStatement(
"BiFunction<String, Record, Contract> fromIdAndRecord = getDecoder(templateId).orElseThrow(() -> new IllegalArgumentException(\"No template found for identifier \" + templateId))")
.addStatement("String contractId = event.getContractId()")
.addStatement("Record arguments = event.getArguments()")
.addStatement("return fromIdAndRecord.apply(contractId, arguments)")
"$T decoderFunc = getDecoder(templateId).orElseThrow(() -> new IllegalArgumentException(\"No template found for identifier \" + templateId))",
decoderFunctionType
)
.addStatement("return decoderFunc.apply(event)")
.build())
.build()

Expand All @@ -78,7 +75,7 @@ object DecoderClass {
b.addStatement("$N = new $T()", decodersField, decodersMapType)
templateNames.foreach { template =>
b.addStatement(
"$N.put($T.TEMPLATE_ID, $T.Contract::fromIdAndRecord)",
"$N.put($T.TEMPLATE_ID, $T.Contract::fromCreatedEvent)",
decodersField,
template,
template)
Expand Down
Loading

0 comments on commit d6236a9

Please sign in to comment.