Skip to content

Commit

Permalink
epic: adds @naturalId support for entities and service id parameter.
Browse files Browse the repository at this point in the history
  • Loading branch information
ivangsa committed Dec 11, 2024
1 parent 5f237e3 commit 141becc
Show file tree
Hide file tree
Showing 20 changed files with 251 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
entity Customer {
@naturalId
customerId Long required
@naturalId
anotherId String required

name String required maxlength(254) /** Customer name */
email String required maxlength(254)
/** Customer Addresses can be stored in a JSON column in the database. */
Expand Down Expand Up @@ -45,16 +48,16 @@ input CustomerSearchCriteria {
service CustomerService for (Customer) {
@post
createCustomer(Customer) Customer withEvents CustomerEvent
@naturalId
@get("/{customerId}")

@get("/{customerId}/{anotherId}") @naturalId(Customer)
getCustomer(id) Customer?
@naturalId
@put("/{customerId}")
@put("/{customerId}/{anotherId}") @naturalId(Customer)
updateCustomer(id, Customer) Customer? withEvents CustomerEvent
@patch("/{id}")
@patch("/{customerId}/{anotherId}") @naturalId(Customer)
patchCustomer(id, Customer) Customer? withEvents CustomerEvent
@delete("/{id}")
@delete("/{customerId}/{anotherId}") @naturalId(Customer)
deleteCustomer(id) withEvents CustomerEvent

@get("/search") @paginated
searchCustomers(CustomerSearchCriteria) Customer[]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,52 @@ public String findEntityAggregate(String entityName, Options options) {
return aggregateNames.isEmpty()? null : (String) aggregateNames.get(0);
}

public List<Map> naturalIdFields(Map entity, Options options) {
return ZDLFindUtils.naturalIdFields(entity);
}

public String naturalIdsRepoMethodSignature(Map entity, Options options) {
return ZDLJavaSignatureUtils.naturalIdsRepoMethodSignature(entity);
}

public String naturalIdsRepoMethodCallSignature(Map entity, Options options) {
return ZDLJavaSignatureUtils.naturalIdsRepoMethodCallSignature(entity);
}

public String findById(Map method, Options options) {
var zdl = options.get("zdl");
var naturalIdEntity = JSONPath.get(method, "$.options.naturalId");
if(naturalIdEntity != null) {
var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity);
return ZDLJavaSignatureUtils.naturalIdsRepoMethodCallSignature(entity);
}
return "findById(id)";
}

public String idFieldInitialization(Map method, Options options) {
var zdl = options.get("zdl");
var naturalIdEntity = JSONPath.get(method, "$.options.naturalId");
if(naturalIdEntity != null) {
var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity);
List<Map> fields = ZDLFindUtils.naturalIdFields(entity);
return fields.stream().map(field -> String.format("var %s = %s;", field.get("name"), ZDLJavaSignatureUtils.populateField(field)))
.collect(Collectors.joining("\n"));
}
return generator.getIdJavaType() + " id = null;";
}

public String idParamsCallSignature(Map method, Options options) {
var zdl = options.get("zdl");
var naturalIdEntity = JSONPath.get(method, "$.options.naturalId");
if(naturalIdEntity != null) {
var entity = (Map) JSONPath.get(zdl, "$.allEntitiesAndEnums." + naturalIdEntity);
var fields = ZDLFindUtils.naturalIdFields(entity);
return ZDLJavaSignatureUtils.fieldsParamsCallSignature(fields);
}
return "id";
}


public Collection<String> findServiceInputs(Map service, Options options) {
var zdl = options.get("zdl");
var inputDTOSuffix = (String) options.get("inputDTOSuffix");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{{~assign 'entity' aggregateCommandsForMethod.entity }}
{{~assign 'hasNaturalId' (isTruthy method.options.naturalId) }}
{{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }}
// hasNaturalId: {{hasNaturalId}}, notHasNaturalId: {{notHasNaturalId}}
{{~#if (isCrudMethod 'create' method=method entity=entity )}}
log.debug("[CRUD] Request to save {{entity.className}}: {}", input);
var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}});
Expand All @@ -21,13 +24,24 @@
// TODO implement this search by criteria
var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable);
return {{wrapWithMapper entity}};
{{~else if (isCrudMethod 'get' method=method entity=entity )}}
{{~else if (and (isCrudMethod 'get' method=method entity=entity ) notHasNaturalId)}}
log.debug("[CRUD] Request to get {{entity.className}} : {}", id);
var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id);
return {{wrapWithMapper entity}};
{{~else if (isCrudMethod 'delete' method=method entity=entity )}}
{{~else if (and (isCrudMethod 'get' method=method entity=entity ) hasNaturalId)}}
{{{logMethodCall method}}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}};
return {{wrapWithMapper entity}};
{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) notHasNaturalId)}}
log.debug("[CRUD] Request to delete {{entity.className}} : {}", id);
{{entity.instanceName}}Repository.deleteById(id);
{{~> (partial '../withEvents')}}
{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) hasNaturalId)}}
{{{logMethodCall method}}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}};
if({{entity.instanceName}}.isPresent()) {
{{entity.instanceName}}Repository.delete({{entity.instanceName}}.get());
{{~> (partial '../withEvents')}}
}
{{~/if}}

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{{~/if}}
{{!-- Optional<Entity> patch(id, Map) --}}
{{~else if (and entity method.options.patch method.paramId method.parameter method.returnType method.returnTypeIsOptional)}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> {
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> {
return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}});
})
.map({{entity.instanceName}}Repository::save)
Expand All @@ -26,7 +26,7 @@
return {{entity.instanceName}};
{{!-- Optional<Entity> update(id, Entity) --}}
{{~else if (and entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> {
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> {
return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}});
})
.map({{entity.instanceName}}Repository::save)
Expand All @@ -38,7 +38,7 @@
return {{entity.instanceName}};
{{!-- Entity update(id, Entity) --}}
{{~else if (and entity method.paramId method.parameter method.returnType)}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> {
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> {
return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}});
})
.map({{entity.instanceName}}Repository::save)
Expand All @@ -48,10 +48,10 @@
{{!-- Optional<Entity> get(id) --}}
{{~else if (and entity method.paramId method.returnType method.returnTypeIsOptional)}}
{{~assign 'needMapping' (not (eq entity.name method.returnType))}}
return {{entity.instanceName}}Repository.findById(id){{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}};
return {{entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}};
{{!-- Entity get(id) --}}
{{~else if (and entity method.paramId method.returnType)}}
return {{entity.instanceName}}Repository.findById(id);
return {{entity.instanceName}}Repository.{{{findById method}}};
{{!-- Optional<Entity> get(MyEntity) --}}
{{~else if (and entity method.parameter method.returnType method.returnTypeIsOptional)}}
var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{{assign 'entity' aggregateCommandsForMethod.entity }}
{{~assign 'hasNaturalId' (isTruthy method.options.naturalId) }}
{{~assign 'notHasNaturalId' (isFalsy hasNaturalId) }}
{{~#if (isCrudMethod 'create' method=method entity=entity )}}
log.debug("[CRUD] Request to save {{entity.className}}: {}", input);
var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}});
Expand All @@ -20,13 +22,24 @@
// TODO implement this search by criteria
var {{entity.instanceNamePlural}} = {{entity.instanceName}}Repository.findAll(pageable);
return {{wrapWithMapper entity}};
{{~else if (isCrudMethod 'get' method=method entity=entity )}}
{{~else if (and (isCrudMethod 'get' method=method entity=entity ) notHasNaturalId)}}
log.debug("[CRUD] Request to get {{entity.className}} : {}", id);
var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id);
return {{wrapWithMapper entity}};
{{~else if (isCrudMethod 'delete' method=method entity=entity )}}
{{~else if (and (isCrudMethod 'get' method=method entity=entity ) hasNaturalId)}}
{{{logMethodCall method}}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}};
return {{wrapWithMapper entity}};
{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) notHasNaturalId)}}
log.debug("[CRUD] Request to delete {{entity.className}} : {}", id);
{{entity.instanceName}}Repository.deleteById(id);
{{~> (partial '../withEvents')}}
{{~else if (and (isCrudMethod 'delete' method=method entity=entity ) hasNaturalId)}}
{{{logMethodCall method}}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{naturalIdsRepoMethodCallSignature entity}}};
if({{entity.instanceName}}.isPresent()) {
{{entity.instanceName}}Repository.delete({{entity.instanceName}}.get());
{{~> (partial '../withEvents')}}
}
{{~/if}}

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{{~/if}}
{{!-- Optional<Entity> update(id, Entity) --}}
{{~else if (and entity method.paramId method.parameter method.returnType method.returnTypeIsOptional)}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> {
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> {
return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}});
})
.map({{entity.instanceName}}Repository::save)
Expand All @@ -26,18 +26,18 @@
return {{entity.instanceName}};
{{!-- Entity update(id, Entity) --}}
{{~else if (and entity method.paramId method.parameter method.returnType)}}
var {{entity.instanceName}} = {{entity.instanceName}}Repository.findById(id).map(existing{{entity.className}} -> {
var {{entity.instanceName}} = {{entity.instanceName}}Repository.{{{findById method}}}.map(existing{{entity.className}} -> {
return {{asInstanceName service.name}}Mapper.update(existing{{entity.className}}, {{{mapperInputCallSignature method.parameter}}});
}).map({{entity.instanceName}}Repository::save).orElseThrow();
{{~> (partial '../withEvents')}}
return {{wrapWithMapper entity}};
{{!-- Optional<Entity> get(id) --}}
{{~else if (and entity method.paramId method.returnType method.returnTypeIsOptional)}}
{{~assign 'needMapping' (not (eq entity.name method.returnType))}}
return {{entity.instanceName}}Repository.findById(id){{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}};
return {{entity.instanceName}}Repository.{{{findById method}}}{{#if needMapping}}.map({{asInstanceName service.name}}Mapper::as{{method.returnType}}){{/if}};
{{!-- Entity get(id) --}}
{{~else if (and entity method.paramId method.returnType)}}
return {{entity.instanceName}}Repository.findById(id);
return {{entity.instanceName}}Repository.{{{findById method}}};
{{!-- Optional<Entity> get(MyEntity) --}}
{{~else if (and entity method.parameter method.returnType method.returnTypeIsOptional)}}
var {{entity.instanceName}} = {{asInstanceName service.name}}Mapper.update(new {{entity.className}}(), {{{mapperInputCallSignature method.parameter}}});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,13 @@ import org.springframework.stereotype.Repository;
*/
@SuppressWarnings("unused")
@Repository
public interface {{entity.className}}Repository extends JpaRepository<{{entity.className}}, {{idJavaType}}> {}
public interface {{entity.className}}Repository extends JpaRepository<{{entity.className}}, {{idJavaType}}> {

{{~#if aggregate}}
default Optional<{{aggregate}}> find{{aggregate}}ById({{idJavaType}} id) {
return findById(id).map({{aggregate}}::new);
}
{{~/if}}

{{~#if (naturalIdFields entity)}}{{{naturalIdsRepoMethodSignature entity}}};{{/if}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ public interface {{entity.className}}Repository extends MongoRepository<{{entity
return findById(id).map({{aggregate}}::new);
}
{{~/if}}

{{~#if (naturalIdFields entity)}}{{{naturalIdsRepoMethodSignature entity}}};{{/if}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@
assertNotNull({{entity.instanceName}}.getId());
assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}}));
{{~else if (isCrudMethod 'update' method=method entity=entity )}}
var id = 1L; // TODO fill id
{{{idFieldInitialization method}}}
var input = new {{methodParameterType method}}();
// TODO fill input data
{{~#each entity.fields as |field|}}
// input.set{{capitalize field.name}}({{{populateField field}}});
{{~/each}}
assertTrue({{entity.instanceName}}Repository.containsKey(id));
var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}(id, input);
// assertTrue({{entity.instanceName}}Repository.containsKey(id));
var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}({{idParamsCallSignature method}}, input);
assertTrue({{entity.instanceName}}.isPresent());
assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}}.get()));
{{~else if (isCrudMethod 'list' method=method entity=entity) }}
// var results = {{serviceInstance}}.list{{entity.classNamePlural}}(PageRequest.of(0, 10));
// assertNotNull(results);
{{~else if (isCrudMethod 'get' method=method entity=entity )}}
var id = 1L; // TODO fill id
var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}(id);
{{{idFieldInitialization method}}}
var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}({{idParamsCallSignature method}});
assertTrue({{entity.instanceName}}.isPresent());
{{~else if (isCrudMethod 'delete' method=method entity=entity )}}
var id = 1L; // TODO fill id
assertTrue({{entity.instanceName}}Repository.containsKey(id));
{{serviceInstance}}.delete{{entity.className}}(id);
assertFalse({{entity.instanceName}}Repository.containsKey(id));
{{{idFieldInitialization method}}}
// assertTrue({{entity.instanceName}}Repository.containsKey(id));
{{serviceInstance}}.delete{{entity.className}}({{idParamsCallSignature method}});
// assertFalse({{entity.instanceName}}Repository.containsKey(id));
{{~else~}}
// TODO: implement this test
{{~/if}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@
assertNotNull({{entity.instanceName}}.getId());
assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}}));
{{~else if (isCrudMethod 'update' method=method entity=entity )}}
var id = "1"; // TODO fill id
{{{idFieldInitialization method}}}
var input = new {{methodParameterType method}}();
// TODO fill input data
{{~#each entity.fields as |field|}}
// input.set{{capitalize field.name}}({{{populateField field}}});
{{~/each}}
assertTrue({{entity.instanceName}}Repository.containsKey(id));
var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}(id, input);
// assertTrue({{entity.instanceName}}Repository.containsKey(id));
var {{entity.instanceName}} = {{serviceInstance}}.update{{entity.className}}({{idParamsCallSignature method}}, input);
assertTrue({{entity.instanceName}}.isPresent());
assertTrue({{entity.instanceName}}Repository.containsEntity({{entity.instanceName}}.get()));
{{~else if (isCrudMethod 'list' method=method entity=entity) }}
// var results = {{serviceInstance}}.list{{entity.classNamePlural}}(PageRequest.of(0, 10));
// assertNotNull(results);
{{~else if (isCrudMethod 'get' method=method entity=entity )}}
var id = "1"; // TODO fill id
var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}(id);
{{{idFieldInitialization method}}}
var {{entity.instanceName}} = {{serviceInstance}}.get{{entity.className}}({{idParamsCallSignature method}});
assertTrue({{entity.instanceName}}.isPresent());
{{~else if (isCrudMethod 'delete' method=method entity=entity )}}
var id = "1"; // TODO fill id
assertTrue({{entity.instanceName}}Repository.containsKey(id));
{{serviceInstance}}.delete{{entity.className}}(id);
assertFalse({{entity.instanceName}}Repository.containsKey(id));
{{{idFieldInitialization method}}}
// assertTrue({{entity.instanceName}}Repository.containsKey(id));
{{serviceInstance}}.delete{{entity.className}}({{idParamsCallSignature method}});
// assertFalse({{entity.instanceName}}Repository.containsKey(id));
{{~else~}}
// TODO: implement this test
{{~/if}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,13 @@ import {{entitiesPackage}}.*;
import {{outboundRepositoryPackage}}.{{entity.className}}Repository;

public class {{entity.className}}RepositoryInMemory extends InMemoryJpaRepository<{{entity.className}}> implements {{entity.className}}Repository {

{{~#if (naturalIdFields entity)}}
@Override
public {{{naturalIdsRepoMethodSignature entity}}} {
return getEntities().values().stream().filter(e ->
{{#joinWithTemplate (naturalIdFields entity) delimiter='&&'}} isSameValue({{name}}, readField(e, "{{name}}")) {{/joinWithTemplate}}
).findFirst();
}
{{~/if}}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,13 @@ import {{entitiesPackage}}.*;
import {{outboundRepositoryPackage}}.{{entity.className}}Repository;

public class {{entity.className}}RepositoryInMemory extends InMemoryMongodbRepository<{{entity.className}}> implements {{entity.className}}Repository {

{{~#if (naturalIdFields entity)}}
@Override
public {{{naturalIdsRepoMethodSignature entity}}} {
return getEntities().values().stream().filter(e ->
{{#joinWithTemplate (naturalIdFields entity) delimiter='&&'}} isSameValue({{name}}, readField(e, "{{name}}")) {{/joinWithTemplate}}
).findFirst();
}
{{~/if}}
}
Loading

0 comments on commit 141becc

Please sign in to comment.