<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.vaithu.holon</groupId> <artifactId>holon-jdbc</artifactId> <name>Holon JDBC</name> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <vaadin.version>14.3.0</vaadin.version> <holon.platform.version>5.4.2</holon.platform.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.0.RELEASE</version> </parent> <repositories> <!-- The order of definitions matters. Explicitly defining central here to make sure it has the highest priority. --> <!-- Main Maven repository --> <repository> <id>central</id> <url>https://repo1.maven.apache.org/maven2</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <!-- Repository used by many Vaadin add-ons --> <repository> <id>Vaadin Directory</id> <url>https://maven.vaadin.com/vaadin-addons</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <pluginRepositories> <!-- Main Maven repository --> <pluginRepository> <id>central</id> <url>https://repo.maven.apache.org/maven2</url> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> <dependencyManagement> <dependencies> <dependency> <groupId>com.vaadin</groupId> <artifactId>vaadin-bom</artifactId> <version>${vaadin.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.holon-platform</groupId> <artifactId>bom</artifactId> <version>${holon.platform.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.vaadin</groupId> <!-- Replace artifactId with vaadin-core to use only free components --> <artifactId>vaadin</artifactId> <exclusions> <!-- Webjars are only needed when running in Vaadin 13 compatibility mode --> <exclusion> <groupId>com.vaadin.webjar</groupId> <artifactId>*</artifactId> </exclusion> <exclusion> <groupId>org.webjars.bowergithub.insites</groupId> <artifactId>*</artifactId> </exclusion> <exclusion> <groupId>org.webjars.bowergithub.polymer</groupId> <artifactId>*</artifactId> </exclusion> <exclusion> <groupId>org.webjars.bowergithub.polymerelements</groupId> <artifactId>*</artifactId> </exclusion> <exclusion> <groupId>org.webjars.bowergithub.vaadin</groupId> <artifactId>*</artifactId> </exclusion> <exclusion> <groupId>org.webjars.bowergithub.webcomponents</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.vaadin</groupId> <artifactId>vaadin-spring-boot-starter</artifactId> <exclusions> <!-- Excluding so that webjars are not included. --> <exclusion> <groupId>com.vaadin</groupId> <artifactId>vaadin-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.vaadin</groupId> <artifactId>vaadin-testbench</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.github.bonigarcia</groupId> <artifactId>webdrivermanager</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <!-- Holon: JDBC starter --> <dependency> <groupId>com.holon-platform.jdbc</groupId> <artifactId>holon-starter-jdbc-datastore-hikaricp</artifactId> </dependency> <!-- Holon: Vaadin Flow starter --> <dependency> <groupId>com.holon-platform.vaadin</groupId> <artifactId>holon-starter-vaadin-flow</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8 --> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <version>19.7.0.0</version> </dependency> </dependencies> <build> <defaultGoal>spring-boot:run</defaultGoal> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <!-- Clean build and startup time for Vaadin apps sometimes may exceed the default Spring Boot's 30sec timeout. --> <configuration> <wait>500</wait> <maxAttempts>240</maxAttempts> </configuration> </plugin> <!-- Take care of synchronizing java dependencies and imports in package.json and main.js files. It also creates webpack.config.js if not exists yet. --> <plugin> <groupId>com.vaadin</groupId> <artifactId>vaadin-maven-plugin</artifactId> <version>${vaadin.version}</version> <executions> <execution> <goals> <goal>prepare-frontend</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <profiles> <profile> <!-- Production mode is activated using -Pproduction --> <id>production</id> <properties> <vaadin.productionMode>true</vaadin.productionMode> </properties> <dependencies> <dependency> <groupId>com.vaadin</groupId> <artifactId>flow-server-production-mode</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <jvmArguments>-Dvaadin.productionMode</jvmArguments> </configuration> </plugin> <plugin> <groupId>com.vaadin</groupId> <artifactId>vaadin-maven-plugin</artifactId> <version>${vaadin.version}</version> <executions> <execution> <goals> <goal>build-frontend</goal> </goals> <phase>compile</phase> </execution> </executions> </plugin> </plugins> </build> </profile> <profile> <id>it</id> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <id>start-spring-boot</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop-spring-boot</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> <!-- Runs the integration tests (*IT) after the server is started --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-failsafe-plugin</artifactId> <executions> <execution> <goals> <goal>integration-test</goal> <goal>verify</goal> </goals> </execution> </executions> <configuration> <trimStackTrace>false</trimStackTrace> <enableAssertions>true</enableAssertions> </configuration> </plugin> </plugins> </build> </profile> </profiles> </project>
Author: skadhir
SnackOrder example using Holon-Platform and Vaadin
This is my another post in learning my Vaadin journey through Holon Platform. There is already an example here https://vaadin.com/learn/tutorials/dynamic-web-forms-with-validation-in-java explaining the concepts but I just want to mimic the same so that I can learn more about the Holon Platform APIs myself.
Here is the simplified code for the Dynamic WebForm example of SnackOrder
import com.holonplatform.core.property.PropertyBox; import com.holonplatform.vaadin.flow.components.Components; import com.holonplatform.vaadin.flow.components.PropertyInputForm; import com.holonplatform.vaadin.flow.components.PropertyListing; import com.holonplatform.vaadin.flow.components.SingleSelect; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.combobox.ComboBox; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.data.provider.ListDataProvider; import com.vaadin.flow.router.Route; import java.util.*; @Route("Snack") public class SnackOrderView extends VerticalLayout { private PropertyInputForm inputForm; private PropertyListing listing; public SnackOrderView() { initialize(); } private void initialize() { Map<String, List<String>> snacks = new HashMap<>(); snacks.put("Fruits", Arrays.asList("Banana", "Apple", "Orange", "Avocado")); snacks.put("Candy", Arrays.asList("Chocolate bar", "Gummy bears", "Granola bar")); snacks.put("Drinks", Arrays.asList("Soda", "Water", "Coffee", "Tea")); List<PropertyBox> snackOrders = new ArrayList<>(); ListDataProvider<PropertyBox> gridDataProvider = new ListDataProvider<>(snackOrders); Button orderBtn = Components.button() .text("Order") .withThemeName("primary") .enabled(false) .onClick(event -> { snackOrders.add(inputForm.getValue()); listing.refresh(); }) .build(); SingleSelect<String> snackSelect = Components.input.singleSelect(String.class) .required() .enabled(false) .withValueChangeListener(event -> { orderBtn.setEnabled(true); }) .build(); SingleSelect<String> snackTypeSelect = Components.input.singleSelect(String.class) .required() .items(snacks.keySet()) .withValueChangeListener(event -> { ((ComboBox<String>) snackSelect.getComponent()).setItems(snacks.get(event.getValue())); ((ComboBox<String>) snackSelect.getComponent()).setEnabled(true); }) .build(); HorizontalLayout hl = Components.hl().build(); inputForm = Components.input.form(hl, SnackOrder.PROPERTY_SET) .composer((content, source) -> { source.getComponents().forEach(component -> { content.add(component); }); content.add(orderBtn); }) .bind(SnackOrder.TYPE, snackTypeSelect) .bind(SnackOrder.SNACK, snackSelect) .initializer(content -> { content.setSpacing(true); content.setDefaultVerticalComponentAlignment(Alignment.BASELINE); }) .withValueChangeListener(event -> { orderBtn.setEnabled(inputForm.isValid()); }) .build(); listing = Components.listing.properties(SnackOrder.PROPERTY_SET) .dataSource(gridDataProvider) .build(); add(inputForm.getComponent(), listing.getComponent()); } }
Here is the screenprint showing the neat and simple web form
Vaadin Lazy Loading issues
I’ve a grid like this and if I edit the description column it throws error “java.lang.IndexOutOfBoundsException: Index: 0, Size: 0”.
It happens when there is only one matching value in the combobox filter. The combobox looks like this
The dataprovider used is
CallbackDataProvider<String, String> descDataProvider = DataProvider.fromFilteringCallbacks(query -> { String filter = query.getFilter().orElse(""); datastore.query(Product.TARGET) .restrict(query.getLimit(), query.getOffset()) .stream(Product.DESCRIPTION) .filter(s -> StringUtils.startsWithIgnoreCase(s,filter)) .forEach(s -> System.out.print(s+"\t")); Stream<String> propertyBoxStream = datastore.query(Product.TARGET) .restrict(query.getLimit(), query.getOffset()) .stream(Product.DESCRIPTION) .filter(s -> StringUtils.startsWithIgnoreCase(s,filter)); // s -> s.startsWith(filter) return propertyBoxStream; }, query -> { String filter = query.getFilter().orElse(""); long propertyBoxStream = datastore.query(Product.TARGET) // .filter(Product.DESCRIPTION.startsWithIgnoreCase(filter)) // .filter(Product.DESCRIPTION.startsWithIgnoreCase(filter)) .stream(Product.DESCRIPTION) .filter(s -> StringUtils.startsWithIgnoreCase(s,filter)) .count(); write("filter " + filter+"\t count :"+propertyBoxStream); return (int) propertyBoxStream; });
and the stacktrace is
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(ArrayList.java:653) ~[na:1.8.0_131] at java.util.ArrayList.get(ArrayList.java:429) ~[na:1.8.0_131] at com.vaadin.flow.data.provider.DataCommunicator.lambda$getJsonItems$3(DataCommunicator.java:615) ~[flow-data-2.2.2.jar:2.2.2] at java.util.stream.IntPipeline$4$1.accept(IntPipeline.java:250) ~[na:1.8.0_131] at java.util.stream.Streams$RangeIntSpliterator.forEachRemaining(Streams.java:110) ~[na:1.8.0_131] at java.util.Spliterator$OfInt.forEachRemaining(Spliterator.java:693) ~[na:1.8.0_131] at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) ~[na:1.8.0_131] at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) ~[na:1.8.0_131] at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) ~[na:1.8.0_131] at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:1.8.0_131] at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) ~[na:1.8.0_131] at com.vaadin.flow.data.provider.DataCommunicator.getJsonItems(DataCommunicator.java:617) ~[flow-data-2.2.2.jar:2.2.2] at com.vaadin.flow.data.provider.DataCommunicator.collectChangesToSend(DataCommunicator.java:560) ~[flow-data-2.2.2.jar:2.2.2] at com.vaadin.flow.data.provider.DataCommunicator.flush(DataCommunicator.java:477) ~[flow-data-2.2.2.jar:2.2.2] at com.vaadin.flow.data.provider.DataCommunicator.lambda$requestFlush$2f364bb9$1(DataCommunicator.java:425) ~[flow-data-2.2.2.jar:2.2.2] at com.vaadin.flow.internal.StateTree.lambda$runExecutionsBeforeClientResponse$1(StateTree.java:368) ~[flow-server-2.2.2.jar:2.2.2] at java.util.ArrayList.forEach(ArrayList.java:1249) ~[na:1.8.0_131] at com.vaadin.flow.internal.StateTree.runExecutionsBeforeClientResponse(StateTree.java:365) ~[flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.server.communication.UidlWriter.encodeChanges(UidlWriter.java:411) ~[flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.server.communication.UidlWriter.createUidl(UidlWriter.java:187) ~[flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.server.communication.UidlRequestHandler.writeUidl(UidlRequestHandler.java:121) ~[flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.server.communication.UidlRequestHandler.synchronizedHandleRequest(UidlRequestHandler.java:90) ~[flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:40) ~[flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.server.VaadinService.handleRequest(VaadinService.java:1545) ~[flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.server.VaadinServlet.service(VaadinServlet.java:247) [flow-server-2.2.2.jar:2.2.2] at com.vaadin.flow.spring.SpringServlet.service(SpringServlet.java:120) [vaadin-spring-12.2.0.jar:na] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:712) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:352) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.springframework.web.servlet.mvc.ServletForwardingController.handleRequestInternal(ServletForwardingController.java:141) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:177) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:52) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.2.0.RELEASE.jar:5.2.0.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.0.RELEASE.jar:5.2.0.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579) [tomcat-embed-core-9.0.27.jar:9.0.27] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.27.jar:9.0.27] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_131] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_131] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.27.jar:9.0.27] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_131]
I do not know what is the problem and why it fails when there is only one matching value. If you know any solution, please put in the comments below.
Holon-Platform Examples for Vaadin Grid
Holon-Platform works on Properties. So, it is always best to have all your definitions modeled in an interface like here
import com.holonplatform.core.Validator; import com.holonplatform.core.datastore.DataTarget; import com.holonplatform.core.property.BooleanProperty; import com.holonplatform.core.property.NumericProperty; import com.holonplatform.core.property.PropertySet; import com.holonplatform.core.property.PropertyValueConverter; import com.holonplatform.core.property.StringProperty; /** * Product property model */ public interface Product { public static final NumericProperty<Long> ID = NumericProperty.longType("id").message("Id"); public static final StringProperty SKU = StringProperty.create("sku"); public static final StringProperty DESCRIPTION = StringProperty.create("description").message("Description"); public static final StringProperty CATEGORY = StringProperty.create("category"); public static final NumericProperty<Double> UNIT_PRICE = NumericProperty.doubleType("price") // not negative value validator .withValidator(Validator.notNegative()); public static final BooleanProperty WITHDRAWN = BooleanProperty.create("withdrawn") // set a property value converter from Integer model type to Boolean .converter(PropertyValueConverter.numericBoolean(Integer.class)); // Product property set public static final PropertySet<?> PROPERTY_SET = PropertySet .builderOf(ID, SKU, DESCRIPTION, CATEGORY, UNIT_PRICE, WITHDRAWN).withIdentifier(ID).build(); // "products" DataTarget public static final DataTarget<?> TARGET = DataTarget.named("products"); }
INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-1', 'Product 1', 'C1', 19.90, 0); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-2', 'Product 2', 'C2', 29.90, 0); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-3', 'Product 3', 'C1', 15.00, 0); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-4', 'Product 4', 'C2', 75.50, 1); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-5', 'Product 5', 'C3', 19.90, 0); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-6', 'Product 6', 'C1', 39.90, 0); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-7', 'Product 7', 'C4', 44.20, 0); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-8', 'Product 8', 'C1', 77.00, 0); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-9', 'Product 9', 'C3', 23.70, 1); INSERT INTO products (sku,description,category,price,withdrawn) VALUES ('product-10', 'Product 10', 'C2', 92.20, 0);
Note that, every table should have a primary key and if there is none, you cannot update the row using Holon-Platform and if you try to do it, this is the message you would see
2020-07-16 10:16:36.842 WARN 32220 — [nio-8080-exec-4] com.holonplatform.datastore.jdbc : (Save operation) Cannot obtain the primary key for operation [PropertyBoxOperationConfiguration [value=PropertyBox – PROPERTIES: [“transactionId”:java.lang.Long],[“interfaceName”:java.lang.String],[“createTime”:java.time.LocalDateTime] – VALUES: (“transactionId”=1065093643),(“interfaceName”=test),(“createTime”=2020-01-17T08:21:55.983), target=DataTarget [name=Product, type=java.lang.String]]]: an INSERT operation will be performed by default
As it says, it will be doing INSERT operation instead of UPDATE. So, ensure there is a primary key and if not, tell the PropertySet which column to use as the primary key (you can combine multiple columns to make them as primary key if needed) like
public static final PropertySet<?> PROPERTY_SET = PropertySet.builderOf(TRANSACTIONID,INTERFACENAME,CREATETIME) .withIdentifier(TRANSACTIONID) .build();
Now, we can create any components binding using this model definition. Let’s first see how to create a grid using Holon Platform Listing API
Components.configure(this) .fullSize() .add( Components.listing.properties(Product.PROPERTY_SET) .dataSource(datastore, Product.TARGET) .build() ) ;
Now, let’s add some properties like
multiSelect
selectAllCheckboxVisibility
columnReorderingAllowed
resizable
editable
customCompnentColumn
public class MainView extends VerticalLayout { @Autowired private Datastore datastore; private PropertyListing listing; @PostConstruct public void init() { Components.configure(this) .fullSize() .add(listing = Components.listing.properties(Product.PROPERTY_SET) .dataSource(datastore, Product.TARGET) .withThemeVariants(GridVariant.LUMO_COMPACT) .multiSelect() .selectAllCheckboxVisibility(GridMultiSelectionModel.SelectAllCheckboxVisibility.VISIBLE) .columnReorderingAllowed(true) .resizable(true) .editable() .withComponentColumn(properties -> { IronIcon editIcon = new IronIcon("lumo", "edit"); editIcon.setClassName("size-s"); return Components.button() .icon(editIcon) .withThemeName("small") .onClick(event -> listing.editItem(properties)) .build(); }) .header("Edit") .add() .build() ) ; } }
So, how can we convert a column values to a component like button or span? Here is an example which shows, how to convert the Withdrawn property into a span component. The steps to do are
- Include @JsModule(“@vaadin/vaadin-lumo-styles/badge.js”)
- Include @CssImport(value = “./styles/shared-styles.css”, include = “lumo-badge”)
- Add a new method componentRenderer to the fluent builder PropertyListing like
.componentRenderer(Product.WITHDRAWN,properties -> { boolean yes = properties.getValue(Product.WITHDRAWN); return Components.span() .text(yes ? "Yes" : "No") .elementConfiguration(element -> element.setAttribute("theme",yes ? "badge success" : "badge error")) .build(); })
How about the theming? Are there any default themes available to set how the row and column must render? Grid provides some default variants like
.withThemeVariants(GridVariant.LUMO_COMPACT,GridVariant.LUMO_COLUMN_BORDERS,GridVariant.LUMO_ROW_STRIPES)
Editing is also easy. You just have to call the following methods in sequence
editable editorBuffered(true) withComponentColumn editorComponent withEditorSaveListener
Here is a complete example code
Components.configure(this) .fullSize() .add(listing = Components.listing.properties(Product.PROPERTY_SET) .dataSource(datastore, Product.TARGET) .withThemeVariants(GridVariant.LUMO_COMPACT, GridVariant.LUMO_COLUMN_BORDERS, GridVariant.LUMO_ROW_STRIPES) .multiSelect() .selectAllCheckboxVisibility(GridMultiSelectionModel.SelectAllCheckboxVisibility.VISIBLE) .columnReorderingAllowed(true) .columnsAutoWidth() .resizable(true) .editable() .editorBuffered(true) .withComponentColumn(properties -> { IronIcon editIcon = new IronIcon("lumo", "edit"); editIcon.setClassName("size-s"); return Components.button() .icon(editIcon) .withThemeName("small") .onClick(event -> listing.editItem(properties)) .build(); }) .editorComponent(new Div( Components.button("Save", event -> { listing.saveEditingItem(); listing.refreshEditingItem(); }), Components.button("Cancel", event -> listing.cancelEditing()) )) .header("Edit") .add() .withEditorSaveListener(event -> { PropertyBox propertyBox = event.getItem(); datastore.save(Product.TARGET, propertyBox); listing.refreshEditingItem(); propertyBox.propertyValues().forEach(System.out::println); PropertyInputForm inputForm = Components.input.form(Product.PROPERTY_SET) .build(); inputForm.setValue(propertyBox); Components.dialog.message() .sizeUndefined() .withComponent(inputForm.getComponent()) .open(); }) .componentRenderer(Product.WITHDRAWN, properties -> { boolean yes = properties.getValue(Product.WITHDRAWN); return Components.span() .text(yes ? "Yes" : "No") .elementConfiguration(element -> element.setAttribute("theme", yes ? "badge success" : "badge error")) .build(); }) .build() ) ;
Now is the time to implement ItemClickListener. I feel it is the better way to show the details or any other action to the user when he/she clicks the row. Here I want to show a dialog window which will have the row values in a form and allow the user to edit the same. Once the YES button is clicked, the editing item is saved to the backend and the row is refreshed to show the new values. The code to achieve this function is
.withItemClickListener(event -> { showSaveDialog(event.getItem());
The method showSaveDialog() implementation is
private void showSaveDialog(PropertyBox propertyBox) { PropertyInputForm inputForm = Components.input.form(Product.PROPERTY_SET) .initializer(formLayout -> { formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("490px", 1)); formLayout.setWidth("490px"); }) .build(); inputForm.setValue(propertyBox); Dialog open = Components.dialog.question(confirmSelected -> { if (confirmSelected) { save(inputForm.getValue()); } }) .withComponent(inputForm.getComponent()) .open(); open.setCloseOnEsc(true); } private void save(PropertyBox propertyBox) { datastore.save(Product.TARGET, propertyBox); listing.refreshItem(propertyBox); propertyBox.propertyValues().forEach(objectPropertyValue -> { System.out.println(objectPropertyValue.getValue()); }); }
When there are thousands of records available in a database table, how do we load them lazily into our grid? The Holon-Platform datastore API makes everything super simple and here is the code to justify that.
.dataSource(DataProvider.fromCallbacks(query -> { return datastore.query(Product.TARGET).restrict(query.getLimit(),query.getOffset()) .stream(Product.PROPERTY_SET); }, query -> { return (int) datastore.query(Product.TARGET).count(); }))
Do you’ve any other API to make this so simple and that works like a charm? I bet, NO.
Is there any difference in the page loading or user see any difference? I did some local testing and it worked perfectly fine. I’ll have to do some load test to authenticate my above statement.
Next,let us see how to remove a row from the grid. The Datastore API has method to remove an item and the code to use is
datastore.delete(Product.TARGET,properties);
Since it is a virtual column, we can use the same method withComponentColumn and add it to the grid like
.withComponentColumn(properties -> { return Components.button() .icon(VaadinIcon.TRASH) .withThemeName("small") .withThemeVariants(ButtonVariant.LUMO_ERROR) .onClick(event -> deleteItem(properties)) .build(); }) .header("Delete") .add()
private void deleteItem(PropertyBox properties) { datastore.delete(Product.TARGET,properties); listing.refresh(); }
Are you wondering what about the validation? What if someone edits the row with invalid values? Does Holon Platform automatically take care of the validations already defined in the model file? Of course, YES. Here is an example to show the validation error when category property goes beyond 10 char limit. Not only this, it also takes care of preventing invalid values entered by the user. For example, if user tries to enter characters in place of numerals, it is automatically prevented by the framework. No, explicit code validation is required.
public static final StringProperty CATEGORY = StringProperty.create("category") .withValidator(Validator.max(10)) ;
Next, let’s see how to set some footer and a value to it. If you look into the column UNIT_PRICE, the values are in numeric. So, obviously we want to the total of all Product’s UNIT_PRICE. Here is the code to set footer
.footer(Product.UNIT_PRICE, calculateUnitPrice())
private String calculateUnitPrice() { return "Total : " + datastore.query(Product.TARGET) .findOne(Product.UNIT_PRICE.sum()).orElse(0.0000); }
What if user deletes a product? How can the footer show the updated value? To set the footer accordingly, we need first get the footer cell and then set the value like
listing.getFooter().ifPresent(item -> { ItemListing.ItemListingRow<Property<?>> itemListingRow = item.getRows().stream().filter(o -> o.getCell(Product.UNIT_PRICE).isPresent()) .findFirst() .orElse(null); if (itemListingRow != null) { itemListingRow.getCell(Product.UNIT_PRICE).ifPresent(itemListingCell -> itemListingCell.setText(calculateUnitPrice())); } });
private void deleteItem(PropertyBox properties) { datastore.delete(Product.TARGET, properties); listing.getFooter().ifPresent(item -> { ItemListing.ItemListingRow<Property<?>> itemListingRow = item.getRows().stream().filter(o -> o.getCell(Product.UNIT_PRICE).isPresent()) .findFirst() .orElse(null); if (itemListingRow != null) { itemListingRow.getCell(Product.UNIT_PRICE).ifPresent(itemListingCell -> itemListingCell.setText(calculateUnitPrice())); } }); listing.refresh(); }
How to switch Lumo themes dynamically?
I thought it is hard to change the themes on the fly but after looking into a sample, there is a simplest way to achieve that. So, here is the code which changes from Dark mode to Light and vice versa.
Button changeTheme = Components.button() .onClick(event -> { ThemeList themeList = UI.getCurrent().getElement().getThemeList(); if (themeList.contains(Lumo.DARK)) { themeList.remove(Lumo.DARK); }else { themeList.add(Lumo.DARK); } }) .text("Change theme") .build();
And screenprints are
If you want to switch just a single component/layout, again that is also so simple. All you need is, that component’s element -> themelist like
ThemeList themeList = formLayout.getElement().getThemeList();
and it will look like
Vaadin + Holon Platform Form Demo
Here is an example which explains how Vaadin Form works. I’d like to show the same using Holon Platform. This is more simple, less code and easy to understand.
First, we need to create an interface with all Form fields and its validators.
import com.holonplatform.core.Validator; import com.holonplatform.core.property.BooleanProperty; import com.holonplatform.core.property.PropertySet; import com.holonplatform.core.property.StringProperty; public interface SignUp { public static final StringProperty FIRSTNAME = StringProperty.create("firstname") .message("First Name") .withValidator(Validator.min(3)) .withValidator(Validator.notNull()) ; public static final StringProperty LASTTNAME = StringProperty.create("lastname") .message("Last Name") .withValidator(Validator.min(3)) .withValidator(Validator.notNull()) ; public static final StringProperty USRHANDLE = StringProperty.create("userhandle") .message("User Handle") .withValidator(Validator.min(3)) .withValidator(Validator.notNull()) ; public static final StringProperty PWD1 = StringProperty.create("password1") .message("Wanted Password") .withValidator(Validator.min(3)) .withValidator(Validator.notNull()) ; public static final StringProperty PWD2 = StringProperty.create("firstname") .message("Password again") .withValidator(Validator.min(3)) .withValidator(Validator.notNull()) ; public static final StringProperty EMAIL = StringProperty.create("email") .message("Email") .withValidator(Validator.email()) ; public static final BooleanProperty ALLOW_MARKETING = BooleanProperty.create("allow_marketing") .message("Allow Marketing") ; public static final PropertySet<?> PROPERTIES = PropertySet.of(FIRSTNAME,LASTTNAME,USRHANDLE,PWD1,PWD2,ALLOW_MARKETING,EMAIL); }
import com.holonplatform.core.Validator; import com.holonplatform.vaadin.flow.components.Components; import com.holonplatform.vaadin.flow.components.Input; import com.holonplatform.vaadin.flow.components.PropertyInputForm; import com.holonplatform.vaadin.flow.components.ValidatableInput; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.button.ButtonVariant; import com.vaadin.flow.component.formlayout.FormLayout; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.PasswordField; import com.vaadin.flow.data.binder.Binder; import com.vaadin.flow.data.binder.ErrorLevel; import com.vaadin.flow.data.binder.ValidationResult; import com.vaadin.flow.router.Route; import javax.annotation.PostConstruct; @Route public class MainView extends VerticalLayout { private ValidatableInput<String> pwd1; private ValidatableInput<String> pwd2; @PostConstruct public void init() { FormLayout formLayout = new FormLayout(); PropertyInputForm inputForm = PropertyInputForm.builder(formLayout, SignUp.PROPERTIES) .composer((content, source) -> { source.getComponents().forEach(component -> { content.add(component); }); }) .withPostProcessor((property, input) -> { input.setRequired(true); if (SignUp.USRHANDLE.equals(property)) { formLayout.setColspan(input.getComponent(), 2); } if (SignUp.ALLOW_MARKETING.equals(property)) { input.setRequired(false); } if (SignUp.EMAIL.equals(property)) { input.getComponent().setVisible(false); input.setRequired(false); } }) .initializer(content -> { content.setMaxWidth("500px"); content.getStyle().set("margin", "0 auto"); content.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1, FormLayout.ResponsiveStep.LabelsPosition.TOP), new FormLayout.ResponsiveStep("490px", 2, FormLayout.ResponsiveStep.LabelsPosition.TOP)); }) .bind(SignUp.PWD1, pwd1 = createSecretInput()) .bind(SignUp.PWD2, pwd2 = createSecretInput()) .withValidator(SignUp.PWD2,Validator.create(s -> s.equals(pwd1.getValue()),"Password1 and Password2 should match")) .validateOnValueChange(true) .build(); inputForm.getInput(SignUp.ALLOW_MARKETING).get().addValueChangeListener(event -> { inputForm.getInput(SignUp.EMAIL).get().setVisible(event.getValue()); inputForm.getInput(SignUp.EMAIL).get().setRequired(event.getValue()); }); Button join_the_comm = Components.button().text("Join the Comm") .withThemeVariants(ButtonVariant.LUMO_PRIMARY) .onClick(event -> { inputForm.validate(); if (inputForm.isValid()) { PropertyInputForm form = Components.input.form(SignUp.PROPERTIES) .build(); form.setValue(inputForm.getValue()); Components.dialog .message() .withComponent(form) .open(); } }) .build(); Components.configure(this) .addAndAlign(formLayout, Alignment.CENTER) .addAndAlign(join_the_comm, Alignment.CENTER) ; } private ValidatableInput<String> createSecretInput() { return ValidatableInput.builder(Components.input.secretString() .required() .build()) .validateOnValueChange(false).build(); } }
And the screenprints are
Holon Platform Quick Tutorial
I prefer Holon Platform Vaadin flow APIs over core Vaadin APIs because of its builder methods. It is easy to chain the methods so where ever possible I’d use Holon Platform. So, here is an example of it for the concepts explained here https://vaadin.com/learn/tutorials/vaadin-quick-start
@Route public class MainView extends VerticalLayout { private VerticalLayout todoList; private Input<String> todoTask; @PostConstruct public void init() { Components.configure(this) .add(Components.h2().text("Todo List").build()) .add(todoList = Components.vl().build()) .add(Components.hl() .spacing() .add(todoTask = Components.input.string().build()) .add(Components.button() .text("Add") .withThemeVariants(ButtonVariant.LUMO_PRIMARY) .withClickShortcut(Key.ENTER) .add() .onClick(click -> { todoList.add(Components.input .boolean_() .label(todoTask.getValue()) .build().getComponent() ); }) .build() ).build() ); } }
And the output looks like
Using cards in Vaadin
Yes, I know it is difficult to write CSS styles for a java programmer. All, he can do is, google and copy some styles that fits for him. So, I did the same. Here is the css file for cards.
.card { /* Add shadows to create the "card" effect */ box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); transition: 0.3s; border-radius: 5px; /* 5px rounded corners */ } /* On mouse-over, add a deeper shadow */ .card:hover { box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); } /* Add some padding inside the card container */ .container { padding: 2px 16px; }
And, how to use this in Java …
import com.holonplatform.core.datastore.Datastore; import com.holonplatform.vaadin.flow.components.Components; import com.holonplatform.vaadin.flow.components.Input; import com.holonplatform.vaadin.flow.components.PropertyListing; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.dependency.CssImport; import com.vaadin.flow.component.html.Div; import com.vaadin.flow.component.html.Emphasis; import com.vaadin.flow.component.html.H4; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.Route; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; @Route @CssImport(value = "./styles/cards.css") public class MainView extends VerticalLayout { @Autowired private Datastore datastore; private PropertyListing listing; @PostConstruct public void init() { setSizeFull(); listing = Components.listing.properties(Product.PRODUCT) .dataSource(datastore, Product.TARGET) .build(); add(addToCard("Form", Components.input .form(Product.PRODUCT) .build().getComponent())); add(addToCard("Grid", listing.getComponent())); } private Div addToCard(String text, Component component) { Div card = new Div(); Div container = new Div(); Emphasis emphasis = new Emphasis(text); container.add(new H4(emphasis)); container.add(component); container.setClassName("container"); card.add(container); card.setClassName("card"); card.setWidthFull(); return card; } }
There are several card types available but achieving all from Java is not only super complex but also needs time & patience. So, I wish Vaadin makes some cards components in the future.
Using Vaadin Lumo Badges
Using Vaadin Lumo Badges is fairly simple. All you need to know is, what are the themes available and how does they look like.
When I started looking for sample java code, I did not find anything straight. So, putting here some examples so that it will be easy for my future reference and you. Here is the sample code and some beautiful color screen prints showing all the varieties of badge themes available in Vaadin.
@Route @CssImport(value = "./styles/shared-styles.css", include = "lumo-badge") @JsModule("@vaadin/vaadin-lumo-styles/badge.js") public class MainView extends VerticalLayout { @PostConstruct public void init() { add(createBadges("Colors", "badge", "badge success", "badge error", "badge contrast")); add(createBadges("Primary", "badge primary", "badge success primary", "badge error primary", "badge contrast primary")); add(createBadges("Small", "badge small", "badge success small", "badge error small", "badge contrast small")); add(createBadges("Small Primary", "badge small primary", "badge success small primary", "badge error small primary", "badge contrast small primary")); add(createBadges("Pill", "badge pill", "badge success pill", "badge error pill", "badge contrast pill")); add(createBadges("Pill Primary", "badge primary pill", "badge success primary pill", "badge error primary pill", "badge contrast primary pill")); add(createEmptyBadges("Empty", "badge", "badge success", "badge error", "badge contrast")); add(createIconBadges("Colors Icon", "badge", "badge success", "badge error", "badge contrast")); add(createIconBadges("Primary Icon", "badge primary", "badge success primary", "badge error primary", "badge contrast primary")); add(createIconBadges("Small Icon", "badge small", "badge success small", "badge error small", "badge contrast small")); add(createIconBadges("Small Primary Icon", "badge small primary", "badge success small primary", "badge error small primary", "badge contrast small primary")); add(createIconBadges("Pill Icon", "badge pill", "badge success pill", "badge error pill", "badge contrast pill")); add(createIconBadges("Pill Primary Icon", "badge primary pill", "badge success primary pill", "badge error primary pill", "badge contrast primary pill")); } private HorizontalLayout createBadges(String title, String... themes) { add(new H2(title)); HorizontalLayout hl = new HorizontalLayout(); for (String theme : themes) { Span span = new Span(theme); span.getElement().setAttribute("theme", theme); hl.add(span); } return hl; } private HorizontalLayout createEmptyBadges(String title, String... themes) { add(new H2(title)); HorizontalLayout hl = new HorizontalLayout(); for (String theme : themes) { Span span = new Span(); span.getElement().setAttribute("theme", theme); hl.add(span); } return hl; } private HorizontalLayout createIconBadges(String title, String... themes) { add(new H4(title)); HorizontalLayout hl = new HorizontalLayout(); for (String theme : themes) { IronIcon icon = new IronIcon("lumo","checkmark"); icon.getElement().setAttribute("theme", theme); hl.add(icon); } return hl; } }
Screenprints
For more information, visit https://cdn.vaadin.com/vaadin-lumo-styles/1.6.0/demo/badges.html
Audit Logging using Log4j in IIB
Every project has its own way to capture the logs and they are mostly falls under one of these categories
- Subflows
- Monitoring Profiles
And these logs are stored either as files or in database tables. So, here I’m going to explain how to use Log4j to capture the logs from monitoring profiles. Note, we’re not going to use Log4j node but instead we use a simple log4j configuration file and some java code to capture all the elements available from the monitoring profiles.
Let us take a simple flow to understand.
And the monitoring configurations for MQInput Node are
So, the monitoring profile is ready and I’m going to use MQTT instead of MQ to get the monitoring event messages.
MQTT is enabled and the port number is 11884. Let us test the monitoring profile configured and see how the event message looks like.
The event monitoring messages are published in topic IBM/IntegrationBus/IB10NODE/Monitoring/default/# and the message looks like
<wmb:event xmlns:wmb="http://www.ibm.com/xmlns/prod/websphere/messagebroker/6.1.0/monitoring/event"> <wmb:eventPointData> <wmb:eventData wmb:productVersion="100011" wmb:eventSchemaVersion="6.1.0.3" wmb:eventSourceAddress="MQ Input.transaction.Start"> <wmb:eventIdentity wmb:eventName="OrderReceived"/> <wmb:eventSequence wmb:creationTime="2020-06-01T02:02:32.402892Z" wmb:counter="1"/> <wmb:eventCorrelation wmb:localTransactionId="f8a1af6d-013b-4420-9c1b-baf23d5e10a4-5" wmb:parentTransactionId="" wmb:globalTransactionId=""/> </wmb:eventData> <wmb:messageFlowData> <wmb:broker wmb:name="IB10NODE" wmb:UUID="b7d2e6dd-0b36-4728-9015-818133dbeb14"/> <wmb:executionGroup wmb:name="default" wmb:UUID="61704de3-b47c-4b23-b95f-0ffd934fdcb1"/> <wmb:messageFlow wmb:uniqueFlowName="IB10NODE.default.TestMon.TestMon" wmb:name="TestMon" wmb:UUID="429ff9ef-1c8d-48c1-bf42-99ac3bd247fb" wmb:threadId="21980"/> <wmb:node wmb:nodeLabel="MQ Input" wmb:nodeType="ComIbmMQInputNode" wmb:detail="IN.Q"/> </wmb:messageFlowData> </wmb:eventPointData> <wmb:applicationData xmlns=""> <wmb:complexContent wmb:elementName="MQMD"> <MQMD> <SourceQueue>IN.Q</SourceQueue> <Transactional>true</Transactional> <Encoding>273</Encoding> <CodedCharSetId>1208</CodedCharSetId> <Format>MQSTR </Format> <Version>2</Version> <Report>0</Report> <MsgType>8</MsgType> <Expiry>-1</Expiry> <Feedback>0</Feedback> <Priority>0</Priority> <Persistence>0</Persistence> <MsgId>414d5120494942763130514d475220204064d15e10000105</MsgId> <CorrelId>000000000000000000000000000000000000000000000000</CorrelId> <BackoutCount>0</BackoutCount> <ReplyToQ> </ReplyToQ> <ReplyToQMgr>IIBv10QMGR </ReplyToQMgr> <UserIdentifier>vaithu </UserIdentifier> <AccountingToken>160105150000002d74cc5dfa721e73431607412b32030000000000000000000b</AccountingToken> <ApplIdentityData> </ApplIdentityData> <PutApplType>28</PutApplType> <PutApplName>vaithu</PutApplName> <PutDate>2020-06-01</PutDate> <PutTime>02:02:32.480</PutTime> <ApplOriginData> </ApplOriginData> <GroupId>000000000000000000000000000000000000000000000000</GroupId> <MsgSeqNumber>1</MsgSeqNumber> <Offset>0</Offset> <MsgFlags>0</MsgFlags> <OriginalLength>-1</OriginalLength> </MQMD> </wmb:complexContent> </wmb:applicationData> <wmb:bitstreamData> <wmb:bitstream wmb:encoding="base64Binary">TUQgIAIAAAAAAAAACAAAAP////8AAAAAEQEAALgEAABNUVNUUiAgIAAAAAAAAAAAQU1RIElJQnYxMFFNR1IgIEBk0V4QAAEFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIElJQnYxMFFNR1IgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN4cDI2NyAgICAgIBYBBRUAAAAtdMxd+nIec0MWB0ErMgMAAAAAAAAAAAALICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAcAAAAZ21haWwuc3Jpbml2YXNhLkFwcGxpY2F0aW9uIDIwMjAwNjAxMDIwMjMyNDggICAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAA/////zxPcmRlcnM+CiAgIDxPcmRlcj4KICAgICAgPEN1c3RvbWVySUQ+R1JFQUw8L0N1c3RvbWVySUQ+CiAgICAgIDxFbXBsb3llZUlEPjY8L0VtcGxveWVlSUQ+CiAgICAgIDxPcmRlckRhdGU+MTk5Ny0wNS0wNlQwMDowMDowMDwvT3JkZXJEYXRlPgogICAgICA8UmVxdWlyZWREYXRlPjE5OTctMDUtMjBUMDA6MDA6MDA8L1JlcXVpcmVkRGF0ZT4KICAgICAgPFNoaXBJbmZvIFNoaXBwZWREYXRlPSIxOTk3LTA1LTA5VDAwOjAwOjAwIj4KICAgICAgICAgPFNoaXBWaWE+MjwvU2hpcFZpYT4KICAgICAgICAgPEZyZWlnaHQ+My4zNTwvRnJlaWdodD4KICAgICAgICAgPFNoaXBOYW1lPkdyZWF0IExha2VzIEZvb2QgTWFya2V0PC9TaGlwTmFtZT4KICAgICAgICAgPFNoaXBBZGRyZXNzPjI3MzIgQmFrZXIgQmx2ZC48L1NoaXBBZGRyZXNzPgogICAgICAgICA8U2hpcENpdHk+RXVnZW5lPC9TaGlwQ2l0eT4KICAgICAgICAgPFNoaXBSZWdpb24+T1I8L1NoaXBSZWdpb24+CiAgICAgICAgIDxTaGlwUG9zdGFsQ29kZT45NzQwMzwvU2hpcFBvc3RhbENvZGU+CiAgICAgICAgIDxTaGlwQ291bnRyeT5VU0E8L1NoaXBDb3VudHJ5PgogICAgICA8L1NoaXBJbmZvPgogICA8L09yZGVyPgo8L09yZGVycz4=</wmb:bitstream> </wmb:bitstreamData> </wmb:event>
Let us see what happens when there is an exception. To generate an exception, I’m just going to make the out queue name to a random non-existing queue OUTaa.Q and now the monitoring event with Exception tree looks like
<wmb:event xmlns:wmb="http://www.ibm.com/xmlns/prod/websphere/messagebroker/6.1.0/monitoring/event"> <wmb:eventPointData> <wmb:eventData wmb:productVersion="100011" wmb:eventSchemaVersion="6.1.0.3" wmb:eventSourceAddress="MQ Input.transaction.Rollback"> <wmb:eventIdentity wmb:eventName="OrderRollback"/> <wmb:eventSequence wmb:creationTime="2020-06-01T02:17:30.450741Z" wmb:counter="2"/> <wmb:eventCorrelation wmb:localTransactionId="47ab24c4-91c8-48b6-91a2-e0cc0393085b-1" wmb:parentTransactionId="" wmb:globalTransactionId=""/> </wmb:eventData> <wmb:messageFlowData> <wmb:broker wmb:name="IB10NODE" wmb:UUID="b7d2e6dd-0b36-4728-9015-818133dbeb14"/> <wmb:executionGroup wmb:name="default" wmb:UUID="61704de3-b47c-4b23-b95f-0ffd934fdcb1"/> <wmb:messageFlow wmb:uniqueFlowName="IB10NODE.default.TestMon.TestMon" wmb:name="TestMon" wmb:UUID="429ff9ef-1c8d-48c1-bf42-99ac3bd247fb" wmb:threadId="8076"/> <wmb:node wmb:nodeLabel="MQ Input" wmb:nodeType="ComIbmMQInputNode" wmb:detail="IN.Q"/> </wmb:messageFlowData> </wmb:eventPointData> <wmb:applicationData xmlns=""> <wmb:complexContent wmb:elementName="ExceptionList"> <ExceptionList> <RecoverableException> <File>F:\build\S1000_slot1\S1000_P\src\DataFlowEngine\TemplateNodes\ImbOutputTemplateNode.cpp</File> <Line>303</Line> <Function>ImbOutputTemplateNode::processMessageAssemblyToFailure</Function> <Type>ComIbmMQOutputNode</Type> <Name>TestMon#FCMComposite_1_2</Name> <Label>TestMon.MQ Output</Label> <Catalog>BIPmsgs</Catalog> <Severity>3</Severity> <Number>2230</Number> <Text>Caught exception and rethrowing</Text> <Insert> <Type>14</Type> <Text>TestMon.MQ Output</Text> </Insert> <MessageException> <File>F:\build\S1000_slot1\S1000_P\src\DataFlowEngine\Connectors\mqconnector\MQConnection.cpp</File> <Line>1003</Line> <Function>MQConnection::acquireOutputQueueHandle</Function> <Type></Type> <Name></Name> <Label></Label> <Catalog>BIPmsgs</Catalog> <Severity>3</Severity> <Number>2666</Number> <Text>Failed to open queue</Text> <Insert> <Type>2</Type> <Text>-1</Text> </Insert> <Insert> <Type>5</Type> <Text>MQW101</Text> </Insert> <Insert> <Type>2</Type> <Text>2085</Text> </Insert> <Insert> <Type>5</Type> <Text></Text> </Insert> <Insert> <Type>5</Type> <Text>IIBv10QMGR</Text> </Insert> <Insert> <Type>5</Type> <Text>OUTaa.Q</Text> </Insert> <Insert> <Type>2</Type> <Text>2</Text> </Insert> </MessageException> </RecoverableException> </ExceptionList> </wmb:complexContent> </wmb:applicationData> </wmb:event>
Now, we know how the monitoring profile and monitoring events works. Let us develop a solution which will accept these messages and log them as we needed. For this, I’ve another message flow and it looks like
The MQTTSubscribe node is used to get the published event monitoring messages and the Java compute node is used to decipher the event monitoring XML message and log them. A sample log file looks like
2020-05-31 22:54:29.036 [Thread-49] INFO - ----------------------------------------Transaction Starts--------------------------------------------------------- 2020-05-31 22:54:29.058 [Thread-49] INFO - Event Number :2 2020-05-31 22:54:30.001 [Thread-49] ERROR - Data Element Name :ExceptionList 2020-05-31 22:54:32.301 [Thread-49] ERROR - Error Message :Failed to open queue;MQW101;2085;IIBv10QMGR;OUTaa.Q; 2020-05-31 22:54:32.301 [Thread-49] INFO - ----------------------------------------Transaction Ends--------------------------------------------------------- 2020-05-31 22:54:36.142 [Thread-49] INFO - ----------------------------------------Transaction Starts--------------------------------------------------------- 2020-05-31 22:54:36.156 [Thread-49] INFO - Event Number :1 2020-05-31 22:54:37.475 [Thread-49] INFO - Data Element Name :MQMD 2020-05-31 22:54:37.476 [Thread-49] INFO - *********************************************************************** 2020-05-31 22:54:37.477 [Thread-49] INFO - SourceQueue : IN.Q 2020-05-31 22:54:37.478 [Thread-49] INFO - Transactional : true 2020-05-31 22:54:37.478 [Thread-49] INFO - Encoding : 273 2020-05-31 22:54:37.480 [Thread-49] INFO - CodedCharSetId : 1208 2020-05-31 22:54:37.480 [Thread-49] INFO - Format : MQSTR 2020-05-31 22:54:37.481 [Thread-49] INFO - Version : 2 2020-05-31 22:54:37.482 [Thread-49] INFO - Report : 0 2020-05-31 22:54:37.483 [Thread-49] INFO - MsgType : 8 2020-05-31 22:54:37.484 [Thread-49] INFO - Expiry : -1 2020-05-31 22:54:37.484 [Thread-49] INFO - Feedback : 0 2020-05-31 22:54:37.485 [Thread-49] INFO - Priority : 0 2020-05-31 22:54:37.485 [Thread-49] INFO - Persistence : 0 2020-05-31 22:54:37.486 [Thread-49] INFO - MsgId : 414d5120494942763130514d475220204064d15e1000010d 2020-05-31 22:54:37.486 [Thread-49] INFO - CorrelId : 000000000000000000000000000000000000000000000000 2020-05-31 22:54:37.487 [Thread-49] INFO - BackoutCount : 1 2020-05-31 22:54:37.487 [Thread-49] INFO - ReplyToQ : 2020-05-31 22:54:37.487 [Thread-49] INFO - ReplyToQMgr : IIBv10QMGR 2020-05-31 22:54:37.488 [Thread-49] INFO - UserIdentifier : vaithu 2020-05-31 22:54:37.488 [Thread-49] INFO - AccountingToken : 160105150000002d74cc5dfa721e73431607412b32030000000000000000000b 2020-05-31 22:54:37.488 [Thread-49] INFO - ApplIdentityData : 2020-05-31 22:54:37.490 [Thread-49] INFO - PutApplType : 28 2020-05-31 22:54:37.490 [Thread-49] INFO - PutApplName : vaithu 2020-05-31 22:54:37.491 [Thread-49] INFO - PutDate : 2020-06-01 2020-05-31 22:54:37.492 [Thread-49] INFO - PutTime : 02:54:15.150 2020-05-31 22:54:37.493 [Thread-49] INFO - ApplOriginData : 2020-05-31 22:54:37.493 [Thread-49] INFO - GroupId : 000000000000000000000000000000000000000000000000 2020-05-31 22:54:37.494 [Thread-49] INFO - MsgSeqNumber : 1 2020-05-31 22:54:37.494 [Thread-49] INFO - Offset : 0 2020-05-31 22:54:37.494 [Thread-49] INFO - MsgFlags : 0 2020-05-31 22:54:37.495 [Thread-49] INFO - OriginalLength : -1 2020-05-31 22:54:37.495 [Thread-49] INFO - *********************************************************************** 2020-05-31 22:54:37.495 [Thread-49] INFO - OrderReceived Payload : MD ÿÿÿÿ ¸ MQSTR AMQ IIBv10QMGR @dÑ^ IIBv10QMGR vaithu -tÌ]úrsCA+2 vaithu 2020060102541515 ÿÿÿÿ<Orders> <Order> <CustomerID>GREAL</CustomerID> <EmployeeID>6</EmployeeID> <OrderDate>1997-05-06T00:00:00</OrderDate> <RequiredDate>1997-05-20T00:00:00</RequiredDate> <ShipInfo ShippedDate="1997-05-09T00:00:00"> <ShipVia>2</ShipVia> <Freight>3.35</Freight> <ShipName>Great Lakes Food Market</ShipName> <ShipAddress>2732 Baker Blvd.</ShipAddress> <ShipCity>Eugene</ShipCity> <ShipRegion>OR</ShipRegion> <ShipPostalCode>97403</ShipPostalCode> <ShipCountry>USA</ShipCountry> </ShipInfo> </Order> </Orders> 2020-05-31 22:54:37.497 [Thread-49] INFO - ----------------------------------------Transaction Ends--------------------------------------------------------- 2020-05-31 22:54:41.125 [Thread-49] INFO - ----------------------------------------Transaction Starts--------------------------------------------------------- 2020-05-31 22:54:41.135 [Thread-49] INFO - Event Number :2 2020-05-31 22:54:42.179 [Thread-49] ERROR - Data Element Name :ExceptionList 2020-05-31 22:54:44.299 [Thread-49] ERROR - Error Message :Failed to open queue;MQW101;2085;IIBv10QMGR;OUTaa.Q; 2020-05-31 22:54:44.299 [Thread-49] INFO - ----------------------------------------Transaction Ends---------------------------------------------------------
Here each event message is clearly separated and easily readable. The exception handler captures the right error message and ignores all other unnecessary information. So, we know the key elements, input and output payload and the exception message. Since we’re using monitoring profile, there is no need for any custom subflows and this solution is production tested code and works fantastically, thanks to log4j settings.
2020-05-31 22:54:41.125 [Thread-49] INFO - ----------------------------------------Transaction Starts--------------------------------------------------------- 2020-05-31 22:54:41.135 [Thread-49] INFO - Event Number :2 2020-05-31 22:54:42.179 [Thread-49] ERROR - Data Element Name :ExceptionList 2020-05-31 22:54:44.299 [Thread-49] ERROR - Error Message :Failed to open queue;MQW101;2085;IIBv10QMGR;OUTaa.Q; 2020-05-31 22:54:44.299 [Thread-49] INFO - ----------------------------------------Transaction Ends---------------------------------------------------------
All these logs are categorically separated like nodename/servername/flowname so that it is easy to identify the log files. The log files gets rolled up automatically and each log file size is 10MB as these are controlled by the log4j settings.
All you’ve to do is, just enable monitoring and see the logs in the folder configured in the log4j config file.
If you’d like to understand how the log4j file looks like and the Java code used, there is a session on this next week Sunday.
Contact us at support@vaithu.com for more details and to purchase.