From 8997aac29e30731ff0612313ef1cc12408ec243d Mon Sep 17 00:00:00 2001 From: Marc Becker Date: Thu, 11 Sep 2025 10:16:18 +0200 Subject: [PATCH] Make use of improved event handler APIs - Typed queries - Typed entity references - More possibilities for return types --- srv/pom.xml | 1 + .../java/my/bookshop/RatingCalculator.java | 19 ++- .../handlers/AdminServiceAddressHandler.java | 44 ++++--- .../handlers/AdminServiceAuditHandler.java | 25 ++-- .../handlers/AdminServiceHandler.java | 111 ++++++++---------- .../handlers/CatalogServiceHandler.java | 72 +++++------- .../HierarchySiblingActionHandler.java | 27 ++--- .../handlers/NotesServiceHandler.java | 78 ++++++------ srv/src/main/resources/application.yaml | 2 + 9 files changed, 166 insertions(+), 213 deletions(-) diff --git a/srv/pom.xml b/srv/pom.xml index f0ab50e7..df053511 100644 --- a/srv/pom.xml +++ b/srv/pom.xml @@ -235,6 +235,7 @@ cds.gen true true + true diff --git a/srv/src/main/java/my/bookshop/RatingCalculator.java b/srv/src/main/java/my/bookshop/RatingCalculator.java index 0539c905..a983da84 100644 --- a/srv/src/main/java/my/bookshop/RatingCalculator.java +++ b/srv/src/main/java/my/bookshop/RatingCalculator.java @@ -2,21 +2,18 @@ import static cds.gen.my.bookshop.Bookshop_.BOOKS; +import cds.gen.my.bookshop.Books; +import cds.gen.my.bookshop.Reviews; +import com.sap.cds.Result; +import com.sap.cds.ql.Select; +import com.sap.cds.ql.Update; +import com.sap.cds.services.persistence.PersistenceService; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.OptionalDouble; import java.util.stream.Stream; - import org.springframework.stereotype.Component; -import com.sap.cds.Result; -import com.sap.cds.ql.Select; -import com.sap.cds.ql.Update; -import com.sap.cds.services.persistence.PersistenceService; - -import cds.gen.my.bookshop.Books; -import cds.gen.my.bookshop.Reviews; - /** * Takes care of calculating the average rating of a book based on its review * ratings. @@ -34,8 +31,8 @@ public class RatingCalculator { * Initializes the ratings for all existing books based on their reviews. */ public void initBookRatings() { - Result result = db.run(Select.from(BOOKS).columns(b -> b.ID())); - for (Books book : result.listOf(Books.class)) { + var result = db.run(Select.from(BOOKS).columns(b -> b.ID())); + for (Books book : result) { setBookRating(book.getId()); } } diff --git a/srv/src/main/java/my/bookshop/handlers/AdminServiceAddressHandler.java b/srv/src/main/java/my/bookshop/handlers/AdminServiceAddressHandler.java index a669f658..240e1025 100644 --- a/srv/src/main/java/my/bookshop/handlers/AdminServiceAddressHandler.java +++ b/srv/src/main/java/my/bookshop/handlers/AdminServiceAddressHandler.java @@ -2,16 +2,13 @@ import static cds.gen.adminservice.AdminService_.ADDRESSES; -import java.time.Duration; -import java.util.Optional; -import java.util.stream.Stream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.sap.cds.Result; +import cds.gen.adminservice.Addresses; +import cds.gen.adminservice.Addresses_; +import cds.gen.adminservice.AdminService_; +import cds.gen.adminservice.Orders; +import cds.gen.api_business_partner.ApiBusinessPartner; +import cds.gen.api_business_partner.ApiBusinessPartner_; +import cds.gen.api_business_partner.BusinessPartnerChangedContext; import com.sap.cds.ql.CQL; import com.sap.cds.ql.Insert; import com.sap.cds.ql.Predicate; @@ -33,15 +30,14 @@ import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration; import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceConfiguration.TimeLimiterConfiguration; import com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorator; - -import cds.gen.adminservice.Addresses; -import cds.gen.adminservice.Addresses_; -import cds.gen.adminservice.AdminService_; -import cds.gen.adminservice.Orders; -import cds.gen.api_business_partner.ApiBusinessPartner; -import cds.gen.api_business_partner.ApiBusinessPartner_; -import cds.gen.api_business_partner.BusinessPartnerChangedContext; +import java.time.Duration; +import java.util.Optional; +import java.util.stream.Stream; import my.bookshop.MessageKeys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; /** * Custom handler for the Admin Service Addresses, which come from a remote S/4 System @@ -107,13 +103,13 @@ public void patchAddressId(EventContext context, Stream orders) { orders.filter(o -> o.getShippingAddressId() != null).forEach(order -> { String addressId = order.getShippingAddressId(); - Result replica = db.run(Select.from(ADDRESSES).where(a -> a.businessPartner().eq(businessPartner).and(a.ID().eq(addressId)))); + var replica = db.run(Select.from(ADDRESSES).where(a -> a.businessPartner().eq(businessPartner).and(a.ID().eq(addressId)))); // check if the address was not yet replicated if(replica.rowCount() < 1) { logger.info("Replicating Address '{}' from S/4 service", addressId); Addresses remoteAddress = bupa.run(Select.from(ADDRESSES) .where(a -> a.businessPartner().eq(businessPartner).and(a.ID().eq(addressId)))) - .single(Addresses.class); + .single(); remoteAddress.setTombstone(false); db.run(Insert.into(ADDRESSES).entry(remoteAddress)); @@ -128,15 +124,15 @@ public void updateBusinessPartnerAddresses(BusinessPartnerChangedContext context String businessPartner = context.getData().getBusinessPartner(); // fetch affected entries from local replicas - Result replicas = db.run(Select.from(ADDRESSES).where(a -> a.businessPartner().eq(businessPartner))); + var replicas = db.run(Select.from(ADDRESSES).where(a -> a.businessPartner().eq(businessPartner))); if(replicas.rowCount() > 0) { logger.info("Updating Addresses for BusinessPartner '{}'", businessPartner); // fetch changed data from S/4 -> might be less than local due to deletes - Result remoteAddresses = bupa.run(Select.from(ADDRESSES).where(a -> a.businessPartner().eq(businessPartner))); + var remoteAddresses = bupa.run(Select.from(ADDRESSES).where(a -> a.businessPartner().eq(businessPartner))); // update replicas or add tombstone if external address was deleted - replicas.streamOf(Addresses.class).forEach(rep -> { + replicas.stream().forEach(rep -> { Optional matching = remoteAddresses - .streamOf(Addresses.class) + .stream() .filter(ext -> ext.getId().equals(rep.getId())) .findFirst(); diff --git a/srv/src/main/java/my/bookshop/handlers/AdminServiceAuditHandler.java b/srv/src/main/java/my/bookshop/handlers/AdminServiceAuditHandler.java index b8837772..c9ed7176 100644 --- a/srv/src/main/java/my/bookshop/handlers/AdminServiceAuditHandler.java +++ b/srv/src/main/java/my/bookshop/handlers/AdminServiceAuditHandler.java @@ -2,13 +2,9 @@ import static cds.gen.adminservice.AdminService_.ORDERS; -import java.util.Arrays; -import java.util.Optional; -import java.util.stream.Stream; - -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; - +import cds.gen.adminservice.AdminService_; +import cds.gen.adminservice.Orders; +import cds.gen.adminservice.Orders_; import com.sap.cds.ql.Select; import com.sap.cds.ql.cqn.CqnDelete; import com.sap.cds.services.EventContext; @@ -24,10 +20,11 @@ import com.sap.cds.services.handler.annotations.Before; import com.sap.cds.services.handler.annotations.ServiceName; import com.sap.cds.services.persistence.PersistenceService; - -import cds.gen.adminservice.AdminService_; -import cds.gen.adminservice.Orders; -import cds.gen.adminservice.Orders_; +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; /** * A custom handler that creates AuditLog messages. @@ -74,7 +71,7 @@ public void beforeUpdateOrUpsertOrder(EventContext context, Stream order }); } - @Before(event = { CqnService.EVENT_DELETE }, entity = { Orders_.CDS_NAME }) + @Before(entity = { Orders_.CDS_NAME }) public void beforeDelete(CdsDeleteEventContext context) { // prepare a select statement to read old currency code Select ordersSelect = toSelect(context.getCqn()); @@ -95,11 +92,11 @@ private void auditCfgChange(final Action action, final ConfigChange cfgChange, E private Optional readOldOrders(String ordersId) { // prepare a select statement to read old order number - Select ordersSelect = Select.from(ORDERS).columns(Orders_::OrderNo) + var ordersSelect = Select.from(ORDERS).columns(Orders_::OrderNo) .where(o -> o.ID().eq(ordersId).and(o.IsActiveEntity().eq(true))); // read old orders from DB - return this.db.run(ordersSelect).first(Orders.class); + return this.db.run(ordersSelect).first(); } private static ConfigChange createConfigChange(Orders orders, Orders oldOrders) { diff --git a/srv/src/main/java/my/bookshop/handlers/AdminServiceHandler.java b/srv/src/main/java/my/bookshop/handlers/AdminServiceHandler.java index c517f493..a7245171 100644 --- a/srv/src/main/java/my/bookshop/handlers/AdminServiceHandler.java +++ b/srv/src/main/java/my/bookshop/handlers/AdminServiceHandler.java @@ -1,22 +1,20 @@ package my.bookshop.handlers; import static cds.gen.adminservice.AdminService_.ORDERS; -import static cds.gen.adminservice.AdminService_.ORDER_ITEMS; import static cds.gen.my.bookshop.Bookshop_.BOOKS; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; -import java.util.function.Supplier; -import java.util.stream.Stream; -import org.springframework.stereotype.Component; -import com.sap.cds.Result; +import cds.gen.adminservice.AdminService; +import cds.gen.adminservice.AdminService_; +import cds.gen.adminservice.Books; +import cds.gen.adminservice.BooksAddToOrderContext; +import cds.gen.adminservice.BooksCovers; +import cds.gen.adminservice.Books_; +import cds.gen.adminservice.OrderItems; +import cds.gen.adminservice.OrderItems_; +import cds.gen.adminservice.Orders; +import cds.gen.adminservice.Upload; +import cds.gen.adminservice.Upload_; +import cds.gen.my.bookshop.Bookshop_; import com.sap.cds.ql.Select; import com.sap.cds.ql.Update; import com.sap.cds.ql.Upsert; @@ -37,20 +35,18 @@ import com.sap.cds.services.handler.annotations.ServiceName; import com.sap.cds.services.messages.Messages; import com.sap.cds.services.persistence.PersistenceService; - -import cds.gen.adminservice.AdminService; -import cds.gen.adminservice.AdminService_; -import cds.gen.adminservice.Books; -import cds.gen.adminservice.BooksAddToOrderContext; -import cds.gen.adminservice.BooksCovers; -import cds.gen.adminservice.Books_; -import cds.gen.adminservice.OrderItems; -import cds.gen.adminservice.OrderItems_; -import cds.gen.adminservice.Orders; -import cds.gen.adminservice.Upload; -import cds.gen.adminservice.Upload_; -import cds.gen.my.bookshop.Bookshop_; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; import my.bookshop.MessageKeys; +import org.springframework.stereotype.Component; /** * Custom business logic for the "Admin Service" (see admin-service.cds) @@ -100,11 +96,11 @@ public void beforeCreateOrder(Stream orders, EventContext context) { // calculate the actual quantity difference // FIXME this should handle book changes, currently only quantity changes are handled int diffQuantity = quantity - db.run(Select.from(Bookshop_.ORDER_ITEMS).columns(i -> i.quantity()).byId(orderItem.getId())) - .first(OrderItems.class).map(i -> i.getQuantity()).orElse(0); + .first().map(i -> i.getQuantity()).orElse(0); // check if enough books are available - Result result = db.run(Select.from(BOOKS).columns(b -> b.ID(), b -> b.stock(), b -> b.price()).byId(bookId)); - result.first(Books.class).ifPresent(book -> { + var result = db.run(Select.from(BOOKS).columns(b -> b.ID(), b -> b.stock(), b -> b.price()).byId(bookId)); + result.first().ifPresent(book -> { if (book.getStock() < diffQuantity) { // Tip: you can have localized messages and use parameters in your messages messages.error(MessageKeys.BOOK_REQUIRE_STOCK, book.getStock()) @@ -128,38 +124,31 @@ public void beforeCreateOrder(Stream orders, EventContext context) { }); } - /** + /* * Calculate the total order value preview when editing an order item - * - * @param context - * @param orderItem */ - @Before(event = DraftService.EVENT_DRAFT_PATCH) - public void patchOrderItems(DraftPatchEventContext context, OrderItems orderItem) { + @Before + public void patchOrderItems(DraftPatchEventContext context, OrderItems_ ref, OrderItems orderItem) { // check if quantity or book was updated Integer quantity = orderItem.getQuantity(); String bookId = orderItem.getBookId(); - String orderItemId = orderItem.getId(); - BigDecimal amount = calculateAmountInDraft(orderItemId, quantity, bookId); + BigDecimal amount = calculateAmountInDraft(ref, quantity, bookId); if (amount != null) { orderItem.setAmount(amount); } } - /** + /* * Calculate the total order value preview when deleting an order item from the order - * - * @param context */ - @Before(event = DraftService.EVENT_DRAFT_CANCEL, entity = OrderItems_.CDS_NAME) - public void cancelOrderItems(DraftCancelEventContext context) { - String orderItemId = (String) analyzer.analyze(context.getCqn()).targetKeys().get(OrderItems.ID); - if(orderItemId != null) { - calculateAmountInDraft(orderItemId, 0, null); + @Before + public void cancelOrderItems(DraftCancelEventContext context, OrderItems_ ref) { + if(ref.asRef().targetSegment().filter().isPresent()) { + calculateAmountInDraft(ref, 0, null); } } - private BigDecimal calculateAmountInDraft(String orderItemId, Integer newQuantity, String newBookId) { + private BigDecimal calculateAmountInDraft(OrderItems_ ref, Integer newQuantity, String newBookId) { Integer quantity = newQuantity; String bookId = newBookId; if (quantity == null && bookId == null) { @@ -167,12 +156,11 @@ private BigDecimal calculateAmountInDraft(String orderItemId, Integer newQuantit } // get the order item that was updated (to get access to the book price, quantity and order total) - Result result = adminService.run(Select.from(ORDER_ITEMS) + var result = adminService.run(Select.from(ref) .columns(o -> o.quantity(), o -> o.amount(), o -> o.book().expand(b -> b.ID(), b -> b.price()), - o -> o.parent().expand(p -> p.ID(), p -> p.total())) - .where(o -> o.ID().eq(orderItemId).and(o.IsActiveEntity().eq(false)))); - OrderItems itemToPatch = result.first(OrderItems.class).orElseThrow(notFound(MessageKeys.ORDERITEM_MISSING)); + o -> o.parent().expand(p -> p.ID(), p -> p.total()))); + OrderItems itemToPatch = result.single(); BigDecimal bookPrice = null; // fallback to existing values @@ -191,9 +179,8 @@ private BigDecimal calculateAmountInDraft(String orderItemId, Integer newQuantit // get the price of the updated book ID if(bookPrice == null) { - result = db.run(Select.from(BOOKS).byId(bookId).columns(b -> b.price())); - Books book = result.first(Books.class).orElseThrow(notFound(MessageKeys.BOOK_MISSING)); - bookPrice = book.getPrice(); + var bookResult = db.run(Select.from(BOOKS).byId(bookId).columns(b -> b.price())); + bookPrice = bookResult.single().getPrice(); } // update the amount of the order item @@ -215,9 +202,9 @@ private BigDecimal calculateAmountInDraft(String orderItemId, Integer newQuantit * @param context */ @On(entity = Books_.CDS_NAME) - public void addBookToOrder(BooksAddToOrderContext context) { + public Orders addBookToOrder(BooksAddToOrderContext context) { String orderId = context.getOrderId(); - List orders = adminService.run(Select.from(ORDERS).columns(o -> o._all(), o -> o.Items().expand()).where(o -> o.ID().eq(orderId))).listOf(Orders.class); + List orders = adminService.run(Select.from(ORDERS).columns(o -> o._all(), o -> o.Items().expand()).where(o -> o.ID().eq(orderId))).list(); Orders order = orders.stream().filter(p -> p.getIsActiveEntity()).findFirst().orElse(null); // check that the order with given ID exists and is not in draft-mode @@ -241,9 +228,9 @@ public void addBookToOrder(BooksAddToOrderContext context) { newItem.setQuantity(context.getQuantity()); order.getItems().add(newItem); - Orders updatedOrder = adminService.run(Update.entity(ORDERS).data(order)).single(Orders.class); + Orders updatedOrder = adminService.run(Update.entity(ORDERS).data(order)).single(); messages.success(MessageKeys.BOOK_ADDED_ORDER); - context.setResult(updatedOrder); + return updatedOrder; } /** @@ -260,7 +247,7 @@ public Upload getUploadSingleton() { * @param csv */ @On - public void addBooksViaCsv(CdsUpdateEventContext context, Upload upload) { + public List addBooksViaCsv(CdsUpdateEventContext context, Upload upload) { InputStream is = upload.getCsv(); if (is != null) { try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { @@ -287,7 +274,7 @@ public void addBooksViaCsv(CdsUpdateEventContext context, Upload upload) { throw new ServiceException(ErrorStatuses.SERVER_ERROR, MessageKeys.BOOK_IMPORT_INVALID_CSV, e); } } - context.setResult(Arrays.asList(upload)); + return Arrays.asList(upload); } @Before(event = {CqnService.EVENT_CREATE, CqnService.EVENT_UPDATE, DraftService.EVENT_DRAFT_NEW, DraftService.EVENT_DRAFT_PATCH}) @@ -296,10 +283,6 @@ public void restoreCoversUpId(CqnStructuredTypeRef ref, BooksCovers cover) { cover.setUpId((String) analyzer.analyze(ref).rootKeys().get(Books.ID)); } - private Supplier notFound(String message) { - return () -> new ServiceException(ErrorStatuses.NOT_FOUND, message); - } - private BigDecimal defaultZero(BigDecimal decimal) { return decimal == null ? BigDecimal.valueOf(0) : decimal; } diff --git a/srv/src/main/java/my/bookshop/handlers/CatalogServiceHandler.java b/srv/src/main/java/my/bookshop/handlers/CatalogServiceHandler.java index d477c3ff..8230e2e6 100644 --- a/srv/src/main/java/my/bookshop/handlers/CatalogServiceHandler.java +++ b/srv/src/main/java/my/bookshop/handlers/CatalogServiceHandler.java @@ -1,18 +1,16 @@ package my.bookshop.handlers; import static cds.gen.catalogservice.CatalogService_.BOOKS; -import static cds.gen.catalogservice.CatalogService_.REVIEWS; - -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.stereotype.Component; +import cds.gen.catalogservice.Books; +import cds.gen.catalogservice.BooksAddReviewContext; +import cds.gen.catalogservice.Books_; +import cds.gen.catalogservice.CatalogService_; +import cds.gen.catalogservice.Reviews; +import cds.gen.catalogservice.SubmitOrderContext; +import cds.gen.reviewservice.ReviewService; +import cds.gen.reviewservice.ReviewService_; import com.sap.cds.Result; -import com.sap.cds.Struct; import com.sap.cds.ql.CQL; import com.sap.cds.ql.Insert; import com.sap.cds.ql.Select; @@ -34,17 +32,13 @@ import com.sap.cds.services.messages.Messages; import com.sap.cds.services.persistence.PersistenceService; import com.sap.cds.services.request.FeatureTogglesInfo; - -import cds.gen.catalogservice.BooksAddReviewContext; -import cds.gen.catalogservice.Books; -import cds.gen.catalogservice.Books_; -import cds.gen.catalogservice.CatalogService_; -import cds.gen.catalogservice.Reviews; -import cds.gen.catalogservice.SubmitOrderContext; -import cds.gen.reviewservice.ReviewService; -import cds.gen.reviewservice.ReviewService_; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import my.bookshop.MessageKeys; import my.bookshop.RatingCalculator; +import org.springframework.stereotype.Component; /** * Custom business logic for the "Catalog Service" (see cat-service.cds) @@ -92,22 +86,18 @@ public List items(List items) { context.setCqn(copy); } - /** + /* * Invokes some validations before creating a review. - * - * @param context {@link ReviewContext} */ - @Before(entity = Books_.CDS_NAME) - public void beforeAddReview(BooksAddReviewContext context) { + @Before + public void beforeAddReview(Books_ ref, BooksAddReviewContext context) { String user = context.getUserInfo().getName(); - String bookId = (String) analyzer.analyze(context.getCqn()).targetKeys().get(Books.ID); - Result result = db.run(Select.from(REVIEWS) - .where(review -> review.book_ID().eq(bookId).and(review.createdBy().eq(user)))); + var result = db.run(Select.from(ref.reviews()) + .where(review -> review.createdBy().eq(user))); if (result.first().isPresent()) { - throw new ServiceException(ErrorStatuses.METHOD_NOT_ALLOWED, MessageKeys.REVIEW_ADD_FORBIDDEN) - .messageTarget(REVIEWS, r -> r.createdBy()); + throw new ServiceException(ErrorStatuses.METHOD_NOT_ALLOWED, MessageKeys.REVIEW_ADD_FORBIDDEN); } } @@ -116,8 +106,8 @@ public void beforeAddReview(BooksAddReviewContext context) { * * @param context {@link ReviewContext} */ - @On(entity = Books_.CDS_NAME) - public void onAddReview(BooksAddReviewContext context) { + @On + public Reviews onAddReview(Books_ ref, BooksAddReviewContext context) { String bookId = (String) analyzer.analyze(context.getCqn()).targetKeys().get(Books.ID); cds.gen.reviewservice.Reviews review = cds.gen.reviewservice.Reviews.create(); review.setBookId(bookId); @@ -126,10 +116,9 @@ public void onAddReview(BooksAddReviewContext context) { review.setText(context.getText()); Result res = reviewService.run(Insert.into(ReviewService_.REVIEWS).entry(review)); - cds.gen.reviewservice.Reviews inserted = res.single(cds.gen.reviewservice.Reviews.class); messages.success(MessageKeys.REVIEW_ADDED); - context.setResult(Struct.access(inserted).as(Reviews.class)); + return res.single(Reviews.class); } /** @@ -159,10 +148,10 @@ public void setIsReviewable(CdsReadEventContext context, List books) { return; } - CqnSelect query = Select.from(BOOKS, b -> b.filter(b.ID().in(bookIds)).reviews()) + var query = Select.from(BOOKS, b -> b.filter(b.ID().in(bookIds)).reviews()) .where(r -> r.createdBy().eq(user)); - Set reviewedBooks = db.run(query).streamOf(Reviews.class).map(Reviews::getBookId) + Set reviewedBooks = db.run(query).stream().map(Reviews::getBookId) .collect(Collectors.toSet()); for (Books book : books) { @@ -173,24 +162,19 @@ public void setIsReviewable(CdsReadEventContext context, List books) { } @On - public void onSubmitOrder(SubmitOrderContext context) { + public SubmitOrderContext.ReturnType onSubmitOrder(SubmitOrderContext context) { Integer quantity = context.getQuantity(); String bookId = context.getBook(); - Optional book = db.run(Select.from(BOOKS).columns(Books_::stock).byId(bookId)).first(Books.class); - - book.orElseThrow(() -> new ServiceException(ErrorStatuses.NOT_FOUND, MessageKeys.BOOK_MISSING) - .messageTarget(BOOKS, b -> b.ID())); - - int stock = book.map(Books::getStock).get(); + Books book = db.run(Select.from(BOOKS).columns(Books_::stock).byId(bookId)).single(); + int stock = book.getStock(); if (stock >= quantity) { db.run(Update.entity(BOOKS).byId(bookId).data(Books.STOCK, stock -= quantity)); SubmitOrderContext.ReturnType result = SubmitOrderContext.ReturnType.create(); result.setStock(stock); - - context.setResult(result); + return result; } else { throw new ServiceException(ErrorStatuses.CONFLICT, MessageKeys.ORDER_EXCEEDS_STOCK, quantity); } diff --git a/srv/src/main/java/my/bookshop/handlers/HierarchySiblingActionHandler.java b/srv/src/main/java/my/bookshop/handlers/HierarchySiblingActionHandler.java index 0791daaf..5cd4dfad 100644 --- a/srv/src/main/java/my/bookshop/handlers/HierarchySiblingActionHandler.java +++ b/srv/src/main/java/my/bookshop/handlers/HierarchySiblingActionHandler.java @@ -2,23 +2,18 @@ import static cds.gen.adminservice.AdminService_.GENRE_HIERARCHY; -import java.util.List; - -import org.springframework.stereotype.Component; - -import com.sap.cds.ql.CQL; +import cds.gen.adminservice.AdminService_; +import cds.gen.adminservice.GenreHierarchy; +import cds.gen.adminservice.GenreHierarchyMoveSiblingContext; +import cds.gen.adminservice.GenreHierarchy_; import com.sap.cds.ql.Select; import com.sap.cds.ql.Update; -import com.sap.cds.ql.cqn.CqnStructuredTypeRef; import com.sap.cds.services.handler.EventHandler; import com.sap.cds.services.handler.annotations.On; import com.sap.cds.services.handler.annotations.ServiceName; import com.sap.cds.services.persistence.PersistenceService; - -import cds.gen.adminservice.AdminService_; -import cds.gen.adminservice.GenreHierarchy; -import cds.gen.adminservice.GenreHierarchyMoveSiblingContext; -import cds.gen.adminservice.GenreHierarchy_; +import java.util.List; +import org.springframework.stereotype.Component; @Component @ServiceName(AdminService_.CDS_NAME) @@ -33,18 +28,18 @@ public class HierarchySiblingActionHandler implements EventHandler { this.db = db; } - @On(entity = GenreHierarchy_.CDS_NAME) - void onMoveSiblingAction(CqnStructuredTypeRef ref, GenreHierarchyMoveSiblingContext context) { + @On + void onMoveSiblingAction(GenreHierarchy_ ref, GenreHierarchyMoveSiblingContext context) { // Find current node and its parent - GenreHierarchy toMove = db.run(Select.from(CQL.entity(GENRE_HIERARCHY, ref)) + GenreHierarchy toMove = db.run(Select.from(ref) .columns(c -> c.ID(), c -> c.parent_ID())) - .single(GenreHierarchy.class); + .single(); // Find all children of the parent, which are siblings of the entry being moved List siblingNodes = db.run(Select.from(GENRE_HIERARCHY) .columns(c -> c.ID(), c -> c.siblingRank()) .where(c -> c.parent_ID().eq(toMove.getParentId()))) - .listOf(GenreHierarchy.class); + .list(); int oldPosition = 0; int newPosition = siblingNodes.size(); diff --git a/srv/src/main/java/my/bookshop/handlers/NotesServiceHandler.java b/srv/src/main/java/my/bookshop/handlers/NotesServiceHandler.java index 5801cf2a..b257b803 100644 --- a/srv/src/main/java/my/bookshop/handlers/NotesServiceHandler.java +++ b/srv/src/main/java/my/bookshop/handlers/NotesServiceHandler.java @@ -3,18 +3,14 @@ import static cds.gen.notesservice.NotesService_.ADDRESSES; import static cds.gen.notesservice.NotesService_.NOTES; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.stereotype.Component; - -import com.sap.cds.Result; +import cds.gen.api_business_partner.ApiBusinessPartner; +import cds.gen.api_business_partner.ApiBusinessPartner_; +import cds.gen.notesservice.Addresses; +import cds.gen.notesservice.Addresses_; +import cds.gen.notesservice.Notes; +import cds.gen.notesservice.NotesService_; +import cds.gen.notesservice.Notes_; +import com.sap.cds.CdsResult; import com.sap.cds.ql.CQL; import com.sap.cds.ql.Predicate; import com.sap.cds.ql.Select; @@ -31,14 +27,15 @@ import com.sap.cds.services.handler.EventHandler; import com.sap.cds.services.handler.annotations.On; import com.sap.cds.services.handler.annotations.ServiceName; - -import cds.gen.api_business_partner.ApiBusinessPartner; -import cds.gen.api_business_partner.ApiBusinessPartner_; -import cds.gen.notesservice.Addresses; -import cds.gen.notesservice.Addresses_; -import cds.gen.notesservice.Notes; -import cds.gen.notesservice.NotesService_; -import cds.gen.notesservice.Notes_; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Component; @Component @ServiceName(NotesService_.CDS_NAME) @@ -53,24 +50,25 @@ public class NotesServiceHandler implements EventHandler { } @On(entity = Addresses_.CDS_NAME) - Result readAddresses(CdsReadEventContext context) { + void readAddresses(CdsReadEventContext context) { List segments = context.getCqn().ref().segments(); // via note if(segments.size() == 2 && segments.getFirst().id().equals(Notes_.CDS_NAME)) { Map noteKeys = analyzer.analyze(context.getCqn()).rootKeys(); - Notes note = context.getService().run(Select.from(NOTES).columns(n -> n.address_businessPartner(), n -> n.address_ID()).matching(noteKeys)).single(Notes.class); + Notes note = context.getService().run(Select.from(NOTES).columns(n -> n.address_businessPartner(), n -> n.address_ID()).matching(noteKeys)).single(); CqnSelect addressOfNote = CQL.copy(context.getCqn(), new Modifier() { @Override public CqnStructuredTypeRef ref(CqnStructuredTypeRef ref) { - return CQL.entity(Addresses_.CDS_NAME) - .filter(p -> p.get(Addresses.BUSINESS_PARTNER).eq(note.getAddressBusinessPartner()) - .and(p.get(Addresses.ID).eq(note.getAddressId()))) + return CQL.entity(ADDRESSES) + .filter(p -> p.businessPartner().eq(note.getAddressBusinessPartner()) + .and(p.ID().eq(note.getAddressId()))) .asRef(); } }); - return context.getService().run(addressOfNote); + context.setResult(context.getService().run(addressOfNote)); + return; } // notes expanded? @@ -85,30 +83,30 @@ public List items(List items) { }); // read addresses - Result addresses = bupa.run(noNotesExpand); + var addresses = CdsResult.of(bupa.run(noNotesExpand), Addresses.class); // add expanded notes? CqnExpand notesExpand = notesExpandHolder.get(); if(notesExpand != null) { - Select notesSelect = Select.from(NOTES) + var notesSelect = Select.from(NOTES) .columns(ensureSelected(notesExpand.items(), Notes.ADDRESS_BUSINESS_PARTNER, Notes.ADDRESS_ID)) .orderBy(notesExpand.orderBy()) - .where(n -> CQL.or(addresses.streamOf(Addresses.class) + .where(n -> CQL.or(addresses.stream() .map(address -> n.address_businessPartner().eq(address.getBusinessPartner()).and(n.address_ID().eq(address.getId()))) .collect(Collectors.toList())) .and(predicate(notesExpand.ref().rootSegment()))); - Result notes = context.getService().run(notesSelect); - for(Addresses address : addresses.listOf(Addresses.class)) { + var notes = context.getService().run(notesSelect); + for(Addresses address : addresses) { address.setNotes( - notes.streamOf(Notes.class) + notes.stream() .filter(n -> n.getAddressBusinessPartner().equals(address.getBusinessPartner()) && n.getAddressId().equals(address.getId())) .collect(Collectors.toList())); } } - return addresses; + context.setResult(addresses); } @On(entity = Notes_.CDS_NAME) @@ -121,7 +119,7 @@ void readNotes(CdsReadEventContext context) { @Override public CqnStructuredTypeRef ref(CqnStructuredTypeRef ref) { - return CQL.entity(Notes_.CDS_NAME).filter(predicate(segments.get(1))).asRef(); + return CQL.entity(NOTES).filter(predicate(segments.get(1))).asRef(); } @Override @@ -153,10 +151,10 @@ public List items(List items) { CqnExpand addressExpand = addressExpandHolder.get(); if(addressExpand != null) { // read notes and join with addresses - Result notes = context.getService().run(noAddressExpand); - List notesWithAddresses = notes.streamOf(Notes.class).filter(n -> n.getAddressBusinessPartner() != null && n.getAddressId() != null).collect(Collectors.toList()); + var notes = CdsResult.of(context.getService().run(noAddressExpand), Notes.class); + List notesWithAddresses = notes.stream().filter(n -> n.getAddressBusinessPartner() != null && n.getAddressId() != null).collect(Collectors.toList()); if (notesWithAddresses.size() > 0) { - Select addressSelect = Select.from(ADDRESSES) + var addressSelect = Select.from(ADDRESSES) .columns(ensureSelected(addressExpand.items(), Addresses.BUSINESS_PARTNER, Addresses.ID)) .orderBy(addressExpand.orderBy()) .where(a -> CQL.or(notesWithAddresses.stream() @@ -164,9 +162,9 @@ public List items(List items) { .collect(Collectors.toList())) .and(predicate(addressExpand.ref().rootSegment()))); - Result addresses = context.getService().run(addressSelect); - for(Notes note : notes.listOf(Notes.class)) { - note.setAddress(addresses.streamOf(Addresses.class) + var addresses = context.getService().run(addressSelect); + for(Notes note : notes) { + note.setAddress(addresses.stream() .filter(a -> a.getBusinessPartner().equals(note.getAddressBusinessPartner()) && a.getId().equals(note.getAddressId())) .findFirst().orElse(null)); diff --git a/srv/src/main/resources/application.yaml b/srv/src/main/resources/application.yaml index 2096b3cf..5f9dac1d 100644 --- a/srv/src/main/resources/application.yaml +++ b/srv/src/main/resources/application.yaml @@ -6,6 +6,8 @@ spring: jmx: enabled: true cds: + errors: + prefer-service-exception: true odata-v4: endpoint.path: "/api" security: